Когортный анализ: SQL-запрос, визуализация и интерпретация — new-lvl.pro
new-lvl.pro · Статьи · Метрики
Статья // 14 мин чтения

Когортный
анализ: SQL, heatmap
и интерпретация

Как разбить пользователей на когорты, построить таблицу retention в SQL, визуализировать heatmap и увидеть то, что прячется за общими средними.

Что такое когортный анализ и зачем он нужен

Когорта — группа пользователей, объединённых по времени какого-то события. Чаще всего — по месяцу регистрации. Все, кто зарегистрировался в январе — когорта «Январь». В феврале — когорта «Февраль».

Когортный анализ — это способ отслеживать поведение этих групп со временем. Вместо того чтобы смотреть «средний retention за месяц» (который смешивает всех пользователей в кучу), вы смотрите: «как ведёт себя январская когорта через 1, 2, 3 месяца? А февральская?».

Это даёт принципиально другую картину. Общий retention может стоять на месте, а в когортах — расти. Или наоборот: общее среднее растёт за счёт притока новых, а старые когорты гибнут.

Почему средние врут, а когорты — нет

Средний Retention Скрывает проблему
Ситуация: общий Day-30 Retention = 20% и не меняется три месяца.
Кажется: стабильно, всё в порядке.
На самом деле: retention старых когорт падает (с 25% до 15%), но это маскируется притоком новых пользователей из нового канала с retention 22%. Проблема растёт, но среднее стоит на месте.
Когортный Retention Показывает правду
Когортный анализ: видно, что Retention январской когорты = 25%, февральской = 20%, мартовской = 15%. Чёткий нисходящий тренд. Можно искать причину: что изменилось в продукте? в онбординге? в трафике?

Зачем аналитику когортный анализ

Оценка изменений в продукте — новый онбординг улучшил retention? Сравните когорту «до» и «после»
Оценка каналов привлечения — канал A привёл дешёвых пользователей, но их retention в 2 раза ниже канала B
Прогнозирование LTV — зная кривую когорты за 6 месяцев, можно прогнозировать LTV на 12
Обнаружение деградации — если свежие когорты хуже предыдущих, продукт теряет качество

Как правильно строить когортный анализ

5 шагов построения
1
Определите событие когорты: чаще всего — дата регистрации или первой покупки. Реже — дата установки приложения или первого визита
2
Выберите гранулярность: неделя или месяц. Недельные когорты — точнее, но шумнее. Месячные — стабильнее, подходят для большинства задач
3
Определите метрику: retention (% вернувшихся), revenue (выручка когорты), заказы, или другое целевое действие
4
Определите период (period): сколько месяцев/недель прошло с момента попадания в когорту. Период 0 — месяц регистрации, период 1 — следующий месяц и т.д.
5
Визуализируйте как heatmap: строки — когорты, столбцы — периоды, ячейки — значение метрики. Цвет — от высокого к низкому

SQL-запросы для когортного анализа

Запрос 1: Когортный Retention (основной)

Самый важный запрос — его спрашивают на каждом втором собесе. Строит полную матрицу «когорта × период» с процентом retention.

Когортный Retention — полная матрица PostgreSQL
WITH cohorts AS (
  SELECT
    user_id,
    DATE_TRUNC('month', registration_date) AS cohort
  FROM users
),
activity AS (
  SELECT DISTINCT
    user_id,
    DATE_TRUNC('month', activity_date) AS active_month
  FROM user_activity
),
cohort_data AS (
  SELECT
    c.cohort,
    DATE_PART('month',
      AGE(a.active_month, c.cohort)
    )::INT AS period,
    COUNT(DISTINCT c.user_id) AS active_users
  FROM cohorts c
  JOIN activity a ON c.user_id = a.user_id
  WHERE a.active_month >= c.cohort
  GROUP BY c.cohort, period
)
SELECT
  cohort,
  period,
  active_users,
  ROUND(100.0 * active_users
    / FIRST_VALUE(active_users) OVER (
        PARTITION BY cohort ORDER BY period
      ), 1
  ) AS retention_pct
FROM cohort_data
ORDER BY cohort, period;
// Разбор логики
CTE cohorts — определяем когорту каждого пользователя. CTE activity — уникальные месяцы активности. CTE cohort_data — считаем, сколько пользователей из каждой когорты были активны в каждом периоде. Финальный SELECT — FIRST_VALUE берёт размер когорты в периоде 0 и делит на него каждый последующий период.

Запрос 2: Когортный Revenue (LTV по когортам)

