Survival analysis в продуктовой аналитике

Проверь себя · 1/3разбор после ответа
Вы увеличиваете частоту push-уведомлений, ожидая рост заказов. Какая метрика наиболее уместна как guardrail metric?

Что такое survival analysis

Survival analysis (анализ выживаемости) — раздел статистики, изучающий данные «время до события». Исходно — медицина (время до смерти пациента), отсюда название.

В продукте применяется для:

  • Time to churn — когда пользователь уйдёт.
  • Time to conversion — за сколько free-пользователь станет платным.
  • Time to purchase — время до первой покупки.
  • Time to reactivation — когда вернётся ушедший пользователь.
  • Subscription lifetime — срок жизни подписки.

Особенность: данные цензурированы — часть наблюдений не «завершены» к моменту анализа. Survival analysis корректно с этим работает.

Censoring — ключевая концепция

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

Right censoring (90% случаев): знаем, что событие ещё не случилось, но не знаем когда случится.

Пример: пользователь зарегистрировался 3 месяца назад и всё ещё активен. Мы не знаем, когда он уйдёт. Но знаем — не в течение первых 3 месяцев.

Left censoring: событие уже произошло до начала наблюдения.

Interval censoring: событие в известном интервале, но точное время неизвестно.

Наивный подход (только полные наблюдения) → систематическое смещение. Survival analysis корректно использует цензурированные данные.

Kaplan-Meier estimator

Непараметрическая оценка survival function S(t) = P(event after time t).

Алгоритм:

  1. Сортируем наблюдаемые времена событий.
  2. В каждый момент t считаем: S(t) = S(t-1) × (1 - events_at_t / at_risk_at_t).
  3. Цензурированные наблюдения выходят из at_risk, но не считаются событиями.
from lifelines import KaplanMeierFitter

kmf = KaplanMeierFitter()
kmf.fit(df['duration'], df['event_observed'])

kmf.plot_survival_function()
print(kmf.survival_function_)

Получаем ступенчатую функцию — вероятность выживания по времени.

Медианное время выживания. Время, к которому 50% наблюдений «умерли» (событие произошло).

Сравнение групп

Для сравнения выживаемости в двух группах — log-rank тест.

H0: функции выживания одинаковы.

from lifelines.statistics import logrank_test

group_a = df[df['group']=='A']
group_b = df[df['group']=='B']

results = logrank_test(
    group_a['duration'], group_b['duration'],
    event_observed_A=group_a['event'], event_observed_B=group_b['event']
)
print(results.p_value)

Если p < 0.05 — кривые выживания значимо отличаются.

Пример применения: две версии онбординга. Группа A — старая, группа B — новая. Сравниваем время до первой покупки. Новый онбординг эффективнее, если кривая B «падает быстрее» (события случаются раньше).

Cox proportional hazards

Cox-регрессия — полу-параметрический метод. Моделирует hazard rate (мгновенную скорость события) как функцию признаков.

h(t | X) = h0(t) × exp(β₁·X₁ + β₂·X₂ + ...)

Где:

  • h0(t) — базовый hazard (зависит от времени, не параметризуется).
  • β_i — коэффициенты, показывают эффект признаков.

Преимущество: не нужно предполагать форму распределения, но можно оценить эффект признаков.

from lifelines import CoxPHFitter

cph = CoxPHFitter()
cph.fit(df, duration_col='duration', event_col='event', formula='age + country + plan')
cph.print_summary()

На выходе: hazard ratios (HR = exp(β)).

HR > 1: признак увеличивает риск события (быстрее churn).

HR < 1: признак снижает риск (дольше retention).

HR = 1.5 для plan=premium: у премиум-пользователей hazard события на 50% выше (например, конверсия раньше).

Пример: time to churn

Данные: users с signup_date, churn_date (если churn), is_active.

import pandas as pd
from lifelines import KaplanMeierFitter, CoxPHFitter

df['duration'] = (df['churn_date'].fillna(pd.Timestamp.now()) - df['signup_date']).dt.days
df['event'] = df['churn_date'].notna().astype(int)

# Kaplan-Meier по сегментам
kmf = KaplanMeierFitter()
for source in df['acquisition_source'].unique():
    mask = df['acquisition_source'] == source
    kmf.fit(df[mask]['duration'], df[mask]['event'], label=source)
    kmf.plot_survival_function()

# Cox для multi-variate
cph = CoxPHFitter()
cph.fit(df, duration_col='duration', event_col='event',
        formula='age + country + acquisition_source + initial_plan')
cph.print_summary()

Результат: видим, какие признаки предсказывают ранний churn.

Hazard rate vs Survival function

Связанные функции:

Функция выживания S(t). P(событие случится после времени t). Убывающая, стартует с 1, заканчивается в 0.

Hazard rate h(t). Мгновенная скорость события при условии дожития до t. h(t) × dt ≈ вероятность события в интервале dt при условии, что дожили до t.

Накопленный hazard H(t). Интеграл hazard rate. H(t) = -log(S(t)).

Разные взгляды на одно и то же.

AFT vs PH

Два подхода в моделировании выживания:

Proportional Hazards (Cox). Признаки умножают hazard. Предикторы сдвигают функцию hazard по оси y.

Accelerated Failure Time (AFT). Признаки сдвигают ось времени. Предикторы ускоряют или замедляют «часы».

Обе модели дают похожие выводы, но отличаются интерпретацией. Cox популярнее, но AFT проще объяснять бизнесу («этот признак удваивает ожидаемое время жизни»).

