# 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 $Xs are the predictor variables. ```python aft = WeibullAFTFitter() aft.fit(df, duration_col='tenure', event_col='churn') aft.print_summary() df['expected_lifetime'] = aft.predict_expectation(df) ``` To interpret the fitted coefficients, exponentiate them (`exp(coef)`) — the resulting value tells you the multiplicative change in survival duration associated with a one-unit increase in that predictor, i.e. survival duration changes by a factor of $1-\exp(coef)$. You can also inspect the partial effect of one or a few variables by supplying a list of candidate values and plotting the resulting predicted survival curves: ```python aft.plot_partial_effects_on_outcome('support_calls', [0, 3, 5, 8]) plt.show() ``` ![[Pasted image 20260525224239.png]] ## 6.2 What is hazard? The **hazard** is the instantaneous churn rate at time $t$, given that a customer has already survived up to time $t$. It answers a question like: "for a customer who made it to month 6, what's their probability of churning in the next small window of time?" A high hazard *early* on typically indicates lots of users dropping off shortly after signup, which is common in consumer apps. A high hazard appearing *later* typically points to long-term fatigue or contract-end effects. ## 6.3 Cox proportional hazards model The **hazard function** $h(t)$ describes the probability that the event occurs at some specific time, given survival up to that point, and the **hazard rate** is the instantaneous rate at which the event occurs. The hazard function and the survival function are mathematically related and can be derived from one another. The **Cox proportional hazards model** is a semi-parametric survival regression model used to study how covariates affect the risk of an event occurring over time. It rests on a couple of key assumptions: **proportional hazards**, meaning the hazard ratio between any two groups stays constant over time, and that all individuals' hazards remain proportional to one another throughout the study. Notably, Cox regression does *not* require assuming a Weibull or normal distribution for survival time, which is what makes it "semi-parametric" rather than fully parametric. The **hazard ratio** describes how much a unit's hazard increases or decreases relative to a baseline hazard, where the baseline is defined by setting all covariates to their average (or median) values. **When the proportional hazards assumption fails:** in many practical situations, minor violations don't meaningfully affect model performance. If the violation is more serious, it's worth trying an alternative modeling framework, such as the Weibull AFT model from §6.1, and comparing AIC between the two. To check the assumption directly, plot Kaplan-Meier curves by group (`plot(ax=ax)`) — if the resulting lines cross each other, the proportional hazards assumption may not hold. ```python from lifelines import CoxPHFitter cph = CoxPHFitter(penalizer=0.1) # add regularization to prevent overfitting cph.fit(df_catcode, duration_col='tenure', event_col='churn') cph.print_summary() ``` As with Weibull AFT, interpret the result using `exp(coef)` — a covariate increases or decreases risk over time by $1-\exp(coef)$ percent. The proportional hazards assumption can also be checked via bootstrap: ```python cph.check_assumptions(df) ``` ![[Pasted image 20260525231655.png]] The fitted Cox model can also predict an individual's expected remaining survival time — for example, predicting the median remaining time until churn for currently active (non-churned) users: ```python df_current_features = df[df['churn']==0].drop(columns=['tenure', 'churn']) df_current_tenure = df[df['churn']==0]['tenure'] # predict, for currently active users, their individual median time until churn median_remaining_life = cph.predict_median(df_current_features, conditional_after=df_current_tenure) ``` ## 6.4 Cox vs. Weibull AFT regression | Cox | Weibull AFT | | ------------------------------------------------------ | ---------------------------------------------------------------- | | Models risk/hazard | Models survival time directly | | Semi-parametric | Fully parametric | | Assumes proportional hazards | Assumes a Weibull distribution | | Focuses on how variables affect the risk of an event | Focuses on how variables speed up or slow down survival time | | Answers: "how much does this variable increase churn risk?" | Answers: "how much longer will this customer stay?" | # 7. Evaluating Survival Models A survival model's quality can be assessed along several dimensions: the **concordance index (C-index)** measures how well the model ranks survival times or risk across individuals, with 0.5 indicating random performance, 0.6–0.7 fair, 0.7–0.8 good, and above 0.8 strong. **Log-likelihood** measures overall model fit. **AIC** is used to compare parametric models directly, or a partial-AIC variant for Cox PH models. **Calibration** plots visualize predicted survival probabilities against actual observed survival, checking whether the model's confidence is well-founded. And **cross-validation** evaluates generalization performance by computing the C-index across multiple folds, rather than relying on in-sample fit alone.