Кумулятивная выручка по когортам PostgreSQL
WITH cohorts AS (
  SELECT user_id,
    DATE_TRUNC('month', registration_date) AS cohort
  FROM users
),
cohort_revenue AS (
  SELECT
    c.cohort,
    DATE_PART('month',
      AGE(DATE_TRUNC('month', o.order_date), c.cohort)
    )::INT AS period,
    SUM(o.amount) AS revenue,
    COUNT(DISTINCT c.user_id) AS cohort_size
  FROM cohorts c
  JOIN orders o ON c.user_id = o.user_id
    AND o.order_date >= c.cohort
  GROUP BY c.cohort, period
)
SELECT
  cohort, period,
  ROUND(revenue / cohort_size, 0) AS arpu_period,
  ROUND(
    SUM(revenue) OVER (
      PARTITION BY cohort ORDER BY period
    ) / cohort_size, 0
  ) AS cumulative_ltv
FROM cohort_revenue
ORDER BY cohort, period;

Этот запрос строит кривую LTV для каждой когорты. Можно наложить кривые друг на друга и увидеть: новые когорты платят больше или меньше, чем старые?

Запрос 3: Когортная таблица для pivot / heatmap

Чтобы визуализировать heatmap, нужно «развернуть» таблицу — столбцы = периоды. В PostgreSQL это можно сделать через условную агрегацию.

Pivot-таблица для heatmap PostgreSQL
WITH base AS (
  -- используем результат запроса 1 (cohort, period, retention_pct)
  ...
)
SELECT
  cohort,
  MAX(CASE WHEN period = 0 THEN retention_pct END) AS m0,
  MAX(CASE WHEN period = 1 THEN retention_pct END) AS m1,
  MAX(CASE WHEN period = 2 THEN retention_pct END) AS m2,
  MAX(CASE WHEN period = 3 THEN retention_pct END) AS m3,
  MAX(CASE WHEN period = 4 THEN retention_pct END) AS m4,
  MAX(CASE WHEN period = 5 THEN retention_pct END) AS m5
FROM base
GROUP BY cohort
ORDER BY cohort;

Как читать когортный heatmap

Heatmap — стандартный способ визуализации когортного анализа. Строки — когорты, столбцы — периоды жизни, ячейки — retention. Цвет кодирует значение: яркий = высокий, тусклый = низкий.

КогортаРазмерM0M1M2M3M4M5
Янв 20254 200100%38%24%18%15%13%
Фев 20255 100100%41%27%20%17%
Мар 20254 800100%45%32%25%
Апр 20256 300100%48%35%
Май 20255 500100%46%
Июн 20257 000100%

Три направления чтения

По строке →
Кривая затухания
Как одна когорта теряет пользователей со временем. Здоровый паттерн: резкое падение первые 1–2 месяца, потом выход на плато.
По столбцу ↓
Тренд по когортам
Становится ли один и тот же период лучше от когорты к когорте? M1: 38% → 41% → 45% → 48% — продукт улучшается.
По диагонали ↘
Один момент времени
Все когорты в один календарный месяц. Полезно для обнаружения внешних событий, которые повлияли на всех сразу.
// Главный вопрос
Столбец вниз — самый важный. Если M1 retention растёт от когорты к когорте — продукт становится лучше. Если падает — что-то ломается. Это первое, на что смотрят в BigTech при анализе когорт.

5 паттернов в когортной таблице

Паттерн 1: Плато — продукт нашёл ядро

Retention падает первые 2–3 месяца, потом стабилизируется. Это значит, что часть пользователей нашла ценность и остаётся надолго. Это хорошо. Чем выше плато — тем сильнее product-market fit. Для e-commerce плато 10–15% — нормально. Для SaaS 60–70% — ожидаемо.

Паттерн 2: Retention не стабилизируется — протечка

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

Паттерн 3: Улучшение по когортам — продукт растёт

M1 retention: 38% → 41% → 45% → 48% — каждая следующая когорта лучше удерживается. Это отлично. Значит, изменения в продукте работают. Важно: убедитесь, что рост не объясняется изменением микса каналов привлечения.

Паттерн 4: Деградация по когортам — тревога

M1: 48% → 45% → 41% → 38% — каждая следующая когорта хуже. Нужно разбираться. Причины: сменился канал привлечения (пришли менее целевые), сломался онбординг, конкуренты перехватывают, сезонность.

Паттерн 5: Одна когорта выбивается — ищите событие

Все когорты ведут себя похоже, но одна — сильно лучше или хуже. Ищите, что произошло в тот месяц: маркетинговая кампания, релиз фичи, баг, сезонный трафик. Одна аномальная когорта — подсказка, где копать.