Готовься к собесу аналитика как в Duolingo
10 минут в день — SQL, Python, A/B, метрики. 1700+ вопросов в Telegram
Открыть Карьерник в Telegram

Проверка assumptions

Cox предполагает proportional hazards — relative risk constant over time.

Проверка:

cph.check_assumptions(df, p_value_threshold=0.05)

Нарушение: HR для признака меняется со временем. Например, эффект онбординга важен в первые 30 дней, потом «забывается». Решения:

  • Time-varying covariates. Взаимодействие признак × время.
  • Stratified Cox. Разбить по группе, оценить отдельно.
  • Параметрические модели. Weibull, log-normal — другие формы.

Customer Lifetime и Survival

LTV calculation часто использует survival analysis:

LTV = Σ ARPU × P(active at time t) × discount_factor

P(active at time t) — функция выживания. Кривая выживания даёт принципиально правильный LTV, в отличие от простого «средний срок × ARPU».

Зацепки:

  • Ожидаемая месячная выручка на когорту.
  • Когортный LTV.
  • Прогноз выручки от текущей базы.

Продвинутые методы анализа — мощный инструмент для сеньор-аналитика. В тренажёре Карьерник есть задачи по анализу churn, LTV и продуктовым метрикам.

Survival curves для retention

Классическая кривая retention — это просто Kaplan-Meier.

«Day N retention» = S(N) в терминах выживания.

Преимущество survival-подхода:

  • Корректная обработка цензурированных наблюдений (пользователи, которые только что пришли).
  • Единый фреймворк для разных точек времени.
  • Расширяется на признаки (Cox).

Многие инструменты аналитики (Amplitude, Mixpanel) под капотом используют survival analysis для кривых retention.

Biggest challenges

Определение события. Что считать churn? 30 дней без активности? Отмена? Последняя оплата? Должно быть согласованно.

Точка отсчёта. Когда начинается наблюдение? Регистрация? Первая покупка? Первое значимое действие? Зависит от бизнес-вопроса.

Процесс генерации данных. Пользователь может быть неактивен, но всё ещё «жив» (вернётся). Или активирован, но не делает формального churn. Survival analysis предполагает чёткое событие.

Повторяющиеся события. Классический survival — для одного события. Для повторных (повторные покупки) — другие методы (multi-state модели, анализ повторяющихся событий).

Python libraries

lifelines. Самая полная библиотека для survival analysis в Python. Kaplan-Meier, Cox, AFT, параметрические модели.

scikit-survival. Удобен для ML-пайплайнов. Survival Random Forest, Survival SVM.

pysurvival. Deep learning для выживания (DeepSurv).

statsmodels. Базовые методы.

Для большинства задач lifelines хватает.

Real example: subscription retention

SaaS company с monthly subscriptions. Вопросы:

  1. Как выглядит retention curve?
  2. Какие features предсказывают churn?
  3. Какие pricing plans удерживают дольше?
from lifelines import KaplanMeierFitter, CoxPHFitter

# Подготовка данных
df['duration'] = (df['cancelled_at'].fillna(pd.Timestamp('today')) - 
                  df['started_at']).dt.days
df['event'] = df['cancelled_at'].notna().astype(int)

# 1. Overall retention
kmf = KaplanMeierFitter()
kmf.fit(df['duration'], df['event'])
print(f'Median lifetime: {kmf.median_survival_time_} days')

# 2. Cox model
cph = CoxPHFitter()
cph.fit(df, 'duration', 'event',
        formula='plan + acquisition_channel + annual_billing')
cph.print_summary()

# 3. Specific question: annual vs monthly billing
kmf_a = KaplanMeierFitter()
kmf_m = KaplanMeierFitter()

kmf_a.fit(df[df['annual']==1]['duration'], df[df['annual']==1]['event'], label='Annual')
kmf_m.fit(df[df['annual']==0]['duration'], df[df['annual']==0]['event'], label='Monthly')

import matplotlib.pyplot as plt
kmf_a.plot_survival_function()
kmf_m.plot_survival_function()
plt.title('Subscription retention by billing plan')

Обычный результат: annual billing показывает радикально лучшую retention (natural commitment effect).

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

Игнор цензурирования. Только завершённые случаи → систематическая переоценка churn (исключаете долгоживущих).

Средняя продолжительность. Средний срок отражает текущие наблюдения, а не ожидаемое время жизни. Может сильно недооценивать.

Бинарный churn как простая логрегрессия. Если предсказываем «уйдёт ли пользователь за 30 дней» — простая логрегрессия норм. Но если «через сколько уйдёт» — нужен survival.

Нарушение предпосылок. Proportional hazards — не всегда выполняется. Проверяйте.

Маленькие выборки. Cox требует разумного размера выборки. Редкие события → нестабильные оценки.

Читайте также

FAQ

Survival analysis или логистическая регрессия?

Логистическая — для бинарного «churn или нет» в фиксированном окне. Survival — для «когда произойдёт событие» с цензурированием.

Можно ли использовать для событий, не связанных с churn?

Да. Время до конверсии, время до второй покупки, время до любого события — всё это задачи для survival.

Cox или параметрическая модель?

Cox — непараметрический baseline, меньше предпосылок. Параметрические (Weibull, log-normal) — лучше, если можете обосновать распределение, точнее для прогноза.

Повторяющиеся события — как?

Стандартный survival — одно событие. Для множества — используют conditional-модели, Andersen-Gill, Prentice-Williams-Peterson.