К содержанию
ActionPulse

← Все статьи

обновлено
  • аналитика
  • удержание
  • saas
Автор — Редакция ActionPulse

Когортный анализ: как считать удержание пользователей

ActionPulse — когортная таблица удержания W0–W4 на синтетических событиях

Общая активность отвечает на вопрос «сколько людей пользовались продуктом за период», но не показывает, возвращаются ли одни и те же люди. Новые регистрации могут замещать ушедших пользователей, поэтому общий график выглядит стабильным, хотя каждая новая группа быстро перестаёт пользоваться продуктом. Когортный анализ отделяет приток новых пользователей от удержания уже пришедших.

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

Скачать CSV с синтетическими событиями для когортного анализа. Ниже приведены точный запрос ClickHouse и ожидаемая матрица: расчёт можно повторить, проверить на дубликатах и отличить незавершённую неделю от настоящего нуля.

Что такое когорта

Когорта — группа пользователей, объединённых одним условием и общей точкой отсчёта. В продуктовом анализе чаще всего это люди, которые впервые выполнили выбранное стартовое событие в один день, неделю или месяц.

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

Когортный анализ сравнивает поведение таких групп в одинаковом возрасте. Календарные даты заменяются относительными периодами: неделей старта W0, следующей неделей W1 и далее. Так раннюю и позднюю когорты можно сопоставлять по первой, второй или четвёртой неделе жизни.

Чем когортный анализ отличается от общей активности

Общая метрика DAU, WAU или MAU складывает всех активных пользователей в календарном периоде. Она нужна для оценки масштаба аудитории, но не разделяет новичков и тех, кто давно пользуется продуктом.

Когортный отчёт сохраняет состав исходной группы и проверяет, какая её доля вернулась в каждый следующий период. Поэтому два продукта с одинаковым WAU могут иметь разную картину: один удерживает старые группы, а второй поддерживает активность только за счёт постоянного притока новых пользователей.

У этих отчётов разные знаменатели:

  • общая активность считает уникальных пользователей в конкретном календарном периоде;
  • retention делит вернувшихся пользователей на размер их исходной когорты;
  • размер когорты после её формирования не увеличивается из-за новых регистраций.

Как считать retention

Для периодического retention формула выглядит так:

Retention Wn = вернувшиеся пользователи из когорты в Wn / все пользователи когорты в W0 × 100%

В числителе должны быть уникальные пользователи из той же когорты, выполнившие выбранное возвратное событие в периоде Wn. В знаменателе — все уникальные пользователи, выполнившие стартовое событие в W0. Если один человек совершил возвратное событие несколько раз за неделю, для retention он всё равно учитывается один раз.

W0 обычно равен 100%: это период формирования исходной когорты. Значение W1 показывает возврат в первую неделю после старта, W2 — во вторую и так далее. Это не то же самое, что rolling retention, где пользователь считается удержанным, если вернулся в выбранный период или позднее. Перед сравнением отчётов проверьте, какой именно вариант формулы используется.

Пример таблицы W0–W4

Ниже приведён полностью синтетический воспроизводимый пример. В CSV-файле событий находятся 40 полностью синтетических событий, анонимные учебные идентификаторы, два проекта и три целевые когорты. Это не данные клиентов ActionPulse, не отраслевой бенчмарк и не обещание результата.

Файл использует четыре поля реального событийного контура: project_id, event_name, actor_id, event_time. Время событий записано в UTC; недели рассчитываются по часовому поясу проекта Europe/Moscow и начинаются в понедельник. Старт — первое signup_completed пользователя внутри выбранного диапазона, возврат — report_created.

Границы расчёта фиксированы: набор когорт с 20 июля до 17 августа 2026 года, правая граница исключается; момент наблюдения — 24 августа 2026 года, 00:00 по Москве. Пользовательская SQL-консоль читает ограниченное текущим проектом каноническое представление events: напрямую обращаться к сырой таблице другого проекта не нужно и нельзя.

Когорта N W0 W1 W2 W3 W4
20 июля6100%66,67%50%33,33%16,67%
27 июля4100%75%50%0%
10 августа3100%66,67%

N — размер исходной когорты. Каждая строка — пользователи, выполнившие стартовое событие в указанную неделю. Каждый столбец — номер недели относительно этого события, а не календарная неделя для всех строк. Прочерк означает «период ещё не завершился», а 0% — завершённый период, в котором никто не вернулся: это два принципиально разных результата.

В когорте 20 июля N = 6. Четыре уникальных человека создали отчёт на следующей неделе, поэтому W1 = 4 / 6 = 66,67%; на четвёртой неделе вернулся один, поэтому W4 = 1 / 6 = 16,67%. Повторное report_created того же человека не превращает четыре возврата в пять. У когорты 27 июля W3 уже завершилась без возвратов и честно равна 0%, тогда как W4 к моменту наблюдения ещё не закончилась и должна оставаться NULL в SQL или прочерком в таблице.

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

Воспроизводимый ClickHouse SQL для таблицы W0–W4

