Когортный анализ
Когортный анализ
Когортный анализ группирует объекты (обычно пользователей) по единому событию старта и сравнивает, как и насколько долго они остаются активными и ценными. Такой подход отделяет эффект времени в системе (сезоны, акции) от эффекта возраста когорты (дни с момента старта).
1) Базовые определения
Когорта (cohort): множество игроков, объединенных событием «рождения» — регистрация, первый депозит, первая игра, первая покупка.
Ось календаря (calendar time): фактические даты (2025-10-01, …).
Ось возраста когорты (cohort age): дни/недели с момента «рождения» (D0, D1, …).
Метрики удержания: D1/D7/D30 (Exact и Rolling), WAU/MAU, Stickiness (DAU/MAU).
Монетизация: ARPU/ARPPU, кумулятивный LTV (на D7/D30/D90).
Единица учета: пользователь (user/master_id) — фиксируйте в паспорте.
2) Виды когорт и когда их выбирать
Acquisition-когорты: по дате регистрации/первого визита — оценка каналов привлечения и онбординга.
Activation/Monetization-когорты: по первому депозиту/покупке — оценка early-monetization и промо.
Feature-когорты: по первому использованию фичи/игровой категории — эффект релизов.
Behavior-когорты: по RFM/паттерну старта (например, «ночные мобильные»).
3) Оси и сетки: как смотреть матрицу
Матрица когорт: строки — когорты (календарь), колонки — возраст (D0…D90).
Сезонность: сравнивайте диагонали (одинаковый календарный день), чтобы отделять сезонные эффекты.
Нормализация: относительные метрики (CR, доли) + кумулятивные (LTV), показывайте обе.
4) Паспорт когорты и метрик (template)
5) Псевдо-SQL: матрица retention (Exact Dn)
sql
WITH regs AS (
SELECT user_id, DATE_TRUNC('day', MIN(ts)) AS cohort_day
FROM event_register
GROUP BY 1
),
act AS (
SELECT user_id, DATE_TRUNC('day', ts) AS act_day
FROM event_activity
),
ages AS (
SELECT r. user_id, r. cohort_day, a. act_day,
(a. act_day - r. cohort_day) AS age_days
FROM regs r
JOIN act a ON a. user_id = r. user_id
),
exact AS (
SELECT cohort_day,
age_days,
COUNT(DISTINCT user_id) AS users_active
FROM ages
GROUP BY 1,2
),
coh_size AS (
SELECT cohort_day, COUNT(DISTINCT user_id) AS cohort_size
FROM regs GROUP BY 1
)
SELECT e. cohort_day,
e. age_days,
e. users_active::decimal / NULLIF(c. cohort_size,0) AS exact_retention
FROM exact e
JOIN coh_size c USING (cohort_day)
WHERE age_days IN (1,7,30,90)
ORDER BY cohort_day, age_days;
Rolling Dn (активность в 1…n день)
sql
WITH days AS (... as above...),
roll AS (
SELECT cohort_day,
CASE WHEN age_days BETWEEN 1 AND 7 THEN 7
WHEN age_days BETWEEN 1 AND 30 THEN 30 END AS bucket,
COUNT(DISTINCT user_id) AS any_active
FROM days
WHERE age_days BETWEEN 1 AND 30
GROUP BY 1,2
)
SELECT r. cohort_day, r. bucket AS Dn,
r. any_active::decimal / s. cohort_size AS rolling_retention
FROM roll r
JOIN (SELECT cohort_day, COUNT(DISTINCT user_id) cohort_size FROM regs GROUP BY 1) s USING (cohort_day)
ORDER BY cohort_day, Dn;
6) Когортный LTV и монетизация
Кумулятивный LTV(Dn): сумма дохода на пользователя когорты к Dn.
ARPU/ARPPU: доход на пользователя/на платящего по Dn.
% платящих: доля с ≥1 платежом к Dn.
sql
WITH reg AS (
SELECT user_id, DATE_TRUNC('day', MIN(ts)) AS cohort_day
FROM event_register GROUP BY 1
),
pay AS (
SELECT user_id, amount, DATE_TRUNC('day', ts) AS pay_day
FROM fact_payments
),
ltv AS (
SELECT r. cohort_day,
(pay_day - r. cohort_day) AS age_days,
SUM(amount) AS rev
FROM reg r JOIN pay p USING (user_id)
WHERE pay_day >= r. cohort_day
GROUP BY 1,2
),
cum AS (
SELECT cohort_day, age_days,
SUM(rev) OVER (PARTITION BY cohort_day ORDER BY age_days ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS rev_cum
FROM ltv
)
SELECT c. cohort_day, c. age_days,
c. rev_cum::decimal / NULLIF(sz. cohort_size,0) AS ltv_per_user
FROM cum c
JOIN (SELECT cohort_day, COUNT(DISTINCT user_id) cohort_size FROM reg GROUP BY 1) sz USING (cohort_day)
WHERE age_days IN (7,30,90)
ORDER BY cohort_day, age_days;
7) Survival/hazard для удержания
Kaplan–Meier: немодельная кривая выживаемости (S(t)) — доля не «оттекших».
Hazard-модели (Cox/логит-по-дням): влияние признаков (канал, страна, платформа, бонусы, контент) на риск оттока.
Практика: строим KM по сегментам, затем объясняем разницу моделью hazard.
8) Сезонность, TZ и календарь
TZ: храните события в UTC, анализируйте в локальной TZ рынка; будьте консистентны.
Календарь: праздники/зарплатные/матчи/релизы — как флаги; сравнивайте когорты аналогичных недель.
Скользящее окно: для недельных/месячных когорт — кратность праздникам и отчетным периодам.
9) Сегментация и атрибуция
Сегменты: канал привлечения, платформа/OS, гео, первый контент, цена/лимиты, платежный метод.
Атрибуция когорты: «кто привел» пользователя — фиксируйте алгоритм (last non-direct, data-driven).
LTV-взвешивание: сравнивайте не только CR, но и LTV(D30/D90) по каналам/сегментам.
10) Визуализация
Теплокарта матрицы когорт (CR/LTV).
Линии тренда D1/D7/D30 по календарю.
Survival/Hazard графики.
Bridge «что изменило LTV к D30»: вклад платящих, частоты, среднего чека.
11) Эксперименты и причинность
A/B: онбординг, туториалы, paywall, офферы. Основная метрика — D7/D30 retention и LTV(D30).
Квазиэксперименты: DiD/синтетический контроль для выката по рынкам.
Uplift-модели: таргетируйте прирост возврата в ре-активации (Qini/AUUC, uplift@k).
12) Эксплуатация и говернанс
Версионирование: `RET_D7_vN`, `LTV_D30_vN`; changelog при смене определения активности/валюты.
SLO свежести: ежедневные когорты — готовность до 06:00 лок.; лаг данных ≤ 1 ч.
Качество: coverage событий, доля дубликатов, доля ботов/фрода вне когорт.
Доступ: RLS/CLS, маскирование PII; экспорт — только агрегаты.
Runbooks: падение D1 (онбординг), D7 (контент), слом событий/идентичностей.
13) Частые ошибки (анти-паттерны)
Смешение осей: сравнивают разные возрасты когорт в разные сезоны без поправок.
Rolling vs Exact: трактуют как одно и то же.
Смешение единиц: сессии в знаменателе, пользователи в числителе.
Агрегирование «средним средних»: вместо суммирования числителей/знаменателей.
Игнор TZ/календаря: смещение D1 на границах дней/праздников.
Нет фильтра ботов/фрода/QA.
Неучтенные рестарты: сплит/merge учетных записей без мостов идентичности.
14) Чек-лист перед публикацией когортного отчета
- Определены событие рождения, единица, TZ, окна активности
- Исключены боты/фрод/QA; идентичности сведены (golden record)
- Построены матрицы CR (Exact/Rolling) и LTV к D7/D30/D90
- Учтены календарь/праздники; сегменты по каналу/платформе/гео
- Добавлены survival/hazard-графики и bridge LTV
- Документированы версии метрик и алгоритм атрибуции
- Настроены SLO свежести, мониторинг coverage/дубликатов/ошибок
- Готовы runbooks по падениям D1/D7 и обрывам событий
Итог
Когортный анализ — это две оси и дисциплина: фиксированный «момент рождения», корректные окна и TZ, матрицы удержания и LTV, сегментация и причинная проверка изменений. Такой подход помогает не только наблюдать кривые, но и принимать решения: где чинить онбординг, какие каналы масштабировать, какой контент и офферы держат игроков дольше и увеличивают LTV.