Как обсчитать когортный анализ в 9 шагов
Когортный анализ — это метод исследования поведения пользователей в динамике по какому-либо параметру. Для исследования пользователей объединяют несколько групп — когорт — по нужному параметру.
Типичны пример параметра для объединения когорт — первое действие. Например, первый заход на сайт, первая покупка, первый пост. По значениям выбранного параметра, разбитым на интервалы и формируется когорта. Например, для «первого действия» делается разбитие на временные — день, неделя, месяц или просто 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()
Получатся такой график когортного анализа динамики количества активных пользователей по месяцам жизни

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