# 1. What Is Survival Analysis
Survival analysis is a statistical framework for modeling **when** an event happens, not just whether it happens at all. Instead of asking "did this customer churn?", it asks "how long until they churn?" — and critically, it's designed to correctly handle customers who haven't churned yet by the time you're looking at the data.
In a business setting, the workflow typically looks like this: fit a model on historical customers, produce a survival curve $S(t)$ representing the probability of still being active at time $t$, and use that curve for retention forecasting, LTV calculation, and identifying which cohorts or segments decay fastest. Other common applications include the time until a free-trial user converts to a paying subscriber, or the time until a piece of equipment fails.
The **survival function** models the probability that the survival duration exceeds a given value:
$
S(t) = Pr(T>t)
$
Survival analysis relies on three key assumptions: **unambiguous events** (the event of interest happens at a clearly specified, unambiguous time), **comparable survival probabilities across subjects** (an individual's survival probability doesn't depend on when they entered the study), and **non-informative censorship** (censored observations have the same underlying survival prospects as observations that continue to be followed — see §3).
# 2. Why Not Just Use Logistic Regression
Logistic regression throws away the time dimension entirely. It gives you $P(churned)$ at a single fixed snapshot, but ignores *when* the event happened — a customer who churned on day 2 looks identical to one who churned on day 364 as far as a logistic model is concerned. Worse, logistic regression has no natural way to handle customers who are still active (censored, see §3): you'd either have to drop them, losing real information, or incorrectly label them as "not churned," which biases the model. Survival analysis, by contrast, uses every observation — including the still-active ones — and models the full time dimension directly.
# 3. Censoring
A **censored** observation is one where the event of interest hasn't happened yet by the time you stop observing it. A customer who's still active today is **right-censored**: you know they survived *at least* this long, but you don't know their full lifetime. The key insight is that censored does not mean missing — a censored observation still carries real information ("survived at least X days"), and survival models are specifically built to use that information rather than discard it. Dropping censored observations instead would systematically bias estimates toward appearing to churn faster than reality. Because survival analysis models both a duration and an event indicator together, it's inherently designed to handle this kind of data.
There are three types of censoring: **right censoring**, where the true survival duration is greater than the observed duration (for example, a user who hasn't converted yet); **left censoring**, where the true survival duration is less than the observed duration (for example, a patient who already had a virus before observation started); and **interval censoring**, where the true survival duration is known to fall within some range, but not at an exact point.
Before running a survival analysis, it's worth checking the censored data directly: confirm there's a way to identify which observations are censored (look for a dedicated censorship indicator column), check that the proportion of censored data isn't too extreme (a common rule of thumb flags concern above roughly **50%** censored), and investigate whether the censorship itself is random and non-informative — that is, whether being censored has no systematic relationship to a unit's underlying survival prospects.
# 4. Estimating the Survival Curve
## 4.1 Non-parametric vs. parametric modeling
**Non-parametric modeling** makes no assumptions about the underlying shape of the data, which typically produces a survival curve that's step-like and not smooth. **Parametric modeling** instead assumes the data follows a specific statistical distribution, which produces a smooth survival curve when that chosen distribution actually fits the data well.
## 4.2 Kaplan-Meier estimator
The **Kaplan-Meier estimator** (also called the product-limit estimator) is a non-parametric method for estimating the survival function from time-to-event data, especially when censored observations are present:
$
\begin{aligned}
t=2:\ S(t=2) = \left(1-\frac{d_1}{n_1}\right) \times \left(1-\frac{d_2}{n_2}\right) \\
t=3:\ S(t=3) = S(t=2) \times \left(1-\frac{d_3}{n_3}\right)
\end{aligned}
$
Kaplan-Meier has a few notable limitations: it struggles when 50%+ of records are censored, the resulting survival curve isn't smooth, and it isn't well suited to analyzing the effect of covariates on survival — for that, a regression-style survival model is needed instead (see §6).
```python
from lifelines import KaplanMeierFitter
kmf = KaplanMeierFitter()
kmf.fit(df['tenure'], df['churn'])
kmf.plot_survival_function(ci_show=True)
print(kmf.median_survival_time_)
print(kmf.predict(36))
```
![[Pasted image 20260525214008.png]]
## 4.3 Weibull distribution
The **Weibull distribution** is a flexible parametric distribution commonly used in survival analysis to model time-to-event data, capable of representing increasing, decreasing, or constant event rates over time. Its density function is:
$
f(x;\lambda,k) = \frac{k}{\lambda}\left(\frac{x}{\lambda}\right)^{(k-1)}e^{-\left(\frac{x}{\lambda}\right)^{k}}
$
where $k$ determines the shape of the curve and $\lambda$ determines its scale. When $k<1$, the event rate decreases over time; when $k=1$, the event rate stays constant over time; and when $k>1$, the event rate increases over time.
```python
wb = WeibullFitter()
wb.fit(df['tenure'], event_observed=df['churn'])
print(f'k: {wb.rho_:.3f}, lambda: {wb.lambda_:.3f}')
plt.figure(figsize=(4,2))
wb.plot_survival_function(ci_show=True)
plt.show()
```
![[Pasted image 20260525220521.png]]
## 4.4 Other parametric models
Several other parametric distributions are used for survival modeling depending on the assumed shape of the hazard: the **exponential model** assumes a constant hazard rate over time; the **gamma model** is a flexible distribution suited to skewed, positive data with varying hazard behavior; the **log-normal model** assumes the logarithm of survival times follows a normal distribution; and the **log-logistic model** can capture a hazard rate that first increases and then decreases over time.
To choose between these candidate models, **AIC** (Akaike Information Criterion) is the standard tool: it estimates the relative amount of information a model loses, penalizing models with more estimated parameters along the way. A lower AIC indicates a higher-quality model, and — all else equal — a model with fewer parameters is preferred over a more complex one.
```python
from lifelines import WeibullFitter, LogNormalFitter, ExponentialFitter
for model in [WeibullFitter, LogNormalFitter, ExponentialFitter]:
m = model()
m.fit(durations=df['tenure'], event_observed=df['churn'])
print(model.__name__, m.AIC_)
```
## 4.5 Checking distributional fit: the QQ plot
A **Quantile-Quantile (QQ) plot** checks whether a dataset follows a particular distribution — most commonly the normal distribution — by comparing the quantiles of the observed data against the quantiles of a theoretical distribution. If the two distributions being compared are similar, the points on the QQ plot will fall approximately along the line $y=x$. In survival analysis specifically, the `lifelines` package's QQ plot functionality is used to assess how well a Weibull, log-normal, or other parametric survival model actually fits the data: fit several candidate parametric models, plot a QQ plot for each, and prefer whichever plot lies closest to $y=x$.
![[Pasted image 20260525225959.png]]
# 5. Comparing Survival Between Groups: The Log-Rank Test
Kaplan-Meier curves (§4.2) can visualize survival probabilities and confidence intervals across different groups, but visual comparison alone doesn't tell you whether an observed difference is statistically meaningful — that's what the **log-rank test** is for: a non-parametric statistical test that formally evaluates whether the survival distributions of two groups differ significantly. As with any hypothesis test, the resulting **p-value** measures the probability of observing data this extreme (or more extreme) assuming the null hypothesis of no difference is true. When comparing three or more groups at once, **multivariate (pairwise) log-rank testing** extends the same idea across every group simultaneously.
```python
from lifelines.statistics import logrank_test, multivariate_logrank_test
male = df[df['gender_Male']==1]
female = df[df['gender_Female']==1]
gender_test = logrank_test(
male['tenure'], female['tenure'],
male['churn'], female['churn']
)
print(gender_test.summary)
contract_test = multivariate_logrank_test(
df['tenure'], df['contract_length'], df['churn']
)
print(contract_test.summary)
```
# 6. Modeling Survival Time as a Function of Covariates
## 6.1 Weibull AFT (Accelerated Failure Time) regression
Weibull AFT is a regression-based survival model that predicts survival time directly from a set of features:
$
\log(T) = \beta_0 + \beta_1X_1 + \dots + \beta_nX_n + \epsilon
$
where $T$ is the survival time and the $X