Survival Analysis

Survival analysis models the time until an event occurs — and critically, handles observations where the event hasn't happened yet (censoring). Standard regression can't handle censoring correctly; survival models do. Applications include customer churn (time to cancellation), credit default (time to delinquency), employee attrition, clinical trials, and equipment failure.

Key Takeaways

  • Censoring is the key challenge: a customer who hasn't churned yet is still informative — their survival time is at least T. Ignoring censored observations biases estimates downward.
  • The survival function $S(t) = P(T > t)$ and hazard function $h(t)$ are the two core quantities; they are mathematically equivalent and interconvertible.
  • Kaplan-Meier is the non-parametric estimator of $S(t)$ — it requires no distributional assumptions and is the standard first step in any survival analysis.
  • The log-rank test compares two or more survival curves non-parametrically; use it to test whether a treatment, segment, or feature affects survival.
  • Cox proportional hazards regresses covariates onto the hazard without specifying the baseline hazard — the proportional hazards assumption (constant hazard ratio over time) must always be checked.
  • For ML-based survival prediction, gradient boosted survival trees (XGBoost with Cox objective, scikit-survival) outperform Cox on large datasets with complex interactions.

1. Censoring

Censoring arises when the event of interest has not been observed for some subjects by the end of the study or data collection window. There are three main types:

Why Naive Approaches Fail

Correct Approach

Maximum likelihood estimation explicitly models censored observations as "survived at least this long," contributing the probability $P(T \geq t_i)$ to the likelihood rather than a point mass at $t_i$.

Example DataFrame with right censoring (event_occurred=0 means censored):

customer_id duration_months event_occurred
1001 12 1
1002 24 0 (censored)
1003 7 1
1004 36 0 (censored)

2. Survival and Hazard Functions

Two mathematically equivalent representations fully describe a time-to-event distribution.

Survival Function

$$S(t) = P(T > t) = 1 - F(t)$$

Starts at $S(0) = 1$, is non-increasing, and approaches 0 as $t \to \infty$. It gives the probability a subject survives beyond time $t$.

Hazard Function

The instantaneous event rate at time $t$, given survival up to $t$:

$$h(t) = \lim_{\Delta t \to 0} \frac{P(t \leq T < t + \Delta t \mid T \geq t)}{\Delta t}=\frac{f(t)}{S(t)}$$

Cumulative Hazard and Key Relationship

$$H(t) = \int_0^t h(u)\, du = -\log S(t) \qquad \Longrightarrow \qquad S(t) = \exp\!\bigl(-H(t)\bigr)$$

Given any one of $S(t)$, $h(t)$, or $H(t)$, the others follow directly.

Common Parametric Distributions

Distribution Hazard Shape Use Case
Exponential Constant (memoryless) Simple baseline; electronics reliability
Weibull Monotone increasing or decreasing Equipment wear; flexible first parametric choice
Log-normal Rises then falls (hump-shaped) Biological processes; time-to-recovery

3. Kaplan-Meier Estimator

The Kaplan-Meier (KM) estimator is the non-parametric maximum likelihood estimator of $S(t)$. It requires no distributional assumptions and is the standard first step in any survival analysis. The estimate is a step function that drops only at observed event times.

At each ordered event time $t_i$:

$$\hat{S}(t) = \prod_{t_i \leq t} \left(1 - \frac{d_i}{n_i}\right)$$

where $d_i$ is the number of events at $t_i$ and $n_i$ is the number at risk just before $t_i$. Censored observations reduce the risk set $n_i$ without contributing to the numerator.

The log-rank test compares two or more KM curves under the null hypothesis that the survival functions are equal. It is non-parametric and does not assume proportional hazards.

from lifelines import KaplanMeierFitter
from lifelines.statistics import logrank_test
import pandas as pd

