Survival Analysis for Churn
Apply survival analysis to predict customer churn, subscription retention, and time-to-event outcomes. Covers Kaplan-Meier estimators, Cox proportional hazards, censored data handling, and the patterns that turn retention data into actionable insights.
Survival analysis answers: “How long until an event happens?” In SaaS, the event is churn. Traditional classification models predict IF a customer will churn. Survival analysis predicts WHEN — and handles the fact that many customers haven’t churned yet (censored data), which standard models cannot.
Why Survival Analysis for Churn
Classification approach:
Input: Customer features at time T
Output: Will churn? Yes/No (binary)
Problem 1: When? "Yes, they'll churn" — but when? Tomorrow? In 6 months?
Problem 2: Censored data
Customer A: Subscribed 12 months, churned → Label: CHURNED
Customer B: Subscribed 6 months, still active → Label: ???
Classification says: Customer B is "not churned" (wrong — they just haven't YET)
Survival analysis says: Customer B is "censored at 6 months" (correct)
Survival analysis approach:
Input: Customer features + time subscribed + churned/censored
Output: Survival curve (probability of remaining active over time)
Result: "Customer X has a 73% probability of still being active at 12 months"
Kaplan-Meier Estimator
from lifelines import KaplanMeierFitter
import pandas as pd
# Prepare data
data = pd.DataFrame({
"customer_id": ["C001", "C002", "C003", "C004", "C005"],
"duration_months": [12, 6, 18, 3, 24],
"churned": [1, 0, 1, 1, 0], # 0 = censored (still active)
"plan": ["pro", "free", "pro", "free", "enterprise"],
})
# Fit Kaplan-Meier survival curve
kmf = KaplanMeierFitter()
# Overall survival curve
kmf.fit(durations=data["duration_months"], event_observed=data["churned"])
print(f"Median survival time: {kmf.median_survival_time_} months")
# Survival curve by plan
for plan in data["plan"].unique():
mask = data["plan"] == plan
kmf.fit(
durations=data.loc[mask, "duration_months"],
event_observed=data.loc[mask, "churned"],
label=plan,
)
kmf.plot_survival_function()
# Read the curve:
# At month 6: 85% of pro users still active, 40% of free users
# At month 12: 70% of pro users still active, 15% of free users
# → Free plan has dramatically worse retention
Cox Proportional Hazards
from lifelines import CoxPHFitter
# Prepare features
features = pd.DataFrame({
"duration_months": [12, 6, 18, 3, 24, 9, 15, 4, 20, 7],
"churned": [1, 0, 1, 1, 0, 1, 0, 1, 0, 1],
"plan_pro": [1, 0, 1, 0, 0, 1, 0, 0, 1, 0],
"plan_enterprise": [0, 0, 0, 0, 1, 0, 0, 0, 0, 0],
"support_tickets": [2, 0, 1, 5, 0, 3, 1, 8, 0, 4],
"login_frequency": [20, 3, 15, 1, 25, 8, 12, 2, 22, 5],
"onboarding_completed": [1, 0, 1, 0, 1, 1, 0, 0, 1, 0],
})
# Fit Cox model
cph = CoxPHFitter()
cph.fit(features, duration_col="duration_months", event_col="churned")
# Interpret hazard ratios
cph.print_summary()
# Example output:
# coef exp(coef) p-value
# plan_pro -0.85 0.43 0.002 → 57% lower churn risk
# plan_enterprise -1.50 0.22 0.01 → 78% lower churn risk
# support_tickets 0.30 1.35 0.001 → Each ticket = 35% higher churn risk
# login_frequency -0.15 0.86 0.005 → Each login = 14% lower churn risk
# onboarding_done -0.70 0.50 0.003 → 50% lower churn risk if onboarded
Actionable Insights
# Predict individual customer survival
def predict_customer_churn_risk(customer_features):
"""Generate survival curve for a specific customer."""
survival_function = cph.predict_survival_function(customer_features)
return {
"prob_active_3mo": float(survival_function.loc[3]),
"prob_active_6mo": float(survival_function.loc[6]),
"prob_active_12mo": float(survival_function.loc[12]),
"median_lifetime": float(cph.predict_median(customer_features)),
"risk_factors": identify_risk_factors(customer_features),
}
# Interventions based on survival curves:
# Customer with low login_frequency + no onboarding:
# → Trigger onboarding email sequence
# → Schedule customer success call
# → Offer guided product tour
#
# Customer with high support_tickets:
# → Escalate to senior support
# → Proactive outreach from CSM
# → Product feedback loop
Anti-Patterns
| Anti-Pattern | Consequence | Fix |
|---|---|---|
| Ignoring censored customers | Biased model, overpredicts churn | Survival analysis handles censoring natively |
| Binary churn prediction | Cannot prioritize interventions | Survival curves give time-to-churn |
| No segmentation | One-size-fits-all retention strategy | Kaplan-Meier curves per segment |
| Features from after churn | Data leakage | Only use features available at prediction time |
| No intervention validation | Cannot prove retention efforts work | A/B test interventions against control |
Survival analysis transforms churn from a yes/no question into a when question. That difference determines whether you intervene before or after a customer has already decided to leave.