Когортный анализ удержания

Содержание
  1. Кратко
  2. Содержание
  3. Пример
  4. Как пишется
  5. Определение когорты
  6. Период
  7. Смещение периода
  8. Как понять
  9. Почему матрица треугольная
  10. Правый край ненадёжен
  11. Столбец 0 = 100% по определению
  12. Диагонали и сезонность
  13. Агрегатное удержание врёт
  14. Ограничения
  15. Классическая когорта ≠ rolling retention
  16. Нет взвешивания по активности
  17. Сравнение когорт — не A/B-тест
  18. Малые когорты шумят
  19. Survivorship bias в долгосрочных столбцах
  20. Подсказки
  21. На практике
  22. На собеседовании
  23. Ссылки

Кратко

Когортный анализ группирует пользователей по моменту старта и отслеживает, какая доля возвращается в каждый следующий период. Это отвечает на вопрос «удерживает ли продукт пользователей со временем» — но честнее, чем единое число retention.

Когорта — группа людей, у которых первое действие пришлось на один период (обычно месяц). Матрица удержания: строки — когорты, столбцы — месяцы с момента старта, ячейка — доля вернувшихся. Получается треугольная таблица: у свежих когорт заполнены только первые столбцы.

Содержание

Пример

Построение матрицы в Pandas. На входе таблица событий events с user_id и event_date:

import pandas as pd
import numpy as np

df = pd.read_csv(
  'events.csv', parse_dates=['event_date']
)

# Месяц первого действия = когорта
cohort = (
  df.groupby('user_id')['event_date'].min()
  .reset_index()
  .rename(columns={'event_date': 'cohort_month'})
)
cohort['cohort_month'] = (
  cohort['cohort_month'].dt.to_period('M')
)

# Сливаем когорту обратно к событиям
events = df.merge(cohort, on='user_id')
events['activity_month'] = (
  events['event_date'].dt.to_period('M')
)
# Номер месяца с момента старта (period diff)
events['period'] = (
  events['activity_month'].astype('int64')
  - events['cohort_month'].astype('int64')
)

# Матрица: уникальные пользователи на когорту
counts = (
  events
  .groupby(['cohort_month', 'period'])
  ['user_id'].nunique()
  .reset_index()
)
size = cohort.groupby('cohort_month')['user_id'].nunique()
retention = counts.pivot(
  index='cohort_month',
  columns='period',
  values='user_id',
)
# Делим на размер когорты → доли
retention = retention.divide(size, axis=0)

На выходе retention — DataFrame, где retention.loc['2025-01', 3] — доля январской когорты, вернувшейся через 3 месяца.

Как пишется

Определение когорты

Когорта задаётся точкой первого действия. Что считать «первым» — решает задача:

  • Первый визит — для продукта, где активность = присутствие.
  • Первая покупка — для монетизации и LTV.
  • Регистрация — для онбординг-метрик.

Первый визит безопаснее по умолчанию: он ловит всех, кто попал в продукт. Первая покупка отсекает тех, кто зарегистрировался, но не купил — они отдельная история.

Период

Период — шаг столбцов. Месяц — стандарт для B2C, неделя — для ранних стартапов с быстрым циклом, день — для мобильных игр. Период выбирают так, чтобы внутри него большинство пользователей совершили возврат хотя бы раз.

Смещение периода

period — разница между месяцем активности и месяцем когорты. period = 0 — месяц старта, period = 1 — следующий месяц. Это не «календарный месяц номер 2», а «месяц N с момента старта конкретной когорты».

Как понять

Почему матрица треугольная

Когорта «январь 2025» к марту имеет period 0, 1, 2. Когорта «март 2025» к тому же моменту имеет только period 0. Поэтому правый верхний угол пустой — будущие периоды ещё не наступили. Это не пропуски в данных, а структура.

Правый край ненадёжен

У поздних столбцов меньше когорт, поэтому среднее по столбцу прыгает. Если в period = 6 попала только одна когорта, её 35% — это не «удержание на шестом месяце», а «удержание одной когорты». Не делайте выводов о долгосрочном удержании по правому краю, пока не наберётся 5–10 когорт.

Столбец 0 = 100% по определению

В нулевом месяце клиент присутствует всегда — он же только что стартовал. Поэтому period = 0 всегда 100% (или близко). Этот столбец не несёт сигнала об удержании, он — база. Его оставляют для самопроверки: если period = 0 не 100%, в коде ошибка.

Диагонали и сезонность

Если у всех когорт провал на period = 3, это цикл продукта. Если провал в конкретном календарном месяце у всех когорт одновременно — сезонность или общая проблема (сбой, релиз). Диагональ «по календарному месяцу» ловит сезонность; «по периоду» — ловит продуктовый цикл.

Агрегатное удержание врёт

Единый «30% monthly retention» может означать:

  • Все когорты удерживают по 30%.
  • Новые удерживают 50%, старые скатились до 10%.

Второй случай — продуктовая проблема, но агрегат его прячет. Когортная матрица разделяет эти сценарии: на строках видно, кто держится, кто падает.

Ограничения

Когортная матрица — мощный, но не единственный инструмент. Вот что она не показывает:

Классическая когорта ≠ rolling retention