kmf = KaplanMeierFitter()
kmf.fit(durations=df['duration_months'], event_observed=df['churned'], label='All customers')
kmf.plot_survival_function()
print(f"Median survival: {kmf.median_survival_time_:.1f} months")

# Compare two groups
mask = df['segment'] == 'premium'
kmf_prem = KaplanMeierFitter().fit(df[mask]['duration_months'], df[mask]['churned'], label='Premium')
kmf_std  = KaplanMeierFitter().fit(df[~mask]['duration_months'], df[~mask]['churned'], label='Standard')

results = logrank_test(df[mask]['duration_months'], df[~mask]['duration_months'],
                       event_observed_A=df[mask]['churned'],
                       event_observed_B=df[~mask]['churned'])
print(f"Log-rank p-value: {results.p_value:.4f}")

4. Cox Proportional Hazards Model

The Cox model is the workhorse of survival regression. It is semi-parametric: the baseline hazard $h_0(t)$ is left completely unspecified, while the effect of covariates enters multiplicatively:

$$h(t \mid \mathbf{x}) = h_0(t) \cdot \exp\!\bigl(\boldsymbol{\beta}^T \mathbf{x}\bigr)$$

Key Properties

Testing the PH Assumption

from lifelines import CoxPHFitter

cph = CoxPHFitter()
cph.fit(df[['duration_months', 'churned', 'age', 'monthly_spend', 'segment_premium']],
        duration_col='duration_months', event_col='churned')

cph.print_summary()
# Shows: coef, exp(coef)=HR, 95% CI, p-value for each feature

# Check proportional hazards assumption
cph.check_assumptions(df_clean, p_value_threshold=0.05)

Interpretation example: "A hazard ratio of 1.35 for monthly_spend means customers with one unit higher spend have 35% higher hazard of churning at any given time, assuming proportional hazards."

5. When PH Assumption Fails and ML Approaches

Remedies When PH Fails

ML-Based Survival Models

from sksurv.ensemble import RandomSurvivalForest
import numpy as np

# y must be structured array with (event_occurred, duration)
y = np.array([(bool(e), t) for e, t in zip(df['churned'], df['duration_months'])],
             dtype=[('event', bool), ('time', float)])

rsf = RandomSurvivalForest(n_estimators=200, random_state=42)
rsf.fit(X_train, y_train)

# Concordance index (C-index): primary metric for survival models
# Proportion of comparable pairs where model correctly orders survival times
c_index = rsf.score(X_test, y_test)
print(f"C-index: {c_index:.3f}")  # 0.5 = random, 1.0 = perfect

6. Applications and Business Framing

Domain Event Censoring Mechanism Business Use
Customer churn Cancellation Still active at analysis date Personalize retention offers; identify at-risk cohorts
Credit default First missed payment Loan paid off / still current Survival probability as credit score input
Employee attrition Resignation Still employed Identify flight risk; time targeted interventions
Clinical trials Death / recovery End of study / lost to follow-up Compare treatment arms with log-rank test
Equipment failure Breakdown Equipment still running Predictive maintenance scheduling

C-index (Concordance Index)

The primary evaluation metric for survival models. Given all comparable pairs $(i, j)$ where subject $i$ experienced the event before subject $j$ and both were at risk at that time, the C-index is the fraction of pairs the model ranks correctly (assigns higher risk to $i$).

Practice Questions

Q1: Why can't you just use ordinary linear regression to model time-to-event data?

Show answer

Two fundamental problems. First, censoring: many observations haven't experienced the event yet; you only know their survival time is "at least T." Linear regression treats their observed end-of-observation time as the true outcome, which biases estimates — it underestimates survival time for those who would have survived longer. Second, the outcome is right-skewed and non-negative (time can't be negative). Even after log-transformation, standard regression ignores the information in censored observations. Those observations contribute real information ("survived at least this long") that must be incorporated via survival-specific likelihood formulations. Survival models handle this correctly: Cox regression uses the partial likelihood which conditions on event ordering; parametric models incorporate censored observations via $P(T \geq t_i)$ contributions to the full likelihood.