// Ловушка: размер когорты
Маленькие когорты дают шумные данные. Если когорта — 50 человек, а retention = 40%, это 20 человек. Плюс-минус 5 человек (случайность) — и retention прыгает от 30% до 50%. Не делайте выводов по маленьким когортам.

Когортный анализ — не только Retention

Retention — самый популярный, но не единственный вариант когортного анализа. Те же принципы работают для любой метрики, которую вы хотите отследить по когортам.

Метрика в когортахЧто показываетКогда использовать
Revenue / LTVСколько денег приносит когорта со временемОценка монетизации, сравнение каналов по ценности
Заказы на пользователяКак растёт частота покупокE-commerce, маркетплейсы — оценка лояльности
Conversion to paidКакая доля когорты стала платящейFreemium-продукты, SaaS
Feature adoptionКакая доля когорты начала использовать фичуПосле запуска новой функциональности
Support ticketsСколько обращений генерирует когортаОценка качества онбординга, UX-проблем

Когортный анализ по каналам привлечения

Мощный приём: строить отдельные когортные таблицы для каждого канала. Это показывает не просто «сколько стоит привлечение», а «какое качество пользователей приводит каждый канал». Канал с дешёвым CAC, но плохим retention — возможно, не лучший выбор.

Retention по когортам и каналам PostgreSQL
WITH cohorts AS (
  SELECT
    user_id,
    acquisition_channel,
    DATE_TRUNC('month', registration_date) AS cohort
  FROM users
),
activity AS (
  SELECT DISTINCT user_id,
    DATE_TRUNC('month', activity_date) AS active_month
  FROM user_activity
)
SELECT
  c.acquisition_channel,
  c.cohort,
  DATE_PART('month', AGE(a.active_month, c.cohort))::INT AS period,
  COUNT(DISTINCT c.user_id) AS active_users,
  ROUND(100.0 * COUNT(DISTINCT c.user_id)
    / FIRST_VALUE(COUNT(DISTINCT c.user_id)) OVER (
        PARTITION BY c.acquisition_channel, c.cohort
        ORDER BY DATE_PART('month', AGE(a.active_month, c.cohort))
      ), 1
  ) AS retention_pct
FROM cohorts c
JOIN activity a ON c.user_id = a.user_id
WHERE a.active_month >= c.cohort
GROUP BY c.acquisition_channel, c.cohort,
  DATE_PART('month', AGE(a.active_month, c.cohort))
ORDER BY 1, 2, 3;
// Ключевое отличие
PARTITION BY включает acquisition_channel — теперь FIRST_VALUE берёт размер когорты для каждого канала отдельно. Это даёт retention по когортам внутри каждого канала привлечения.

Вопросы про когортный анализ на собесе

🎤 Типичные вопросы
  • Что такое когортный анализ? Зачем он нужен, если можно смотреть общий retention?
  • Напишите SQL-запрос: постройте когортную таблицу retention по месяцу регистрации.
  • Как интерпретировать когортную таблицу? Что означает, если столбец M1 падает от когорты к когорте?
  • DAU вырос на 10%, но retention упал. Как когортный анализ поможет разобраться?
  • Как бы вы оценили эффект нового онбординга с помощью когортного анализа?
  • Можно ли использовать когортный анализ для прогнозирования LTV? Как?
  • Вы видите, что одна когорта сильно выбивается из общего тренда. Ваши действия?
  • Retention в когортах растёт, но общий retention падает. Как это возможно?

Связанные материалы

Главное про когортный анализ

Когортный анализ — это рентген продукта. Общие средние скрывают проблемы и успехи. Когорты показывают: продукт становится лучше или хуже? Какие каналы приводят качественных пользователей? Работает ли новая фича?

На собеседовании от вас ждут: умение написать SQL-запрос для когортной таблицы, знание как минимум трёх направлений чтения heatmap (по строке, по столбцу, по диагонали), и способность объяснить паттерн — не просто «retention падает», а «вот почему и вот что с этим делать».

Следующий шаг: возьмите датасет Olist (Kaggle), постройте когортную таблицу по дате первого заказа и визуализируйте heatmap. Попробуйте прочитать три направления — и записать по одному инсайту для каждого.

АТ
Андрей Тарасенко
// Продуктовый аналитик · Авито · Ментор

Когортный анализ — мой основной рабочий инструмент в Авито. Каждый месяц строю когорты для оценки новых фич и каналов. Статья написана из практики, а не из учебника.

Написать в Telegram