Классическая когортная матрица считает удержание как «был ли пользователь активен хотя бы раз в период N». Rolling retention (или возвратное удержание) считает иначе: пользователь удержан, если он вернулся в период N или позже. Rolling retention всегда выше или равен классическому и лучше отражает долгосрочную лояльность, но не видит «провалов» в активности.

Нет взвешивания по активности

Матрица не различает пользователя, который зашёл один раз в месяц, и того, кто заходит ежедневно. Для метрик «вовлечённости» (daily active, session length) нужны отдельные инструменты — поверх когортной разбивки.

Сравнение когорт — не A/B-тест

Разница в удержании между когортами «январь» и «февраль» может быть вызвана сезонностью, изменением каналов трафика или составом аудитории — не только продуктовым изменением. Для каузальных выводов нужен A/B-тест внутри одной когорты.

Малые когорты шумят

Если в когорте меньше ~100 пользователей, доли retention скачут от 0% до 100% от периода к периоду. Для B2B или нишевых продуктов с малым числом клиентов когортный анализ лучше делать по неделям или дням, а не по месяцам.

Survivorship bias в долгосрочных столбцах

Правый край матрицы состоит из когорт, которые «дожили» до этого периода. Если продукт менялся — а он меняется — старые когорты пережили другую версию продукта. Их удержание не предсказывает поведение новых когорт на тех же сроках.

Подсказки

  • Когорту определяйте под вопрос: визит / покупка / регистрация.
  • period = 0 всегда 100% — если нет, ищите баг в коде.
  • Правый край матрицы нечитаем при малом числе когорт — не делайте выводов.
  • Срезайте по каналу, платформе, плану: когорты из платной рекламы ведут себя иначе, чем органика.
  • Удержание без цели — просто число. Сравнивайте с бенчмарком продукта или прошлыми когортами.
  • Обновляйте матрицу ежемесячно; сдвиг шага пересчитывает все когорты.
  • Не путайте retention с churn: retention = доля вернувшихся, churn = доля ушедших.

На практике

В проекте volta-banking — аналитика необанка — когортная матрица нашла step-change, который агрегат прятал. В сентябре 2024 поменяли прогресс-бар в KYC-флоу (проект 2: A/B-тест дал +4,82 пп, p<0,0001). Когорты до и после фикса разошлись:

  • Пост-фикс когорты (сен–дек 2024) показывают M1 retention на +10–12 пп выше, чем пре-фикс.
  • Все когорты выходят на плато к M5–M6 — естественный сегмент лояльных.
  • Пост-фикс плато стабилизируется выше (~25–28%) — фикс поднял и активацию, и долгосрочное удержание.

Что дала матрица, чего не дал бы агрегат:

  • Видно, где именно улучшилось (M1, не M0) — значит, фикс повлиял на возврат, а не на старт.
  • Видно плато — значит, после M5 качать удержание дороже, чем активацию.
  • Срезы по каналу и плану показали 3× разрыв LTV между Premium и Free — отдельная гипотеза для монетизации.

Рецепт старта:

  1. Возьмите сырые события, определите когорту по первому визиту.
  2. Считайте period через разность astype('int64') периодов (быстрее и чище, чем apply).
  3. Пивот в матрицу, делите на размер когорты.
  4. Сначала читайте столбец M1 — он самый чувствительный к качеству онбординга.
  5. Добавьте срезы: канал, план, платформа. Один срез = одна гипотеза.
  6. Сравнивайте когорты до/после изменения продукта — step-change виден на строках.

На собеседовании

❓ Чем retention отличается от churn?

Retention — доля пользователей, вернувшихся в период N. Churn — доля ушедших. В простом случае retention + churn = 1, но определения расходятся: retention считают от исходной когорты, churn часто считают от активной базы на начало периода. Поэтому «retention 30%» и «churn 70%» могут описывать разное. Уточняйте определение на месте.

— Nikita Boyarkin

❓ Когорту определять по первому визиту или по первой покупке?

Зависит от вопроса. Первая покупка — для монетизации, LTV, RFM. Первый визит — для удержания как присутствия, онбординга, активации. Частая ошибка — считать retention по покупкам там, где продукт freemium и 80% никогда не платят: матрица покажет «все отваливаются», хотя пользователи просто не покупают, а заходят. Подбирайте определение под метрику вопроса.

— Nikita Boyarkin

❓ Почему правый край когортной матрицы ненадёжен и как с этим жить?

У поздних столбцов меньше когорт — к марту cohort «январь» дошла до M2, а «март» только до M0. Среднее по столбцу M6 при одной когорте — это не «удержание на шестом месяце», а удержание одной когорты. Жить так: не делайте выводов о долгосрочном удержании, пока не наберётся 5–10 когорт в столбце; для прогноза LTV используйте только заполненные столбцы и модель распадения, а не среднее по правому краю.

— Nikita Boyarkin

Ссылки

  • Cohort Analysis with Python — полный код когортной матрицы + визуализация в проекте volta-banking.
  • Cohort Analysis — Wikipedia — базовая терминология и история метода.
  • Retention Cohorts — Amplitude — продуктовый взгляд на когорты: когда какой тип удержания выбирать.
  • How to Lie with Retention — Andrew Chen о том, как манипулируют кривыми удержания (и как не попасться).
  • Volta Dashboard — интерактивный дашборд с когортной матрицей, срезами и LTV-моделью.

Смотрите также

← К статьям