Q2: Explain the proportional hazards assumption in the Cox model. How do you test it?

Show answer

The proportional hazards assumption states that the hazard ratio between any two subjects is constant over time — the effect of a covariate multiplies the baseline hazard by a fixed factor regardless of when we look. Example: if a treatment halves the hazard at month 1, it also halves it at month 12 and month 24. This assumption fails when a treatment effect is initially strong but wears off, when the effect is age-dependent, or when different subgroups have qualitatively different survival trajectories (crossing survival curves).

Tests: (1) Schoenfeld residuals — for each covariate, regress its Schoenfeld residuals (contribution to the partial likelihood gradient at each event time) on time; a statistically significant slope indicates the hazard ratio is drifting over time; (2) Log-log plot — plot $\log(-\log \hat{S}(t))$ vs. $\log(t)$ for each group; parallel lines indicate proportional hazards; crossing or converging lines indicate violation.

Fixes: (1) stratify on the offending variable — fit separate baseline hazards per stratum while sharing $\boldsymbol{\beta}$; (2) add a time-covariate interaction term $x \cdot g(t)$; (3) use a flexible parametric model or ML survival model that doesn't impose proportional hazards.

Q3: A Kaplan-Meier curve shows that 30% of customers have churned by month 6, 60% by month 12, and 80% by month 24. What does this tell you and how would you operationalize it?

Show answer

Reading the curve: $S(6) = 0.70$, $S(12) = 0.40$, $S(24) = 0.20$. The median survival time (where $S(t) = 0.5$) is approximately 12 months. The acceleration from month 6 to 12 (30 percentage points lost) vs. month 12 to 24 (20 percentage points lost) indicates that churn decelerates over time — customers who survive past month 12 are likely higher-loyalty, lower-risk, or have greater switching costs.

Operationally: (1) Target interventions at months 3–9, which is the steepest part of the hazard — this is when marginal retention spend has the highest expected return; (2) Segment the curve by customer attributes (premium vs. standard, acquisition channel, product tier) using the log-rank test to identify which segments drive the high early churn; (3) Use Cox regression to quantify which features predict early churn and estimate effect sizes; (4) Deploy a survival model to score individual customers by predicted 6-month churn probability, enabling personalized retention outreach.

Q4: What is the C-index and how does it differ from AUC-ROC?

Show answer

The concordance index (C-index) measures a survival model's ability to correctly rank survival times. Among all comparable pairs — where subject $i$ experienced the event before subject $j$, and $j$ was still at risk at the time $i$ had the event — what fraction did the model rank correctly by assigning higher predicted risk to $i$? C-index = 0.5 is random; C-index = 1.0 is perfect discrimination.

It is analogous to AUC-ROC for binary classification — AUC-ROC is actually the C-index for binary outcomes. The key differences: (1) Time-to-event vs. fixed horizon: AUC-ROC requires a fixed prediction time horizon (e.g., "churned within 6 months?") and treats the outcome as binary; C-index works with full survival times and naturally handles varying follow-up durations; (2) Censoring: C-index excludes pairs where the comparison is ambiguous due to censoring; AUC-ROC applied naively to survival data would mishandle censored subjects; (3) Use case: use C-index when the model outputs risk scores or predicted survival functions; use time-specific AUC when you have a fixed decision horizon and need calibrated probabilities.

Action Items

  • Explain right censoring and why dropping censored observations biases survival estimates
  • Derive or describe the Kaplan-Meier step function — what happens at each event time?
  • Interpret a Cox model hazard ratio (both HR > 1 and HR < 1)
  • State the proportional hazards assumption and how to test it (Schoenfeld residuals)
  • Know the C-index: definition, range, and how it differs from AUC-ROC
  • Name two business applications of survival analysis and describe the event and censoring mechanism for each