Запрос рассчитан на пользовательское каноническое представление events с полями project_id, event_name, actor_id, event_time. Представление уже ограничено проектом текущей SQL-сессии; подставлять чужой проект, обращаться к сырой таблице или обходить права доступа не требуется.

WITH
    parameters AS (
        SELECT
            toDateTime('2026-07-20 00:00:00', 'Europe/Moscow') AS range_start,
            toDateTime('2026-08-17 00:00:00', 'Europe/Moscow') AS range_end,
            toDateTime('2026-08-24 00:00:00', 'Europe/Moscow') AS as_of
    ),
    starts AS (
        SELECT
            actor_id,
            min(
                toDateTime(
                    toStartOfWeek(event_time, 1, 'Europe/Moscow'),
                    'Europe/Moscow'
                )
            ) AS cohort_start
        FROM events
        CROSS JOIN parameters AS p
        WHERE event_name = 'signup_completed'
            AND event_time >= p.range_start
            AND event_time < p.range_end
        GROUP BY actor_id
    ),
    returns AS (
        SELECT
            actor_id,
            toDateTime(
                toStartOfWeek(event_time, 1, 'Europe/Moscow'),
                'Europe/Moscow'
            ) AS return_week
        FROM events
        CROSS JOIN parameters AS p
        WHERE event_name = 'report_created'
            AND event_time >= p.range_start
            AND event_time < p.as_of
        GROUP BY actor_id, return_week
    ),
    cohort_totals AS (
        SELECT
            s.cohort_start,
            uniqExact(s.actor_id) AS cohort_size,
            uniqExactIf(r.actor_id, r.return_week = addWeeks(s.cohort_start, 1)) AS returned_w1,
            uniqExactIf(r.actor_id, r.return_week = addWeeks(s.cohort_start, 2)) AS returned_w2,
            uniqExactIf(r.actor_id, r.return_week = addWeeks(s.cohort_start, 3)) AS returned_w3,
            uniqExactIf(r.actor_id, r.return_week = addWeeks(s.cohort_start, 4)) AS returned_w4
        FROM starts AS s
        LEFT JOIN returns AS r ON s.actor_id = r.actor_id
        GROUP BY s.cohort_start
    )
SELECT
    toDate(cohort_start, 'Europe/Moscow') AS cohort_week,
    cohort_size AS users,
    100.0 AS W0,
    if(addWeeks(cohort_start, 2) <= p.as_of, round(100.0 * returned_w1 / cohort_size, 2), NULL) AS W1,
    if(addWeeks(cohort_start, 3) <= p.as_of, round(100.0 * returned_w2 / cohort_size, 2), NULL) AS W2,
    if(addWeeks(cohort_start, 4) <= p.as_of, round(100.0 * returned_w3 / cohort_size, 2), NULL) AS W3,
    if(addWeeks(cohort_start, 5) <= p.as_of, round(100.0 * returned_w4 / cohort_size, 2), NULL) AS W4
FROM cohort_totals
CROSS JOIN parameters AS p
ORDER BY cohort_week;

Правила округления начала недели заданы в официальной документации ClickHouse по toStartOfWeek; точный подсчёт уникальных участников описан в документации ClickHouse по uniqExact. Суффикс If применяет расчёт только к возвратам выбранной недели.

В учебных событиях специально оставлены проверки, которые легко пропустить:

  1. Старт a01 в 2026-07-19T21:10:00Z — уже понедельник 20 июля по Москве, поэтому пользователь не попадает в предыдущую неделю.
  2. Повторная регистрация a01 не создаёт вторую когорту: выбирается первое стартовое событие внутри диапазона.
  3. Два report_created одного человека за W1 учитываются как один уникальный возврат.
  4. События другого project_id, регистрация до начала периода и регистрация после правой границы не входят в целевые когорты.
  5. Событие после as_of не превращает незавершённую W4 в измеренный результат.
  6. Завершённая неделя без возвратов даёт 0%; незавершённые недели остаются NULL и не попадают в среднее удержание.

Это поведенческий когортный расчёт по пользователям, а не клиентский churn, денежное удержание или доказательство причины оттока. Различие показателей разобрано отдельно в материале про retention и churn SaaS, а область применения пользовательских запросов — в статье про SQL в продуктовой аналитике.

Пример для SaaS-продукта

Представим демонстрационный, а не клиентский кейс сервиса отчётности. Команда хочет понять, возвращаются ли новые пользователи за основной ценностью продукта.

  • стартовое событие — signup_completed;
  • возвратное событие — report_created;
  • период когорты — неделя регистрации;
  • сегмент — новые пользователи без сотрудников и тестовых аккаунтов.

В этом демонстрационном сценарии W4 = 16,67% для когорты из шести пользователей означает только одно: один пользователь из исходной группы создал отчёт на четвёртой неделе после регистрации. Это не доля всех активных пользователей, не конверсия в оплату и не доказательство причины оттока.

