Survival Analysis Censoring Kaplan-Meier Estimator Cox Model

# Survival Analysis: Censoring, Kaplan-Meier Estimator & Cox Model

## Introduction & Motivation

Survival analysis models time-to-event data (duration until failure/death). Censoring: incomplete observation. Kaplan-Meier (product-limit) nonparametrically estimates survival curve; handles censoring via conditional probability. Cox proportional hazards regresses on covariates while accounting for censoring. Critical for medical trials, reliability engineering, customer churn.

Motivation: Standard regression assumes complete observation. Censoring (dropout, end-of-study) common in practice. Ignoring censoring biases estimates.

Applications: Medical trials (time-to-recovery), reliability (time-to-failure), customer lifetime value, unemployment duration.

---

## Core Concepts & Theory

### Survival Function

S(t) = P(T > t). Probability of surviving past time t.

### Hazard Function

h(t) = lim_{Δt→0} P(t < T ≤ t+Δt | T > t) / Δt. Instantaneous failure rate.

### Censoring

Right censoring: observation time c < T (unknown T). Kaplan-Meier handles via product-limit.

---

## Mathematical Formulation

Kaplan-Meier survival estimate:
$$S(t) = \prod_{t_i \leq t} \left(1 - \frac{d_i}{n_i} ight)$$

where d_i = events at t_i, n_i = at-risk at t_i.

Cox partial likelihood:
$$\ell(\beta) = \sum_{i: \delta_i = 1} \left[\beta^T x_i - \log \sum_{j \in R(t_i)} \exp(\beta^T x_j) ight]$$

where R(t_i) = risk set at time t_i.

Hazard ratio:
$$ ext{HR} = \exp(\beta)$$

---

## Advanced Theory & Extensions

### Competing Risks

Multiple event types; cumulative incidence accounts for competing events.

### Time-Dependent Covariates

Covariates change over time; stratified or time-interaction models.

### Frailty Models

Random intercept for hierarchical data; shared frailty for clusters.

---

## Computational Considerations

Kaplan-Meier: O(n log n) sorting + O(n) computation.

Cox partial likelihood: O(iterations × n × p) via Newton-Raphson; scales poorly with p.

Stratified Cox: Condition on strata; handles non-proportional hazards locally.

---

## Practical Implementation Strategies

### Checking Proportional Hazards

Schoenfeld residuals: plot vs. time; should be flat.

### Handling Ties

Breslow, Efron approximations in partial likelihood.

### Confidence Intervals

95% CI: exp(β ± 1.96 * SE(β)). Large-sample normal approximation.

---

## Benchmark Datasets & Evaluation

WHAS500: Worcester Heart Attack Study; 500 patients, 1-year mortality.

Veteran's Lung Cancer: 137 patients, treatment comparison.

Metrics: C-index (concordance), log-rank test (curve comparison), AUC at t.

---

## Key Challenges & Limitations

### Non-Proportional Hazards

Cox assumes constant hazard ratio; violated if lines cross. Use stratified models.

### Sparse Events

Few events limit precision; use Firth's bias-corrected partial likelihood.

### Competing Risks

Ignoring competitors overestimates event probability.

---

## Hyperparameter Tuning

Smoothing (smoothed baseline hazard): Bandwidth via cross-validation.

Frailty variance prior: Weakly informative to avoid overfitting.

Stratification: Balance bias-variance.

---

## Real-World Applications & Case Studies

Clinical Trials: FDA requires survival curves; intent-to-treat with censoring.

Organ Transplant: Waiting list survival; right-censoring from transplant/censoring.

Insurance: Mortality tables; age-stratified Cox model.

---

## Integration with Other Methods

Survival + Machine Learning → Random Survival Forests, gradient-boosted survival.

Survival + Neural Networks → DeepHit (multi-task learning for competing risks).

---

## Summary & Key Takeaways

Survival analysis quantifies time-to-event distributions, accounting for censoring via Kaplan-Meier (nonparametric) or Cox regression (semiparametric).

Principles:
1. Censoring is missing data; must be handled correctly.
2. Kaplan-Meier uses product-limit formula.
3. Cox assumes proportional hazards; verify via residuals.
4. Hazard ratio exp(β) is interpretable effect size.
5. Log-rank test compares survival curves.

---

---

## Appendix: Practical Labs

### Lab 1: Kaplan-Meier Estimator

import numpy as np
import pandas as pd

def kaplan_meier(T, event):
 """
 T: time-to-event
 event: 1 = event, 0 = censored
 """
 data = pd.DataFrame({'T': T, 'event': event})
 data = data.sort_values('T')
 
 S = 1.0
 survival_times = []
 survival_probs = []
 
 for t in data['T'].unique():
 subset = data[data['T'] <= t]
 at_risk = len(data[data['T'] >= t])
 events = len(subset[subset['event'] == 1])
 
 if at_risk > 0:
 S *= (1 - events / at_risk)
 survival_times.append(t)
 survival_probs.append(S)
 
 return np.array(survival_times), np.array(survival_probs)

