RFM-анализ - это метод сегментации клиентов, основанный на их покупательской активности. С помощью RFM-анализа можно, во-первых, оценить доли клиентов в каждом из сегментов. Во-вторых, вспоминая постулат, что клиента легче удержать, чем привлечь. К пользователям, попавшими в разные сегменты, можно применять различные стратегии удержания / поощрения.
Сегментировать клиентов вообще можно различными способами. Как следует из названия, в RFM анализе сегментация идет по трем измерениям:
Recency (давность) — как давно клиент покупал
Frequency (частота) — как часто он покупает
Monetary (деньги) — какую сумму тратит
В каждом измерении выделяют обычно три ранга. Условно это градации "Хорошо", "Нормально", "Плохо". Ранги обычно кодируются как 1, 2, 3. Причем, как-то нет общепринятой договоренности, что кодирует "Хорошо" - 1 или 3. Поэтому лучше приводить расшифровку, как именно вы закодировали ранги.
Итого получается три измерения по три ранга или 27 сочетаний (сегментов) пользователей: от постоянно тратящих большие суммы, причем последний раз вот совсем недавно, до сделавших давным-давно один мааааленький платеж.
Как работать с клиентами из разных сегментов, я рассматривать в этой статье не буду. Перейду сразу к пошаговой инструкции, как обсчитывать RFM-анализ.
Общий алгоритм обсчета
1 Построить из исходного датасета таблицу пользователей, определив для каждого количество транзакций, общую сумму платежей, дату первой и последней операции.
2 Вычислить дополнительные показатели.
3 Определить границы рангов и присвоить их пользователям.
4 Построить RFM-таблицу, сгруппировав данные по рангам.
5 На основе полученной RFM-таблицы построить хитмап по определенному показателю
Обсчет RFM-анализа на примере Python
Допустим у нас есть датасет транзакций платежей пользователей за некоторый период. Минимальный набор данных для RFM-анализа: id транзакций, дата транзакции, id пользователя и сумма транзакции.
id | date | user_id | donate |
---|---|---|---|
332345 | 2023-01-01 | 406173 | 1000.00 |
332331 | 2023-01-01 | 350353 | 300.00 |
332333 | 2023-01-01 | 419745 | 500.00 |
332337 | 2023-01-01 | 343691 | 300.00 |
332339 | 2023-01-01 | 343285 | 1000.00 |
Шаг 1. Формируем таблицу клиентов
Далаем groupby по пользователям c агрегациями: количество транзакций, общая сумма транзакций, первая и последняя дата операций:
user_table = tranz.groupby('user_id').agg({
'id' : 'count',
'donate' : 'sum'},
'date' : ['min', 'max'])
user_table.columns = ('tr_count', 'donate_sum', 'first_date', 'last_date')
user_id | tr_count | donate_sum | first_date | last_date |
---|---|---|---|---|
343905 | 4 | 5280.00 | 2023-07-28 | 2024-10-23 |
343915 | 5 | 1500.00 | 2024-03-15 | 2024-11-06 |
343919 | 4 | 204000.00 | 2023-10-17 | 2024-06-11 |
343923 | 2 | 3200.00 | 2023-05-16 | 2023-05-16 |
343941 | 2 | 8710.00 | 2023-03-09 | 2023-12-29 |
Шаг 2. Вычисляем дополнительные показатели
В дальнейшем нам потребуется дополнительные показатели, которые можно вычислить из только что сагрегированных метрик. Например, для измерения F надо вычислить частоту - это количество операций в единицу времени жизни клиента (день / неделя / месяц). Для этого количество транзакций у нас уже есть, но надо вычислить время жизни - это разница между первой и последней операцией.
user_table['day_on'] = user_table['last_date'] - user_table['first_date']
user_table['day_on'] = user_table['day_on'].dt.days + 1
user_table['oper_frec'] = user_table['tr_count'] / user_table['month_on']
user_id | tr_count | donate_sum | first_date | last_date | day_on | oper_frec |
---|---|---|---|---|---|---|
343905 | 4 | 5280.00 | 2023-07-28 | 2024-10-23 | 454 | 0.25 |
343915 | 5 | 1500.00 | 2024-03-15 | 2024-11-06 | 237 | 0.62 |
343919 | 4 | 204000.00 | 2023-10-17 | 2024-06-11 | 239 | 0.50 |
343923 | 2 | 3200.00 | 2023-05-16 | 2023-05-16 | 1 | 2.00 |
343941 | 2 | 8710.00 | 2023-03-09 | 2023-12-29 | 296 | 0.20 |
Шаг 3. Определение границ рангов
Это самый нетривиальный этап расчета, потому что разделить измерения RFM на ранги можно различными способами исходя из различных задач и вводных. Самый очевидный - разделить, так чтобы в каждый ранг попало примерно одинаковое количество пользователей. Проще всего это сделать через процентили 0.33 и 0.66 соответственно. Однако, стоит учесть, что очевидные границы на 0.33 и 0.66 процентилях не всегда удачно делят пользователей на три сопоставимые части. А в некоторых случаях, границы стоит выбирать в абсолютных значениях частоты, долготы или денег, исходя из поставленной задачи, практики или логики бизнес-процессов.
В Python такое деление можно сделать с помощью функций pd.cut или pd.qcut
labels = ['1', '2', '3']
user_table['R1'] = pd.cut(user_table['day_last'], bins=[-1, 31, 90, 1000], labels=labels)
user_table['F1'] = pd.cut(user_table['oper_frec'], bins=[-1, 0.9, 2, 1000], labels=labels)
user_table['M1'] = pd.qcut(user_table['oper_sum'], q=[0, .33, .66, 1.], labels=labels)
Также можно использовать конструкции с lambda x или lambda row. В этом случае можно задавать условия деление на когорты с учетом значений в других колонках. Например, ниже "разовые" клиенты определены по одному дню жизни, пусть даже они сделали 10 платежей в этот день.
user_table_rfm['R'] = user_table_rfm['day_last']\
.apply(lambda x: '1' if x < 45 else ('2' if x < 180 else '3'))
user_table_rfm['F'] = user_table_rfm\
.apply(lambda row: '3' if row['day_on'] == 1 else ('2' if row['oper_frec'] < 0.8 else '1'), axis=1)
user_table_rfm['M'] = user_table_rfm['donate_sum']\
.apply(lambda x: '1' if x > 2000 else ('2' if x > 750 else '3'))
user_id | tr_count | donate_sum | first_date | last_date | day_on | oper_frec | R | F | M |
---|---|---|---|---|---|---|---|---|---|
343905 | 4 | 5280.00 | 2023-07-28 | 2024-10-23 | 454 | 0.25 | 2 | 2 | 1 |
343915 | 5 | 1500.00 | 2024-03-15 | 2024-11-06 | 237 | 0.62 | 2 | 2 | 2 |
343919 | 4 | 204000.00 | 2023-10-17 | 2024-06-11 | 239 | 0.50 | 3 | 2 | 1 |
343923 | 2 | 3200.00 | 2023-05-16 | 2023-05-16 | 1 | 2.00 | 3 | 1 | 1 |
343941 | 2 | 8710.00 | 2023-03-09 | 2023-12-29 | 296 | 0.20 | 3 | 2 | 1 |
Шаг 4. Строим RFM таблицу
Теперь строим RFM таблицу, группируя талицу пользователей по RFM рангам и проводя агрегацию: количество пользователей, суммарное количество транзакций и сумму платежей для каждого сочетания RFM
rfm_table = user_table_rfm.groupby(['R', 'F', 'M'], as_index = False)\
.agg({'tr_count' : ['count', 'sum'],
'donate_sum' : 'sum'})
rfm_table.columns = ['R', 'F', 'M', 'RFM', 'rfm_users', 'rfm_tr', 'rfm_sum']
Получаем таблицу, в которой максимум на 27 строк - комбинация 3 измерений по 3 ранга. Может быть и меньше, если в какое сочетание RFM не попало ни одного пользователя.
R | F | M | rfm_users | rfm_tr | rfm_sum |
---|---|---|---|---|---|
1 | 1 | 1 | 368 | 7694 | 8860328.26 |
1 | 1 | 2 | 20 | 158 | 27231.00 |
1 | 1 | 3 | 16 | 70 | 5944.00 |
1 | 2 | 1 | 152 | 1112 | 2814391.00 |
1 | 2 | 2 | 43 | 182 | 64151.00 |
Шаг 5. Выводим RFM таблицу в виде heatmap
Для вывода красивой RFM таблицы в виде тепловой карты надо еще провернуть один "фокус": так как таблица плоская, а измерений у нас три, то надо комбинацию двух измерений разместить на одной оси, а третье измерение на другой оси. Например, R И F по вертикали, а M горизонтали. Делается это через сводную таблицу pivot. В итоге должна получится матрица 9*3, в которой в индексах ранги R И F, в колонках ранги M, а в ячейках значения нужного показателя - количество пользователей, количество транзакций или сумма платежей. Эту сводную таблицу уже и выводим в виде heatmap.
fig = plt.figure(figsize = (12, 6))
rfm_pivot = rfm_table.pivot(index = ['R', 'F'], columns = 'M', values = 'rfm_sum').fillna(0)
sns.heatmap(rfm_pivot, cmap='RdYlGn', annot=True, cbar=False, fmt=',.0f')
plt.title(f'Тепловая карта RFM анализа по параметру Сумма платежей')
plt.show()
В итоге получится такая тепловая карта. Останется чуть-чуть поколдовать с метками осей, чтобы на хитмапе сразу было видно, что ранг 1 - это "Хорошо".

Заключение
Как интерпретировать полученные тепловые карты RFM-матриц - отдельная большая тема, которую я в этой статье касаться не буду. Надеюсь, кому-то пригодиться эта пошаговая инструкция обсчета RFM анализа.