ESC
Type to search guides, tutorials, and reference documentation.
Verified by Garnet Grid

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-PatternConsequenceFix
Ignoring censored customersBiased model, overpredicts churnSurvival analysis handles censoring natively
Binary churn predictionCannot prioritize interventionsSurvival curves give time-to-churn
No segmentationOne-size-fits-all retention strategyKaplan-Meier curves per segment
Features from after churnData leakageOnly use features available at prediction time
No intervention validationCannot prove retention efforts workA/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.

Jakub Dimitri Rezayev
Jakub Dimitri Rezayev
Founder & Chief Architect • Garnet Grid Consulting

Jakub holds an M.S. in Customer Intelligence & Analytics and a B.S. in Finance & Computer Science from Pace University. With deep expertise spanning D365 F&O, Azure, Power BI, and AI/ML systems, he architects enterprise solutions that bridge legacy systems and modern technology — and has led multi-million dollar ERP implementations for Fortune 500 supply chains.

View Full Profile →