# Data
T = np.array([5, 6, 6, 7, 8, 9, 10])
event = np.array([1, 1, 0, 1, 0, 1, 1])

times, probs = kaplan_meier(T, event)
print(f"Survival times: {times}")
print(f"Survival probs: {probs}")
assert len(times) == len(probs), "Should match"
assert (probs >= 0).all() and (probs <= 1).all(), "Probs should be in [0, 1]"
print("✓ Kaplan-Meier working")

if __name__ == "__main__":
 print("Lab 1: Kaplan-Meier - PASSED")

### Lab 2: Log-Rank Test

import numpy as np
from scipy.stats import chi2

def log_rank_test(T1, event1, T2, event2):
 """Compare two survival curves"""
 # Merge data
 all_times = np.concatenate([T1, T2])
 all_events = np.concatenate([event1, event2])
 group = np.concatenate([np.zeros_like(T1), np.ones_like(T2)])
 
 # Compute test statistic
 O1 = 0 # Observed events in group 1
 E1 = 0 # Expected events
 V = 0 # Variance
 
 for t in np.unique(all_times):
 mask = all_times == t
 n1 = (group[mask] == 0).sum()
 n2 = (group[mask] == 1).sum()
 d = all_events[mask].sum()
 
 if n1 + n2 > 0:
 O1 += (all_events[mask] * (group[mask] == 0)).sum()
 E1 += d * n1 / (n1 + n2)
 V += d * (n1 + n2 - d) * n1 * n2 / ((n1 + n2)**2 * (n1 + n2 - 1) + 1e-8)
 
 # Chi-squared statistic
 Z = (O1 - E1)**2 / (V + 1e-8)
 pvalue = 1 - chi2.cdf(Z, df=1)
 
 return Z, pvalue

T1 = np.array([1, 3, 5, 6, 8])
event1 = np.array([1, 1, 0, 1, 1])
T2 = np.array([2, 4, 6, 7, 9])
event2 = np.array([1, 1, 1, 0, 1])

Z, pvalue = log_rank_test(T1, event1, T2, event2)
print(f"Log-rank test: Z={Z:.4f}, p-value={pvalue:.4f}")
assert 0 <= pvalue <= 1, "P-value should be in [0, 1]"
print("✓ Log-rank test working")

if __name__ == "__main__":
 print("Lab 2: Log-Rank - PASSED")

### Lab 3: Cox Proportional Hazards

import numpy as np
from scipy.optimize import minimize

def cox_partial_likelihood(beta, X, T, event):
 """Negative partial likelihood for Cox regression"""
 n = len(T)
 risk_score = np.exp(X @ beta)
 
 # Sort by time
 sort_idx = np.argsort(T)
 T_sorted = T[sort_idx]
 event_sorted = event[sort_idx]
 risk_sorted = risk_score[sort_idx]
 
 nll = 0
 for i in range(n):
 if event_sorted[i] == 1:
 risk_set = risk_sorted[i:]
 nll -= np.log(risk_sorted[i] / np.sum(risk_set) + 1e-8)
 
 return nll / n

# Data
np.random.seed(42)
n = 50
X = np.column_stack([np.random.randn(n), np.random.randn(n)])
beta_true = np.array([0.5, -0.3])
T = np.random.exponential(scale=10, size=n)
event = np.random.binomial(1, 0.7, n)

# Fit
beta_init = np.zeros(2)
result = minimize(cox_partial_likelihood, beta_init, args=(X, T, event), method='BFGS')
beta_hat = result.x

print(f"Estimated beta: {beta_hat}")
assert len(beta_hat) == 2, "Should estimate 2 coefficients"
assert np.isfinite(beta_hat).all(), "Should be finite"
print("✓ Cox model working")

if __name__ == "__main__":
 print("Lab 3: Cox - PASSED")

### Lab 4: Hazard Ratio Interpretation

import numpy as np

def hazard_ratio(beta):
 """Hazard ratio from log-scale coefficient"""
 return np.exp(beta)

def hazard_ratio_ci(beta, se_beta, ci=0.95):
 """95% CI for hazard ratio"""
 z_crit = 1.96
 ci_log = [beta - z_crit * se_beta, beta + z_crit * se_beta]
 return np.exp(ci_log[0]), np.exp(ci_log[1])

# Example: treatment effect
beta_treatment = 0.3
se_treatment = 0.1

hr = hazard_ratio(beta_treatment)
ci_low, ci_high = hazard_ratio_ci(beta_treatment, se_treatment)

print(f"Hazard ratio: {hr:.4f}, 95% CI: [{ci_low:.4f}, {ci_high:.4f}]")
assert hr > 0, "HR should be positive"
assert ci_low < hr < ci_high, "HR should be in CI"
assert ci_low > 0, "CI should be positive"
print("✓ Hazard ratio working")

if __name__ == "__main__":
 print("Lab 4: HR - PASSED")

Go deeper with CFSGPT

Get AI-powered deep-dives, save terms, and run advanced simulations — free account.

Create Free Account