Когортный анализ — это метод исследования поведения пользователей в динамике по какому-либо параметру. Для исследования пользователей объединяют несколько групп — когорт — по нужному параметру.
Типичны пример параметра для объединения когорт — первое действие. Например, первый заход на сайт, первая покупка, первый пост. По значениям выбранного параметра, разбитым на интервалы и формируется когорта. Например, для "первого действия" делается разбитие на временные - день, неделя, месяц или просто 30 дней. Далее отдельно анализируются пользователи из каждой когорты. Сравнение поведенческих или других показателей из разных когорт и выявление лучших/худших по какому-либо критерию и есть суть когортного анализа.
Алгоритм обсчета когортного анализа
1 Построить из исходного датасета таблицу юзеров, выделив, кроме прочего, дату первой транзакции.
2 Выделить из этой даты месяц — это будет номер когорты: 1 — январская, а 5 — майская. 3 Отфильтровать из основного датасета транзакции только пользователей из N когорты.
4 Из даты транзакции выделить месяц. Из этого месяца вычесть месяц когорты — это будет месяц жизни когорты. Причем чем "моложе" когорта, тем меньше будет у нее месяцев жизни.
5 Группируем отфильтрованный исходный датасет по месяцам жизни с нужными агрегациями. Получим таблицу для когорты, в которой каждая строка — показатели в определенный месяц жизни — кол-во транзакций, кол-во активных пользователей, простая сумма транзакций, кумулятивная сумма (почти LTV)
6 Повторяем пункты 3–5 для всех когорт в цикле или еще как.
7 Складываем полученные таблицы отдельных когорт в одну прибавлением «снизу».
8 Из полученной таблицы делаем сводную, в индексах — месяц жизни, в колонках — когорта, в ячейках — соответствующий показатель. Должна получиться «треугольная» таблица, в которой с одной стороны от диагонали (зависит от того как отсортировать) нули - такие "0" или такие "null"
9 Строим по полученной треугольной таблице хитмап или график.
Обсчет когортного анализа на примере Python
Допустим у нас есть датасет транзакций платежей пользователей за некоторый период. Минимальный набор данных для когортного анализа - id транзакций, дата, id пользователя, сумма. Даты транзакции - с января по июнь.
tr_id |
date |
user_id |
oper_sum |
---|---|---|---|
2080468405 |
2024-01-31 20:18:00 |
user_0001 |
1000 |
2080039460 |
2024-01-30 22:18:00 |
user_0002 |
200 |
2079851261 |
2024-01-30 14:55:00 |
user_0003 |
300 |
формируем таблицу клиентов
client_table = data.groupby('user_id', as_index=False).agg({
'tr_id' : 'count',
'oper_sum' : 'sum',
'date' : ['min', 'max']})
client_table['first_date'] = pd.to_datetime(client_table['first_date'])
client_table['first_month'] = pd.DatetimeIndex(client_table['first_date']).month
Формируем таблицу когорт, агрегируя основные показатели по таблице транзакций, а затем вычисляя необходимые метрики уже по таблице когорты.
calendar_ch_dynamika = pd.DataFrame([])
for c in range(1, 8):
ch = set(client_table.query('first_month == @c')['user_id'])
dd = data.query('user_id.isin(@ch)').groupby('tr_month', as_index = False)
.agg({'tr_id' : 'count', 'user_id' : 'nunique', 'oper_sum' : 'sum'})
dd = dd.rename(columns = {'tr_id' : 'tr_count', 'user_id' : 'user_count'})
dd['avg_sum'] = dd['oper_sum'] / dd['tr_count']
dd['ch'] = f'{c}_{calendar.month_abbr[c]}'
dd['m_live'] = dd['tr_month'] - c + 1
dd['ltv'] = dd['oper_sum'].cumsum() / len(ch)
dd['ltv_m'] = dd['oper_sum'].cumsum() / (len(ch) * dd['m_live'])
dd['rr'] = dd['user_count'] / len(ch)
dd['cr'] = dd['user_count'] / dd['user_count'].shift(1)
dd['avg_sum'] = dd['oper_sum'] / dd['tr_count']
calendar_ch_dynamika = pd.concat([calendar_ch_dynamika, dd])
calendar_ch_dynamika = calendar_ch_dynamika.reset_index(drop=True)
Получаем такую таблицу:
tr_month |
tr_count |
user_count |
oper_sum |
avg_sum |
ch |
m_live |
ltv |
ltv_m |
rr |
cr |
---|---|---|---|---|---|---|---|---|---|---|
1.0 |
256 |
251 |
128564 |
502.20 |
1_Jan |
1.0 |
512.21 |
512.21 |
1.00 |
NaN |
2.0 |
236 |
230 |
121464 |
514.68 |
1_Jan |
2.0 |
996.13 |
498.06 |
0.92 |
0.92 |
3.0 |
230 |
224 |
115275 |
501.20 |
1_Jan |
3.0 |
1455.39 |
485.13 |
0.89 |
0.97 |
4.0 |
216 |
211 |
119575 |
553.59 |
1_Jan |
4.0 |
1931.78 |
482.95 |
0.84 |
0.94 |
5.0 |
227 |
212 |
129985 |
572.62 |
1_Jan |
5.0 |
2449.65 |
489.93 |
0.84 |
1.00 |
6.0 |
179 |
165 |
104687 |
584.84 |
1_Jan |
6.0 |
2866.73 |
477.79 |
0.66 |
0.78 |
7.0 |
223 |
208 |
128052 |
574.22 |
1_Jan |
7.0 |
3376.90 |
482.41 |
0.83 |
1.26 |
2.0 |
12 |
12 |
5750 |
479.17 |
2_Feb |
1.0 |
479.17 |
479.17 |
1.00 |
NaN |
3.0 |
7 |
7 |
3650 |
521.43 |
2_Feb |
2.0 |
783.33 |
391.67 |
0.58 |
0.58 |
4.0 |
9 |
9 |
4250 |
472.22 |
2_Feb |
3.0 |
1137.50 |
379.17 |
0.75 |
1.29 |
5.0 |
8 |
8 |
4150 |
518.75 |
2_Feb |
4.0 |
1483.33 |
370.83 |
0.67 |
0.89 |
6.0 |
6 |
6 |
3650 |
608.33 |
2_Feb |
5.0 |
1787.50 |
357.50 |
0.50 |
0.75 |
7.0 |
7 |
7 |
3750 |
535.71 |
2_Feb |
6.0 |
2100.00 |
350.00 |
0.58 |
1.17 |
Формируем из нее сводную таблицу. В индексах — месяц жизни, в колонках — когорта, в ячейках — соответствующий показатель
ch_heattable = calendar_ch_dynamika.pivot(index = 'm_live',
columns = 'ch',
values = 'user_count')
ch |
1_Jan |
2_Feb |
3_Mar |
4_Apr |
5_May |
6_Jun |
7_Jul |
---|---|---|---|---|---|---|---|
m_live |
|
|
|
|
|
|
|
1.0 |
251.0 |
12.0 |
53.0 |
42.0 |
275.0 |
698.0 |
146.0 |
2.0 |
230.0 |
7.0 |
12.0 |
13.0 |
92.0 |
158.0 |
NaN |
3.0 |
224.0 |
9.0 |
15.0 |
12.0 |
98.0 |
NaN |
NaN |
4.0 |
211.0 |
8.0 |
9.0 |
10.0 |
NaN |
NaN |
NaN |
5.0 |
212.0 |
6.0 |
11.0 |
NaN |
NaN |
NaN |
NaN |
6.0 |
165.0 |
7.0 |
NaN |
NaN |
NaN |
NaN |
NaN |
7.0 |
208.0 |
NaN |
NaN |
NaN |
NaN |
NaN |
NaN |
Осталось построить графики - хитмап и линейный график
, ax = plt.subplots(1, 2, figsize=(16,5))
ch_heattable = ch_heattable.iloc[:, ::-1]
sns.heatmap(ch_heattable, xticklabels=ch_heattable.columns, cmap='RdYlGn',
annot = True, fmt=f'.0f', cbar=False, ax = ax[0])
ax[1].plot(ch_heattable, label = ch_heattable.columns)
ax[0].set_xlabel('Когорта')
ax[0].set_ylabel('Месяц жизни')
ax[1].set_xlabel('Месяц жизни')
ax[1].set_ylabel(f'user_count')
ax[1].legend(title='Когорты')
plt.suptitle(f'Динамика параметра "user_count" в когортах по месяцам жизни')
plt.show()
Получатся такой график когортного анализа динамики количества активных пользователей по месяцам жизни

Заключение
Надеюсь, эта статья поможет вам разобраться с последовательностью действий при обсчете когортного анализа. Выводы из полученных данных вне рассмотрения данной статьи.
Автор: delffine