Событие report_created подходит лишь в том случае, если именно создание отчёта выражает ценность этого сервиса. Для другого SaaS ключевым действием может быть отправка кампании, проведение сделки или завершение расчёта. Универсального события активации нет: команда должна определить его для своего продукта. Подробнее о сценариях регистрации, активации, оплаты и удержания — на странице продуктовой аналитики для SaaS и в шаблоне 14 событий для B2B SaaS.

Как выбрать стартовое и возвратное событие

Начните не с названия отчёта, а с продуктового вопроса. Если вопрос звучит как «возвращаются ли зарегистрировавшиеся пользователи за ценностью», стартом может быть signup_completed. Если команда исследует удержание после активации, началом должно стать согласованное событие активации. Для удержания платящих клиентов уместнее когорта по payment_succeeded, чем по просмотру формы регистрации.

Хорошее стартовое событие:

  • происходит в понятный момент жизненного цикла;
  • одинаково трактуется на всём выбранном интервале;
  • фиксируется один раз или позволяет однозначно выбрать первую подходящую точку;
  • связано с вопросом, на который команда собирается ответить.

Возвратное событие должно показывать полезное действие, а не просто техническую активность. page_view или любое событие подходит для вопроса «вернулся ли человек вообще», но может завысить ощущение ценности из-за фоновых запросов, служебных событий и случайных открытий. Для вопроса «вернулся ли за результатом» лучше выбрать предметное действие, согласованное продуктовой командой.

Перед расчётом также проверьте стабильность actor_id. Один человек не должен превращаться в нескольких пользователей после входа с другого устройства, а общие корпоративные аккаунты не должны склеивать разных людей без осознанного решения команды.

Типичные ошибки

Смешивать разные точки старта. Регистрация, активация и первая оплата создают разные когорты. Их нельзя подменять друг другом только потому, что все события происходят в начале пути.

Считать возвратом любое фоновое событие. Автоматическое обновление токена или серверная синхронизация ещё не означают, что пользователь вернулся и получил ценность.

Менять семантику события посередине периода. Если после релиза report_created начинает фиксироваться на другом шаге, соседние когорты становятся несопоставимыми. Изменение нужно задокументировать и учитывать при анализе.

Сравнивать незавершённые периоды. У свежей когорты ещё не наступила W4. Значение в незавершённом периоде нельзя трактовать как отсутствие возврата.

Игнорировать размер когорты. Один и тот же процент при большом и малом N имеет разную устойчивость. При объединении строк нельзя просто усреднять проценты без веса исходных когорт.

Смешивать пользователей с разным контекстом. Тариф, канал привлечения, роль и тип компании могут заметно менять поведение. Сначала проверьте общую картину, затем сравните заранее осмысленные сегменты.

Принимать корреляцию за причину. Рост retention после релиза не доказывает, что его вызвала новая функция. Одновременно могли измениться трафик, сезонность, цены или разметка событий.

Как интерпретировать результаты

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

  • резкое снижение между W0 и W1 показывает, где удержание меняется, но не объясняет почему;
  • плато на поздних неделях может указывать на устойчивую группу пользователей, однако её состав нужно исследовать отдельно;
  • рост одного столбца от старых когорт к новым может быть связан с изменением продукта или качества привлечения;
  • различие сегментов полезно, если условия формирования когорт и события у них одинаковы.

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

Как построить когортный отчет в ActionPulse

В ActionPulse отчёт называется «Удержание». Для расчёта нужно выбрать стартовое событие, возвратное событие, диапазон дат и группировку по дням или неделям. Если выбрать возвратом любое событие, отчёт ответит на вопрос о любом зафиксированном возвращении; для оценки повторного получения ценности лучше указать конкретное действие.

Для каждого actor_id ActionPulse берёт первое подходящее стартовое событие внутри выбранного диапазона, а не первое действие пользователя за всё время. Пользователи объединяются в когорты, W0 отображает исходную группу, а повторные события одного пользователя в одном периоде учитываются один раз. Поэтому перед расчётом важно осознанно выбрать даты и проверить, что событие не меняло смысл.

Практический порядок такой:

  1. Сформулируйте вопрос и выберите стартовое событие.
  2. Укажите действие, которое будет считаться возвратом.
  3. Выберите диапазон дат, период и нужные фильтры.
  4. Рассчитайте отчёт и сначала проверьте размеры когорт.
  5. Сравните строки в одинаковом возрасте и не делайте выводов по ещё не завершившимся периодам.
  6. Сформулируйте гипотезу и проверьте её через воронки, пути, сегменты и доступную по тарифу историю действий пользователей.

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

Хотите проверить удержание на своих событиях? Начните пробный период ActionPulse и соберите первый отчёт на собственных данных. Обзор продукта и других сценариев аналитики есть на главной странице ActionPulse.

Проверьте это на своих данных

Пробный период Growth на 14 дней: отчёты и SQL-консоль, карта не нужна. До 30 млн событий за 14 дней. Выгрузка событий через API недоступна во время пробного периода; после оплаты доступна с тарифа Growth.

Попробовать 14 дней бесплатно