Survival analysis в продуктовой аналитике
Что такое survival analysis
Survival analysis (анализ выживаемости) — раздел статистики о анализе time-to-event данных. Исходно — медицина (время до смерти пациента), отсюда название.
В продукте применяется для:
- Time to churn — когда пользователь уйдёт.
- Time to conversion — за сколько free user станет paid.
- Time to purchase — время до первой покупки.
- Time to reactivation — когда вернётся churned пользователь.
- Subscription lifetime — durаtion подписки.
Особенность: данные censored — часть observations не «завершены» к моменту анализа. Survival analysis правильно handles это.
Censoring — ключевая концепция
Censoring — ситуация, когда для наблюдения неизвестен точный time-to-event, но известно, что event не произошёл к определённому моменту.
Right censoring (90% случаев): знаем, что event ещё не случился, но не знаем когда случится.
Пример: пользователь зарегистрировался 3 месяца назад и всё ещё активен. Мы не знаем, когда он churn. Но знаем — не в течение первых 3 месяцев.
Left censoring: event уже произошёл до начала наблюдения.
Interval censoring: event в известном interval, но точное время неизвестно.
Наивный подход (только complete observations) → systematic bias. Survival analysis использует censored data корректно.
Kaplan-Meier estimator
Непараметрическая оценка survival function S(t) = P(event after time t).
Алгоритм:
- Сортируем observed event times.
- В каждый момент t вычисляем: S(t) = S(t-1) × (1 - events_at_t / at_risk_at_t).
- Censored наблюдения выходят из at_risk но не считаются events.
from lifelines import KaplanMeierFitter
kmf = KaplanMeierFitter()
kmf.fit(df['duration'], df['event_observed'])
kmf.plot_survival_function()
print(kmf.survival_function_)Получаем step function — вероятность survival по времени.
Median survival time. Время, к которому 50% observations «умерли» (событие произошло).
Сравнение групп
Для сравнения survival в двух группах — log-rank test.
H0: survival functions одинаковы.
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 — survival curves значимо отличаются.
Пример применения: две версии онбординга. Group A — old, Group B — new. Сравниваем time to first purchase. Новый онбординг эффективнее, если curve B «падает быстрее» (события случаются раньше).
Cox proportional hazards
Cox regression — полу-параметрический метод. Моделирует hazard rate (instantaneous event rate) как функцию covariates.
h(t | X) = h0(t) × exp(β₁·X₁ + β₂·X₂ + ...)Где:
h0(t)— baseline hazard (как в зависимости от времени, не параметризуется).β_i— коэффициенты, показывают effect features.
Преимущество: не нужно предполагать форму distribution, но позволяет оценить effect covariates.
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: feature увеличивает risk события (быстрее churn).
HR < 1: feature снижает risk (дольше retention).
HR = 1.5 для plan=premium: premium members have 50% higher hazard of event (например, converting earlier).
Пример: 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()Результат: видим, какие features предсказывают early churn.
Hazard rate vs Survival function
Связанные функции:
Survival function S(t). P(event occurs after time t). Убывающая, starts at 1, ends at 0.
Hazard rate h(t). Instantaneous rate of event при conditional on survival до t. h(t) × dt ≈ probability события в dt given survived to t.
Cumulative hazard H(t). Интеграл hazard rate. H(t) = -log(S(t)).
Разные views на одно и то же.
AFT vs PH
Два подхода в survival modeling:
Proportional Hazards (Cox). Features умножают hazard. Predictors shift hazard function по оси y.
Accelerated Failure Time (AFT). Features shift time axis. Predictors ускоряют или замедляют «clock».
Обе модели дают похожие insights но отличаются интерпретацией. Cox более popular, но AFT проще объяснять бизнесу («эта feature удваивает expected lifetime»).
Проверка assumptions
Cox предполагает proportional hazards — relative risk constant over time.
Проверка:
cph.check_assumptions(df, p_value_threshold=0.05)Нарушение: HR для feature меняется со временем. Например, эффект onboarding важен в первые 30 дней, потом «забывается». Решения:
- Time-varying covariates. Feature × time interaction.
- Stratified Cox. Разбить по группе, оценить отдельно.
- Parametric models. Weibull, log-normal — иные shapes.
Customer Lifetime и Survival
LTV calculation часто использует survival analysis:
LTV = Σ ARPU × P(active at time t) × discount_factorP(active at time t) — survival function. Survival curve даёт принципиально правильный LTV vs простой «AVG tenure × ARPU».
Hooks:
- Expected monthly revenue per cohort.
- Cohort LTV.
- Forecasted revenue from existing book of business.
Продвинутые методы анализа — мощный инструмент для senior аналитика. В тренажёре Карьерник есть задачи по churn analysis, LTV и продуктовым метрикам.
Survival curves для retention
Классический retention curve — просто Kaplan-Meier.
«Day N retention» = S(N) в survival terms.
Преимущество survival подхода:
- Правильно handle censored observations (users who joined recently).
- Unified framework для разных time points.
- Extensible to covariates (Cox).
Многие аналитические инструменты (Amplitude, Mixpanel) под капотом используют survival analysis для retention curves.
Biggest challenges
Definition of event. Что считать «churn»? 30 дней no activity? Cancellation? Last payment? Должно быть consistent.
Time zero. Когда начинается observation? Signup? First purchase? First meaningful action? Зависит от бизнес-вопроса.
Data generation process. User может быть inactive but still «alive» (будет возвращаться). Или activated но никогда не churn formally. Survival analysis assumes clear event.
Recurring events. Survival classically для single event. Для repeated (повторные purchases) — другие methods (multi-state models, recurrent event analysis).
Python libraries
lifelines. Наиболее полная library для survival analysis в Python. Kaplan-Meier, Cox, AFT, parametric models.
scikit-survival. ML-friendly. Survival Random Forest, Survival SVM.
pysurvival. Deep learning survival models (DeepSurv).
statsmodels. Базовые methods.
Для большинства задач lifelines — достаточно.
Real example: subscription retention
SaaS company с monthly subscriptions. Вопросы:
- Как выглядит retention curve?
- Какие features предсказывают churn?
- Какие 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).
Типичные ошибки
Ignoring censoring. Только complete cases → systematic overestimation of churn (исключает long survivors).
Using mean duration. Average tenure отражает current observed data, не expected lifetime. Может сильно underestimate.
Binary churn as just logistic regression. Если predicting «will user churn in next 30 days», simple logistic — fine. Но если «how long until user churns» — нужен survival.
Assumptions violation. Proportional hazards — не holds always. Проверять.
Small samples. Cox requires reasonable sample sizes. Rare events → unstable estimates.
Читайте также
FAQ
Survival analysis или logistic regression?
Logistic — для binary «churn vs not» в fixed time window. Survival — для «when will event happen» с censoring.
Можно ли использовать для non-churn events?
Да. Time to conversion, time to second purchase, time to any event — всё задачи для survival.
Cox или parametric model?
Cox — non-parametric baseline, меньше assumptions. Parametric (Weibull, log-normal) — лучше если можете обосновать distribution, более precise для forecasting.
Recurring events — как?
Standard survival — single event. Для multiple — используют conditional models, Andersen-Gill, Prentice-Williams-Peterson.