Kaplan–Meier and Nelson–Aalen Estimation

Michael BrenndoerferOctober 26, 202639 min read

Learn Kaplan-Meier and Nelson-Aalen estimation for right-censored data, covering risk sets, ties, Greenwood variance, medians, and NumPy implementation.

Machine Learning from ScratchAll chapters
Part 6 of 15Survival Analysis
Chapter 49Kaplan–Meier and Nelson–Aalen Estimation
Reading the full chapter · 10 sections · 39 min10 sections · 39 min

All 10 sections · 39 min read

Kaplan–Meier and Nelson–Aalen EstimationFull chapter · Sections in this chapter1 / 10 ↓
Reading tools Definitions, links & notes

Choose your expertise level to adjust how many terms are explained. Beginners see more tooltips, experts see fewer to maintain reading flow. Hover over underlined terms for instant definitions.

Article links

Make inline references clickable

Notes & comments

Kaplan–Meier and Nelson–Aalen Estimation

Suppose we follow patients after a hospital discharge and record the time until readmission. Some patients are readmitted during follow-up; others move away, withdraw, or reach the end of the study without a readmission. We cannot simply average the observed times, because the censored patients have unknown event times. We need an estimator that uses exactly the information we have: each patient contributes to the risk set until the last time we know they were event-free.

In the previous chapter we separated event times, censoring times, survival functions, and hazard functions. We now turn those ideas into two nonparametric estimators. The Kaplan-Meier estimator estimates the survival function S(t)=P(T>t)S(t)=P(T>t) directly as a product of conditional survival probabilities. The Nelson-Aalen estimator estimates the cumulative hazard H(t)H(t) by adding up estimated hazard increments at observed event times.

Both estimators are built from the same object: the risk set at each event time. That shared structure lets us derive them side by side. Kaplan-Meier answers “what fraction survived past this time?”, while Nelson-Aalen answers “how much hazard has accumulated by this time?”. We will see that the two answers are closely related but not identical, especially when events are tied or the risk sets are small.

Advantages

Kaplan-Meier and Nelson-Aalen make no parametric assumption about the shape of the survival distribution. We do not need to choose an exponential, Weibull, or log-normal model before looking at the data. The estimated curves follow the observed event times, so they are excellent first descriptions of a time-to-event sample. This makes them useful for exploratory analysis, for checking whether a parametric model is plausible, and for communicating results to audiences who want an empirical survival curve rather than a regression coefficient.

Both estimators handle right censoring in a principled way. A censored subject is not treated as if they survived forever, and they are not dropped from the analysis at time zero. Instead, they remain in the risk set until their censoring time and then leave. Kaplan-Meier curves are a standard way to summarize right-censored follow-up in clinical trials. The same risk-set construction can describe equipment lifetimes or time to customer churn under the stated censoring and sampling assumptions.

The estimators are also simple and fast. A production implementation sorts the observed times and computes risk-set counts and event counts, which is O(nlog⁡n)O(n\log n) in the sample size. The output is easy to interpret: a step function for survival, a step function for cumulative hazard, pointwise confidence intervals, and medians or quantiles when the curve reaches them. The Breslow baseline cumulative-hazard estimator in a Cox model generalizes the Nelson-Aalen construction by weighting each risk-set member by its fitted relative risk; it reduces to Nelson-Aalen when all relative risks are one. Cumulative-hazard estimates also enter counting-process diagnostics, such as Cox martingale residuals, through the fitted cumulative hazard.

Disadvantages

Kaplan-Meier and Nelson-Aalen are marginal estimators. They describe the survival experience of the entire sample, not the effect of a treatment or covariate after adjustment. If treated and untreated patients differ in prognostic factors such as age or disease severity, comparing two Kaplan-Meier curves can mix the treatment effect with those differences. Covariate adjustment requires an additional modeling, weighting, or standardization step; the Cox proportional hazards model we will meet next is one such approach.

For these unadjusted marginal curves, censoring must leave the observed risk sets representative of the event risk in the population. Independence of event and censoring times is a simple sufficient assumption. Independence only after conditioning on predictors of both failure and censoring can require stratification or weighting. Selective dropout of patients with higher subsequent event risk can leave a healthier observed risk set and bias estimated survival upward. Competing risks are another failure mode: if a patient dies from another cause, coding that death as censored and interpreting one minus Kaplan–Meier as cumulative incidence can overestimate the cumulative incidence of the event of interest.

The tails of the curves are fragile. As follow-up progresses, fewer subjects remain at risk, so tail estimates depend on less information. Absolute Greenwood standard errors and pointwise interval widths need not increase monotonically. If the largest observed time is censored, the Kaplan-Meier curve never reaches zero, and we have no direct estimate of survival beyond the last follow-up. A median may not be reached at all, even in a large study, if follow-up is short. Ties and small risk sets also make the finite-sample difference between Kaplan-Meier and the exponential transform of Nelson-Aalen more noticeable.

Formula

The formulas in this chapter look different at first, one a product and the other a sum. The shared logic is easier to see if we start from the data and ask a single question at each event time: who is still eligible to have the event? The answer is the risk set. Once we can count the risk set and the events that occur inside it, both estimators follow almost immediately.

We begin with the observed data. For subject ii,

  • TiT_i is the true event time,
  • CiC_i is the censoring time,
  • Yi=min⁡(Ti,Ci)Y_i=\min(T_i,C_i) is the observed follow-up time,
  • Δi=1{Ti≤Ci}\Delta_i=\mathbf{1}\{T_i\le C_i\} is the event indicator, equal to 11 if we observed the event and 00 if the observation was censored.

We assume independent draws from the target population, strictly positive event times, and right censoring. For the basic marginal estimators, independence of TiT_i and CiC_i is a sufficient censoring assumption, with positive probability of remaining observed throughout the time range we estimate. Random treatment assignment alone does not establish this assumption about follow-up.

Risk sets, tied events, and censoring order

At any time tt, the risk set is the set of subjects still under observation and event-free just before tt:

R(t)={i:Yi≥t}.\mathcal{R}(t)=\{i:Y_i\ge t\}.

Its size is n(t)=∣R(t)∣n(t)=|\mathcal{R}(t)|. This definition is the bookkeeping center of the whole chapter. A subject who has already had the event is no longer eligible to have it again, and a subject who has been censored before tt cannot contribute information about an event at tt. Both leave the risk set. A subject who is censored exactly at tt is a boundary case, and we will handle it by convention below.

If the distinct observed event times are

t1<t2<⋯<tm,t_1<t_2<\cdots<t_m,

then at event time tjt_j we write

nj=n(tj)=∑i=1n1{Yi≥tj}n_j=n(t_j)=\sum_{i=1}^{n}\mathbf{1}\{Y_i\ge t_j\}

for the number at risk and

dj=∑i=1n1{Yi=tj, Δi=1}d_j=\sum_{i=1}^{n}\mathbf{1}\{Y_i=t_j,\ \Delta_i=1\}

for the number of events at that time. The ratio dj/njd_j/n_j is the empirical event fraction among those who were still eligible at tjt_j. That fraction will become the Nelson-Aalen increment, and its complement will become the Kaplan-Meier factor.

It is also where censoring enters: a censored subject remains in njn_j up to and including their censoring time, then leaves. Censoring does not create an event, but it changes who is available for later events.

Tied events occur when dj>1d_j>1. The product-limit estimator handles ties by grouping all events at tjt_j into one conditional factor. Tied event and censoring times require a convention. We use the standard convention that events are processed before censorings at the same time. A subject censored at tjt_j is therefore counted in njn_j for the event at tjt_j, and is removed only after that event time. Removing those censorings first would make the denominator smaller, increase the event fraction, and lower the estimated survival under a different convention.

This convention is worth checking in software. The tied-data check below confirms events before censorings for the installed SciPy and statsmodels implementations; consult documentation when exact tie behavior matters. The convention matters most when the risk set is small or when many subjects share the same recorded time.

Kaplan–Meier product-limit estimation

Kaplan-Meier estimates survival by chaining conditional probabilities. At event time tjt_j, suppose njn_j subjects are at risk and djd_j events occur. The estimated probability of surviving past tjt_j, given that a subject was still at risk just before tjt_j, is

1−djnj1-\frac{d_j}{n_j}

where:

  • djd_j: the number of events at event time tjt_j
  • njn_j: the number of subjects at risk at event time tjt_j

If no events occur at tjt_j, then dj=0d_j=0 and the factor is 11; censoring times do not create factors. If all subjects at risk have an event, the factor is 00 and the estimated survival drops to zero. In between, the factor is the fraction of the current risk set that survives the event time.

Multiplying these conditional factors across all event times up to tt gives the Kaplan-Meier estimator:

S^KM(t)=∏j:tj≤t(1−djnj)\boxed{ \hat S_{\mathrm{KM}}(t) = \prod_{j:t_j\le t} \left(1-\frac{d_j}{n_j}\right) }

with the empty product defined as 11 for times before the first event. The word product-limit refers to this product of estimated conditional survival probabilities. It is the nonparametric maximum likelihood estimator of the survival function under independent right censoring.

where:

  • S^KM(t)\hat S_{\mathrm{KM}}(t): the estimated survival probability of surviving past time tt
  • tjt_j: the jj-th distinct observed event time
  • djd_j: the number of events at event time tjt_j
  • njn_j: the number of subjects at risk at event time tjt_j
  • 1−dj/nj1-d_j/n_j: the estimated conditional probability of surviving past tjt_j given survival to just before tjt_j
  • the product over j:tj≤tj:t_j\le t: multiply the conditional factors for all event times up to and including tt

We can read the formula as a sequence of updates. Start with S^(0)=1\hat S(0)=1. At each event time, multiply the current survival probability by the fraction of the risk set that did not have an event. Censoring changes the next risk set, but it does not directly multiply the curve by anything.

The variance of the Kaplan-Meier estimator is usually estimated with Greenwood’s formula. Greenwood’s cumulative variance factor is

σ^2(t)=∑j:tj≤tdjnj(nj−dj),\hat\sigma^2(t) = \sum_{j:t_j\le t} \frac{d_j}{n_j(n_j-d_j)},

where:

  • σ^2(t)\hat\sigma^2(t): Greenwood’s cumulative variance factor for the survival curve up to time tt, which combines with the squared survival estimate to give the variance of S^KM(t)\hat S_{\mathrm{KM}}(t)
  • djd_j: the number of events at event time tjt_j
  • njn_j: the number of subjects at risk at event time tjt_j
  • nj−djn_j-d_j: the number at risk who do not have the event at tjt_j

Then

Var⁡^(S^KM(t))=S^KM(t)2 σ^2(t).\widehat{\operatorname{Var}}\bigl(\hat S_{\mathrm{KM}}(t)\bigr) = \hat S_{\mathrm{KM}}(t)^2\,\hat\sigma^2(t).

where:

  • Var⁡^(S^KM(t))\widehat{\operatorname{Var}}\bigl(\hat S_{\mathrm{KM}}(t)\bigr): the estimated variance of the Kaplan-Meier survival estimate at time tt
  • S^KM(t)\hat S_{\mathrm{KM}}(t): the Kaplan–Meier survival estimate at time tt
  • σ^2(t)\hat\sigma^2(t): Greenwood’s cumulative variance factor at time tt

To see where this comes from, take logs:

log⁡S^KM(t)=∑j:tj≤tlog⁡(1−djnj).\log \hat S_{\mathrm{KM}}(t) = \sum_{j:t_j\le t} \log\left(1-\frac{d_j}{n_j}\right).

For a binomial proportion p^j=dj/nj\hat p_j=d_j/n_j, the delta method gives

Var⁡(log⁡(1−p^j))≈pjnj(1−pj).\operatorname{Var}\left(\log(1-\hat p_j)\right) \approx \frac{p_j}{n_j(1-p_j)}.

where:

  • p^j=dj/nj\hat p_j=d_j/n_j: the estimated event fraction at event time tjt_j
  • pjp_j: the event probability in the local binomial working model, conditional on membership in the current risk set at tjt_j
  • njn_j: the number of subjects at risk at event time tjt_j
  • djd_j: the number of events at event time tjt_j

Plugging in p^j=dj/nj\hat p_j=d_j/n_j yields dj/[nj(nj−dj)]d_j/[n_j(n_j-d_j)]. This binomial calculation is a sketch of the local variance contribution. The full large-sample derivation uses counting-process martingales, since earlier events change later risk sets. Summing the contributions and applying the delta method once more to S=exp⁡(log⁡S)S=\exp(\log S) gives the factor S^2\hat S^2. The denominator nj−djn_j-d_j shows why Greenwood’s formula fails when nj=djn_j=d_j: at that point the curve hits zero and the variance formula is not finite.

Nelson–Aalen cumulative hazard estimation

For an absolutely continuous event-time distribution, at times with positive survival where the following limit exists, the hazard is the instantaneous event rate among those still at risk:

h(t)=lim⁡Δt↓0P(t≤T<t+Δt∣T≥t)Δt.h(t) = \lim_{\Delta t\downarrow 0} \frac{P(t\le T<t+\Delta t\mid T\ge t)}{\Delta t}.

where:

  • h(t)h(t): the hazard function, the instantaneous event rate at time tt among those still at risk
  • TT: the true event time
  • Δt\Delta t: a small time increment
  • P(t≤T<t+Δt∣T≥t)P(t\le T<t+\Delta t\mid T\ge t): the conditional probability of an event in [t,t+Δt)[t,t+\Delta t) given survival to time tt

The cumulative hazard is

H(t)=∫0th(u) du.H(t)=\int_0^t h(u)\,du.

where:

  • H(t)H(t): the cumulative hazard up to time tt
  • h(u)h(u): the hazard function at time uu

This integral describes a continuous-time hazard with no initial event mass. For inherently discrete event times, cumulative hazard is instead a sum of conditional hazard jumps and need not equal −log⁡S(t)-\log S(t). The implementation below groups events that share a recorded time; it does not split a tied group into a sequence of separate failures.

Nelson-Aalen estimates HH by accumulating estimated hazard increments at the observed event times. At tjt_j, the observed event fraction among those at risk is dj/njd_j/n_j, so we set

ΔH^(tj)=djnj,\Delta \hat H(t_j)=\frac{d_j}{n_j},

where:

  • ΔH^(tj)\Delta \hat H(t_j): the estimated hazard increment at event time tjt_j
  • djd_j: the number of events at event time tjt_j
  • njn_j: the number of subjects at risk at event time tjt_j

Adding these increments gives

H^NA(t)=∑j:tj≤tdjnj\boxed{ \hat H_{\mathrm{NA}}(t) = \sum_{j:t_j\le t} \frac{d_j}{n_j} }

with H^(0)=0\hat H(0)=0, where:

  • H^NA(t)\hat H_{\mathrm{NA}}(t): the Nelson–Aalen cumulative hazard estimate at time tt
  • tjt_j: the jj-th distinct observed event time
  • djd_j: the number of events at event time tjt_j
  • njn_j: the number of subjects at risk at event time tjt_j
  • the sum over j:tj≤tj:t_j\le t: add the hazard increments for all event times up to and including tt

This is a nondecreasing step function. It jumps at event times; censoring alone creates no jump. The jump size is large when many events occur among few subjects at risk, and small when one event occurs among a large risk set.

A grouped Greenwood-type variance estimate is

Var⁡^(H^NA(t))=∑j:tj≤tdj(nj−dj)nj3.\widehat{\operatorname{Var}}\bigl(\hat H_{\mathrm{NA}}(t)\bigr) = \sum_{j:t_j\le t} \frac{d_j(n_j-d_j)}{n_j^3}.

This is a binomial plug-in, Greenwood-type estimate. The Aalen counting-process estimate uses ∑dj/nj2\sum d_j/n_j^2 instead. The two differ by the factor 1−dj/nj1-d_j/n_j in each contribution. A smaller reported variance does not by itself establish better uncertainty coverage, especially with few subjects at risk.

where:

  • Var⁡^(H^NA(t))\widehat{\operatorname{Var}}\bigl(\hat H_{\mathrm{NA}}(t)\bigr): the estimated variance of the Nelson–Aalen cumulative hazard at time tt
  • djd_j: the number of events at event time tjt_j
  • njn_j: the number of subjects at risk at event time tjt_j

A local plug-in argument treats the event count as binomial given the current risk set, with njn_j trials and event probability estimated by dj/njd_j/n_j. This motivates the contribution; it is not an exact finite-sample variance proof for the accumulated estimator. The variance of dj/njd_j/n_j is approximately

1nj2⋅njdjnj(1−djnj)=dj(nj−dj)nj3.\frac{1}{n_j^2}\cdot n_j\frac{d_j}{n_j}\left(1-\frac{d_j}{n_j}\right) = \frac{d_j(n_j-d_j)}{n_j^3}.

Nelson-Aalen is closely related to Kaplan-Meier, but the relationship is not exact in finite samples. Since

log⁡S^KM(t)=∑tj≤tlog⁡(1−djnj),\log \hat S_{\mathrm{KM}}(t) = \sum_{t_j\le t} \log\left(1-\frac{d_j}{n_j}\right),

and log⁡(1−x)≈−x\log(1-x)\approx -x for small xx, we have

S^KM(t)≈exp⁡(−∑tj≤tdjnj)=exp⁡(−H^NA(t)).\hat S_{\mathrm{KM}}(t) \approx \exp\left(-\sum_{t_j\le t}\frac{d_j}{n_j}\right) = \exp\left(-\hat H_{\mathrm{NA}}(t)\right).

The approximation is useful on a time range where the event fractions dj/njd_j/n_j and their accumulated second-order error are small. Large risk sets with few events per time make the individual fractions small. It is not an identity. For 0<x<10<x<1, where xx represents an event fraction dj/njd_j/n_j,

1−x<e−x1-x < e^{-x}

so the product-limit estimator lies below the exponential transform of Nelson–Aalen whenever events occur:

S^KM(t)≤exp⁡(−H^NA(t)).\hat S_{\mathrm{KM}}(t) \le \exp\left(-\hat H_{\mathrm{NA}}(t)\right).

The exact product-integral relationship is

S^KM(t)=∏tj≤t(1−ΔH^NA(tj)),\hat S_{\mathrm{KM}}(t) = \prod_{t_j\le t} \left(1-\Delta \hat H_{\mathrm{NA}}(t_j)\right),

not exp⁡(−H^NA)\exp(-\hat H_{\mathrm{NA}}). We should not report exp⁡(−H^NA)\exp(-\hat H_{\mathrm{NA}}) as if it were Kaplan–Meier. Their relative difference tends to zero in a small-increment limit where the accumulated second-order error also vanishes. Heavy ties or small risk sets can make the difference noticeable.

The finite-time gap has a simple source: for every event fraction xx strictly between zero and one, the product-limit factor 1−x1-x is smaller than the exponential factor e−xe^{-x}. We can display that inequality directly.

PythonIn [3]
CollapseExpand
import matplotlib.pyplot as plt
import numpy as np

event_fraction_grid = np.linspace(0.001, 0.999, 499)
factor_product = 1.0 - event_fraction_grid
factor_exp = np.exp(-event_fraction_grid)
factor_gap = factor_exp - factor_product
VisualizationOut [4]
CollapseExpand
Two curves compare the product-limit factor 1-x with the exponential factor exp(-x). The product-limit curve stays below the exponential curve, and the shaded gap widens as the event fraction grows.
The product-limit factor 1-x and the exponential factor exp(-x) for an event fraction x=d/n. The product-limit factor is always smaller when events occur, so exp(-Nelson-Aalen) lies above Kaplan-Meier.

Mathematical Properties

The formulas above imply a few structural facts that are useful when reading software output or checking a plot:

  • S^KM(t)\hat S_{\mathrm{KM}}(t) is a right-continuous, nonincreasing step function with values in [0,1][0,1] and S^KM(0)=1\hat S_{\mathrm{KM}}(0)=1.
  • H^NA(t)\hat H_{\mathrm{NA}}(t) is a right-continuous, nondecreasing step function with H^NA(0)=0\hat H_{\mathrm{NA}}(0)=0.
  • Both estimators change only at observed event times. A censoring time changes future risk sets but does not create a jump.
  • Ties are handled by grouping: the Kaplan–Meier factor is 1−dj/nj1-d_j/n_j, and the Nelson–Aalen increment is dj/njd_j/n_j.
  • The exponential transform satisfies exp⁡(−H^NA(t))≥S^KM(t)\exp(-\hat H_{\mathrm{NA}}(t))\ge \hat S_{\mathrm{KM}}(t), with exact equality only when no events have occurred at or before time tt.
  • Greenwood’s variance requires nj>djn_j>d_j. If nj=djn_j=d_j, the survival estimate reaches zero and the variance formula is not finite at that point.

Uncertainty, median survival, and tail limitations

The Kaplan-Meier intervals below describe uncertainty at a fixed time; they do not give simultaneous coverage for the whole curve. We use the log-log transformation because its interior limits stay within [0,1][0,1], whereas a symmetric normal interval can extend outside that range. Its coverage accuracy still depends on censoring, sample support, and distance from the endpoints. Let

σ^2(t)=∑j:tj≤tdjnj(nj−dj).\hat\sigma^2(t) = \sum_{j:t_j\le t} \frac{d_j}{n_j(n_j-d_j)}.

For 0<S^(t)<10<\hat S(t)<1, a pointwise (1−α)(1-\alpha) interval is

[S^(t)exp⁡(z1−α/2σ^(t)/∣log⁡S^(t)∣),S^(t)exp⁡(−z1−α/2σ^(t)/∣log⁡S^(t)∣)],\left[ \hat S(t)^{\exp\left(z_{1-\alpha/2}\hat\sigma(t)/|\log \hat S(t)|\right)},\quad \hat S(t)^{\exp\left(-z_{1-\alpha/2}\hat\sigma(t)/|\log \hat S(t)|\right)} \right],

where:

  • S^(t)\hat S(t): the Kaplan–Meier survival estimate at time tt
  • σ^(t)\hat\sigma(t): the square root of Greenwood’s cumulative variance factor, ∑j:tj≤tdj/[nj(nj−dj)]\sqrt{\sum_{j:t_j\le t}d_j/[n_j(n_j-d_j)]}
  • z1−α/2z_{1-\alpha/2}: the standard normal quantile, such as 1.961.96 for a 95% interval
  • α\alpha: the significance level (one minus the confidence level)

The log-log transformation is undefined at S^(t)=0\hat S(t)=0 or 11. We return missing limits at these boundaries instead of treating a zero-width display as statistical certainty. The known starting value S(0)=1S(0)=1 is separate because event times are strictly positive. These are approximate pointwise intervals. Simultaneous coverage over a specified, adequately supported time interval requires a method calibrated for the whole curve, such as a Hall–Wellner band; separate pointwise bootstrap percentiles would still be pointwise.

For H^(t)>0\hat H(t)>0 in a supported regime with a reliable nondegenerate variance and normal approximation, an approximate pointwise log-transformed interval for H(t)H(t) is

[H^(t)exp⁡(−z1−α/2σ^H(t)H^(t)), H^(t)exp⁡(z1−α/2σ^H(t)H^(t))],\left[ \hat H(t)\exp\left(-z_{1-\alpha/2}\frac{\hat\sigma_H(t)}{\hat H(t)}\right), \ \hat H(t)\exp\left(z_{1-\alpha/2}\frac{\hat\sigma_H(t)}{\hat H(t)}\right) \right],

where:

  • H^(t)\hat H(t): the Nelson–Aalen cumulative hazard estimate at time tt
  • σ^H(t)=∑j:tj≤tdj(nj−dj)/nj3\hat\sigma_H(t)=\sqrt{\sum_{j:t_j\le t} d_j(n_j-d_j)/n_j^3}: the standard error from the chosen Greenwood-type variance estimate
  • z1−α/2z_{1-\alpha/2}: the standard normal quantile for a (1−α)(1-\alpha) interval, such as 1.961.96 for a 95% interval
  • α\alpha: the significance level (one minus the confidence level)

The displayed log interval is undefined at H^(t)=0\hat H(t)=0. A no-event estimate at a positive follow-up time does not imply zero uncertainty; inference there needs a boundary-specific method. The chosen binomial variance can also vanish after every member of a tied risk set fails. A resulting zero-width interval is not justified merely because the hazard estimate is positive. The known origin H(0)=0H(0)=0 is a separate structural value.

The median survival time is the first time the estimated survival curve reaches or falls below one half:

m^=inf⁡{t:S^KM(t)≤0.5}.\hat m = \inf\{t:\hat S_{\mathrm{KM}}(t)\le 0.5\}.

Because the curve is a step function, the median is an observed event time, not an interpolated value between steps. If the curve never reaches or falls below 0.50.5 by the last follow-up, the median is not estimable. We should report “median not reached” rather than a fabricated number, together with the last follow-up time and the number still at risk. A confidence interval for the median can be obtained by inverting pointwise survival intervals, as in the Brookmeyer–Crowley approach, or by resampling observed time–event-indicator pairs and recomputing the Kaplan-Meier median. Adequate follow-up around the median is still needed; some resamples or interval endpoints may not reach it. A point estimate alone does not show its sampling uncertainty.

Tail limitations follow directly from the risk-set definition. At late times njn_j is small, so each event removes a larger fraction of the remaining survival estimate. The absolute Greenwood standard error need not grow: it also depends on the survival estimate, which is falling. If the largest observed time is censored, the curve remains above zero and we have no direct estimate beyond that time. A flat empirical tail does not establish a cure fraction. Beyond the last observed follow-up, there are no further observations to support its extension. If nj=djn_j=d_j, the curve drops to zero and Greenwood’s denominator vanishes. Sparse late risk sets limit tail precision. Tied times require the recorded-time convention and time-resolution assumptions to be checked; informative censoring can bias the curve throughout follow-up.

Example - A Ten-Person Failure Table

The formulas are compact, so it is worth slowing down and watching the risk set change row by row. We now work through a small failure table by hand. The observed times and event indicators are:

Ten-person failure table with follow-up times and event indicators.
SubjectYiY_iΔi\Delta_i
121
231
331
450
561
660
781
890
9101
10120

The distinct event times are 2,3,6,8,102,3,6,8,10. We process tied events and censorings using events first. At each event time, the first question is always the same: who is still in the risk set?

At t=2t=2, all ten subjects are at risk and one event occurs:

n1=10,d1=1,1−d1n1=1−110=0.9.n_1=10,\qquad d_1=1,\qquad 1-\frac{d_1}{n_1}=1-\frac{1}{10}=0.9.

Thus S^(2)=0.9\hat S(2)=0.9 and H^(2)=0.1\hat H(2)=0.1. The risk set has shrunk by one because subject 1 has now had the event.

At t=3t=3, subject 1 has already had an event, so nine subjects remain at risk. Two events occur at time 3.

n2=9,d2=2,1−d2n2=1−29=79.n_2=9,\qquad d_2=2,\qquad 1-\frac{d_2}{n_2}=1-\frac{2}{9}=\frac{7}{9}.

The updated estimates are

S^(3)=0.9⋅79=0.7,H^(3)=0.1+29=0.322222.\hat S(3)=0.9\cdot \frac{7}{9}=0.7, \qquad \hat H(3)=0.1+\frac{2}{9}=0.322222.

At t=5t=5 subject 4 is censored. This does not create a step, but it removes subject 4 from later risk sets. That is the only role of censoring here: it changes who is available for future events.

At t=6t=6, subject 4 is gone, but subject 6 is censored at time 6 and remains in the risk set for the event at time 6 under our convention. Six subjects are at risk and one event occurs:

n3=6,d3=1,1−d3n3=1−16=56.n_3=6,\qquad d_3=1,\qquad 1-\frac{d_3}{n_3}=1-\frac{1}{6}=\frac{5}{6}.

Therefore

S^(6)=0.7⋅56=0.583333,H^(6)=0.322222+16=0.488889.\hat S(6)=0.7\cdot \frac{5}{6}=0.583333, \qquad \hat H(6)=0.322222+\frac{1}{6}=0.488889.

At t=8t=8, subjects 5 and 6 have left after time 6, so four subjects remain at risk. One event occurs:

n4=4,d4=1,1−d4n4=1−14=34.n_4=4,\qquad d_4=1,\qquad 1-\frac{d_4}{n_4}=1-\frac{1}{4}=\frac{3}{4}.

Thus

S^(8)=0.583333⋅34=0.4375,H^(8)=0.488889+14=0.738889.\hat S(8)=0.583333\cdot \frac{3}{4}=0.4375, \qquad \hat H(8)=0.488889+\frac{1}{4}=0.738889.

At t=10t=10, two subjects remain at risk and one event occurs:

n5=2,d5=1,1−d5n5=1−12=12.n_5=2,\qquad d_5=1,\qquad 1-\frac{d_5}{n_5}=1-\frac{1}{2}=\frac{1}{2}.

So

S^(10)=0.4375⋅12=0.21875,H^(10)=0.738889+12=1.238889.\hat S(10)=0.4375\cdot \frac{1}{2}=0.21875, \qquad \hat H(10)=0.738889+\frac{1}{2}=1.238889.

At t=12t=12, the one remaining subject is censored, so the Kaplan-Meier curve remains flat at 0.218750.21875 after time 10. It never reaches zero because the last subject leaves the study without an observed event.

The Nelson–Aalen estimate at time 10 is 1.2388891.238889, and

exp⁡(−H^NA(10))=exp⁡(−1.238889)≈0.2897.\exp(-\hat H_{\mathrm{NA}}(10)) = \exp(-1.238889) \approx 0.2897.

This is above the Kaplan–Meier value 0.218750.21875. The gap is the finite-sample effect of using 1−dj/nj1-d_j/n_j rather than e−dj/nje^{-d_j/n_j} at each event time. It is not a bug, and it is one reason we should not label exp⁡(−H^NA)\exp(-\hat H_{\mathrm{NA}}) as Kaplan–Meier.

For uncertainty, the Greenwood increments are

110⋅9, 29⋅7, 16⋅5, 14⋅3, 12⋅1.\frac{1}{10\cdot 9},\ \frac{2}{9\cdot 7},\ \frac{1}{6\cdot 5},\ \frac{1}{4\cdot 3},\ \frac{1}{2\cdot 1}.

Their sum is approximately 0.6595240.659524. At the final event time,

SE⁡^(S^(10))=0.218750.659524≈0.1776.\widehat{\operatorname{SE}}(\hat S(10)) = 0.21875\sqrt{0.659524} \approx 0.1776.

The median is the first time the curve is at or below 0.50.5. At time 6 the estimate is 0.5833330.583333, and at time 8 it is 0.43750.4375, so the estimated median is 88. This is a useful check on the risk-set arithmetic: the median is read from the step function, not computed by interpolating between event times.

Implementation in NumPy

PythonIn [5]
CollapseExpand
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
from book_plot_style import PALETTE, polish_axes, use_book_style
from scipy.stats import norm
PythonIn [6]
CollapseExpand
def fit_km_na(times, events):
    """Return event-time Kaplan-Meier and Nelson-Aalen estimates."""
    times = np.asarray(times, dtype=float)
    events = np.asarray(events)
    if times.ndim != 1 or events.ndim != 1 or times.shape != events.shape:
        raise ValueError(
            "times and events must be one-dimensional with the same shape"
        )
    if times.size == 0:
        raise ValueError("at least one observation is required")
    if not np.all(np.isfinite(times)) or np.any(times <= 0):
        raise ValueError("times must be finite positive elapsed durations")
    if np.any((events != 0) & (events != 1)):
        raise ValueError("events must contain only 0 and 1")
    events = events.astype(int)

    event_times = np.unique(times[events == 1])
    columns = [
        "time",
        "n_at_risk",
        "events",
        "factor",
        "S_km",
        "H_na",
        "greenwood_sum",
        "var_H_sum",
    ]
    if event_times.size == 0:
        return pd.DataFrame(columns=columns)

    rows = []
    S = 1.0
    H = 0.0
    greenwood_sum = 0.0
    var_H_sum = 0.0

    for tj in event_times:
        n = int(np.sum(times >= tj))
        d = int(np.sum((times == tj) & (events == 1)))
        factor = 1.0 - d / n
        S *= factor
        H += d / n
        if n > d:
            greenwood_sum += d / (n * (n - d))
        else:
            greenwood_sum = np.nan
        var_H_sum += d * (n - d) / n**3
        rows.append(
            {
                "time": tj,
                "n_at_risk": n,
                "events": d,
                "factor": factor,
                "S_km": S,
                "H_na": H,
                "greenwood_sum": greenwood_sum,
                "var_H_sum": var_H_sum,
            }
        )

    return pd.DataFrame(rows, columns=columns)
PythonIn [7]
CollapseExpand
def step_value(times, values, t, left_value=1.0):
    """Evaluate a right-continuous step function at time t."""
    times = np.asarray(times)
    values = np.asarray(values)
    idx = np.searchsorted(times, t, side="right") - 1
    if idx < 0:
        return float(left_value)
    return float(values[idx])


def km_loglog_ci(km, level=0.95):
    """Pointwise Greenwood log-log confidence interval for Kaplan-Meier."""
    if not np.isfinite(level) or not 0 < level < 1:
        raise ValueError("level must be strictly between 0 and 1")
    z = norm.ppf(0.5 + level / 2.0)
    S = km["S_km"].to_numpy(dtype=float)
    green = km["greenwood_sum"].to_numpy(dtype=float)
    lower = np.full_like(S, np.nan)
    upper = np.full_like(S, np.nan)
    mask = (S > 0) & (S < 1)
    se = np.zeros_like(S)
    se[mask] = np.sqrt(green[mask]) / np.abs(np.log(S[mask]))
    lower[mask] = S[mask] ** np.exp(z * se[mask])
    upper[mask] = S[mask] ** np.exp(-z * se[mask])
    return lower, upper

At an event that exhausts the risk set, the point estimates remain valid but greenwood_sum and the transformed limits are missing. An all-censored nonempty sample returns an empty event-time table, so there is no estimated median to report.

We first reproduce the ten-person example from the previous section.

PythonIn [8]
CollapseExpand
hand = pd.DataFrame(
    {
        "subject": np.arange(1, 11),
        "time": np.array([2, 3, 3, 5, 6, 6, 8, 9, 10, 12], dtype=float),
        "event": np.array([1, 1, 1, 0, 1, 0, 1, 0, 1, 0], dtype=int),
    }
)

km_hand = fit_km_na(hand["time"], hand["event"])
Out[9]:
time n_at_risk events factor S_km H_na greenwood_sum var_H_sum
0 2.0 10 1 0.900000 0.900000 0.100000 0.011111 0.009000
1 3.0 9 2 0.777778 0.700000 0.322222 0.042857 0.028204
2 6.0 6 1 0.833333 0.583333 0.488889 0.076190 0.051353
3 8.0 4 1 0.750000 0.437500 0.738889 0.159524 0.098228
4 10.0 2 1 0.500000 0.218750 1.238889 0.659524 0.223228

The table matches the hand calculation. The factor column shows the conditional survival factor at each event time, and the H_na column accumulates the Nelson–Aalen increments. At the final event time, the Kaplan–Meier estimate is 0.218750.21875 and the Nelson–Aalen estimate is 1.2388891.238889.

The cumulative curves hide the event-by-event arithmetic. At each event time we can compare the additive Nelson-Aalen increment with the exact decrement in log survival that the product-limit estimator uses.

PythonIn [10]
CollapseExpand
increment_times = km_hand["time"].to_numpy(dtype=float)
na_increments = km_hand["events"].to_numpy(dtype=float) / km_hand[
    "n_at_risk"
].to_numpy(dtype=float)
km_log_decrements = -np.log(km_hand["factor"].to_numpy(dtype=float))
cum_na_increments = np.cumsum(na_increments)
cum_km_decrements = np.cumsum(km_log_decrements)
VisualizationOut [11]
CollapseExpand
Grouped bars compare Nelson-Aalen increments with exact log-survival decrements at five times.
Nelson-Aalen increments d_j/n_j and exact log-survival decrements -log(1-d_j/n_j) at the five event times in the ten-person example. The exact log-survival decrement is larger at each event time, so its cumulative negative log survival exceeds the Nelson-Aalen estimate.
ConsoleOut [12]
CollapseExpand
KM at final event time 10: 0.218750
Nelson-Aalen at final event time: 1.238889
exp(-H_NA): 0.289706
Greenwood SE of S at final event time: 0.177649
Greenwood-type SE of Nelson-Aalen at final event time: 0.472470
KM median: 8

Now we generate a larger right-censored sample. The true event times follow a Weibull distribution with shape 1.51.5 and scale 1212, and censoring times follow an independent exponential distribution with mean 2828. Because we simulate both the event times and the censoring times, we know the true survival curve and can compare it with the estimates.

PythonIn [13]
CollapseExpand
rng = np.random.default_rng(20240517)
n_sim = 240
shape_true = 1.5
scale_true = 12.0

true_time = scale_true * (-np.log(rng.uniform(size=n_sim))) ** (
    1.0 / shape_true
)
censor_time = rng.exponential(scale=28.0, size=n_sim)
sim_time = np.minimum(true_time, censor_time)
sim_event = (true_time <= censor_time).astype(int)

sim = pd.DataFrame({"time": sim_time, "event": sim_event})
km_sim = fit_km_na(sim["time"], sim["event"])
Out[14]:
time n_at_risk events factor S_km H_na greenwood_sum var_H_sum
0 0.366713 235 1 0.995745 0.995745 0.004255 0.000018 0.000018
1 0.803951 228 1 0.995614 0.991377 0.008641 0.000038 0.000037
2 0.816653 227 1 0.995595 0.987010 0.013047 0.000057 0.000057
3 0.883790 225 1 0.995556 0.982623 0.017491 0.000077 0.000076
4 0.923124 224 1 0.995536 0.978237 0.021955 0.000097 0.000096
ConsoleOut [15]
CollapseExpand
Sample size: 240
Observed events: 162
Censored observations: 78
KM median in simulation: 9.240

We compute the pointwise log-log confidence limits and the Greenwood standard errors before plotting, so the plot cells themselves only present already-computed results.

PythonIn [16]
CollapseExpand
lo_sim, hi_sim = km_loglog_ci(km_sim)
se_sim = np.where(
    km_sim["S_km"].to_numpy() > 0,
    km_sim["S_km"].to_numpy() * np.sqrt(km_sim["greenwood_sum"].to_numpy()),
    np.nan,
)

For a reference comparison, we use two installed implementations of Kaplan–Meier: SciPy and statsmodels. The check includes the tied ten-person table and the seeded sample. statsmodels also exposes its event counts and risk-set sizes; applying our grouped Nelson–Aalen sum to those independently computed counts checks the bookkeeping, without calling a library Nelson–Aalen fitter.

PythonIn [17]
CollapseExpand
from scipy.stats import CensoredData, ecdf
from statsmodels.duration.survfunc import SurvfuncRight

reference_rows = []
for case, data, fitted in [
    ("Tied hand table", hand, km_hand),
    ("Seeded sample", sim, km_sim),
]:
    durations = data["time"].to_numpy(dtype=float)
    indicators = data["event"].to_numpy(dtype=int)
    scipy_sf = ecdf(CensoredData.right_censored(durations, indicators == 0)).sf
    reference_km = SurvfuncRight(durations, indicators)
    event_grid = fitted["time"].to_numpy(dtype=float)
    np.testing.assert_array_equal(event_grid, reference_km.surv_times)
    np.testing.assert_array_equal(fitted["n_at_risk"], reference_km.n_risk)
    np.testing.assert_array_equal(fitted["events"], reference_km.n_events)
    scipy_values = scipy_sf.evaluate(event_grid)
    reference_na = np.cumsum(reference_km.n_events / reference_km.n_risk)
    np.testing.assert_allclose(
        fitted["S_km"], scipy_values, atol=1e-12, rtol=1e-12
    )
    np.testing.assert_allclose(
        fitted["S_km"], reference_km.surv_prob, atol=1e-12, rtol=1e-12
    )
    np.testing.assert_allclose(
        fitted["H_na"], reference_na, atol=1e-12, rtol=1e-12
    )
    reference_rows.append(
        {
            "Case": case,
            "KM–SciPy max error": np.max(np.abs(fitted["S_km"] - scipy_values)),
            "KM–statsmodels max error": np.max(
                np.abs(fitted["S_km"] - reference_km.surv_prob)
            ),
            "NA count-check max error": np.max(
                np.abs(fitted["H_na"] - reference_na)
            ),
        }
    )
comparison = pd.DataFrame(reference_rows)
Out[18]:
Case KM–SciPy max error KM–statsmodels max error NA count-check max error
0 Tied hand table 0.000000e+00 5.551115e-17 0.0
1 Seeded sample 1.110223e-16 3.330669e-16 0.0

The computed errors are within floating-point precision. The tied table exercises the events-before-censorings convention. These numerical checks establish agreement for the examples; they do not validate independent censoring in an observed population.

A small edge case illustrates the median problem. In this three-person example, the only event occurs early and the other two subjects are censored afterward, so survival stays at 2/32/3 and the median is not reached.

PythonIn [19]
CollapseExpand
no_median = fit_km_na([1.0, 2.0, 3.0], [1, 0, 0])
Out[20]:
time n_at_risk events factor S_km H_na greenwood_sum var_H_sum
0 1.0 3 1 0.666667 0.666667 0.333333 0.166667 0.074074
ConsoleOut [21]
CollapseExpand
Last KM value in edge case: 0.666667
Did the curve reach 0.5? False

Key Parameters

The times argument contains positive follow-up times in a consistent unit. It should not contain calendar dates unless they have been converted to elapsed time from a meaningful origin. The events argument is the event indicator: 11 for an observed event and 00 for right censoring. Missing times or missing event indicators cannot be handled by the basic estimator; they require a missing-data strategy before fitting.

The tie convention is part of the estimator, not a cosmetic detail. Our function counts every subject with Yi≥tjY_i\ge t_j in njn_j, including subjects censored exactly at tjt_j, and counts only observed events in djd_j. This implements events before censorings. The level argument in km_loglog_ci controls the pointwise confidence level. The usual censoring and sampling assumptions and a sufficiently supported large-sample regime are needed for the Greenwood/delta-method approximation, particularly away from risk exhaustion and transform boundaries.

In SciPy, CensoredData.right_censored receives a Boolean censoring mask, so we pass indicators == 0; ecdf(...).sf then represents the estimated survival function. statsmodels SurvfuncRight instead receives the event indicators directly, with 1 meaning an event. Keeping these opposite indicator conventions explicit prevents a reference check from silently reversing the data.

Key Methods

fit_km_na returns a table at the distinct event times. The columns n_at_risk and events expose the risk-set construction, which is often more informative than the final curve alone. The columns S_km and H_na are the two estimators. The columns greenwood_sum and var_H_sum are cumulative variance components, so their square roots give standard errors after multiplying by the appropriate factors.

step_value evaluates a right-continuous step function at a scalar query time, given sorted event times and an aligned value array. The reference comparison above instead evaluates the library curves on the fitted event-time grid. km_loglog_ci returns pointwise lower and upper survival limits. For a full curve, evaluate the limits at event times and shade only the finite limits, remembering that the resulting band is pointwise rather than simultaneous. Evaluating a step function beyond observed follow-up does not establish survival there.

The SciPy survival object provides evaluate for step-function values. The statsmodels arrays surv_times, surv_prob, n_risk, and n_events expose the event-time curve and the counts used in our comparison. The from-scratch table makes the same risk-set logic visible for inspection.

Visualizing Kaplan–Meier and Nelson–Aalen

The estimates are already computed. We now use plots to expose the mechanism: where the steps occur, how censorings enter, how the product-limit and exponential transforms differ, and how the shrinking risk set limits tail interpretation.

The standard-error plot describes this one simulated cohort. Its shape depends on both the risk set and the falling survival estimate; a rise followed by a decline is not a universal pattern.

VisualizationOut [23]
CollapseExpand
Two step curves declining from one. Kaplan-Meier lies below exp(-Nelson-Aalen), with steps at times 2, 3, 6, 8, and 10. Censoring marks sit below the curves.
Kaplan-Meier and exp(-Nelson-Aalen) curves for the ten-person failure table. Steps occur only at event times; censorings change future risk sets but do not create steps.

The blue curve is the Kaplan-Meier estimate. It drops from 11 to 0.90.9 at time 2, to 0.70.7 at time 3, to 0.58330.5833 at time 6, to 0.43750.4375 at time 8, and to 0.218750.21875 at time 10. The orange curve is exp⁡(−H^NA)\exp(-\hat H_{\mathrm{NA}}). It is close to the blue curve early, but it stays above it. The gap is the finite-sample difference between multiplying by 1−dj/nj1-d_j/n_j and multiplying by e−dj/nje^{-d_j/n_j}.

The censor marks at times 5, 6, 9, and 12 do not create steps. They remind us that subjects leave after their censoring times and are unavailable for later events. The horizontal line at 0.50.5 shows why the median is time 8: the curve is still above 0.50.5 at time 6 and below 0.50.5 at time 8.

VisualizationOut [24]
CollapseExpand
A step-shaped Kaplan-Meier curve close to a smooth true survival curve, surrounded by a blue pointwise confidence band whose width increases through the middle of follow-up and then decreases in the extreme tail. A dashed exp(-Nelson-Aalen) curve stays slightly above the Kaplan-Meier curve. Censoring marks sit below the curves.
Kaplan-Meier curve with a pointwise 95% Greenwood log-log confidence band for the simulated right-censored sample. The dashed curve is exp(-Nelson-Aalen), and the smooth curve is the true Weibull survival function.

The simulated curve tracks the true Weibull survival function reasonably well in the middle of the follow-up range. The Kaplan-Meier curve is a step function, so it cannot follow the smooth curve exactly. The pointwise interval width depends on both the risk set and the survival estimate in this one sample. Its absolute width alone does not describe how much follow-up information remains. The dashed exp⁡(−H^NA)\exp(-\hat H_{\mathrm{NA}}) curve again sits slightly above the product-limit curve. Censoring marks appear as follow-up progresses; they do not create steps in the curve, but they remove subjects from later risk sets, and the risk set is smallest at the latest follow-up times where few patients remain.

The band in this figure is pointwise. It does not have 95% simultaneous coverage for the entire curve. Simultaneous coverage requires calibration for the entire specified time interval; the pointwise 95% labels here do not provide it.

VisualizationOut [25]
CollapseExpand
A left-continuous risk-set curve decreases from 240 subjects at time zero to one just before the last observed event.
Subjects at risk just before each observed event or censoring time. The decreasing risk set exposes how little follow-up remains in the tail.
VisualizationOut [26]
CollapseExpand
The standard-error curve reaches its maximum near follow-up time 10 and then declines; the undefined terminal standard-error value is omitted.
Pointwise Greenwood standard error in this simulated sample. It rises through the middle of follow-up and then declines. The terminal value is omitted because the last event exhausts the risk set.

The risk-set curve starts at 240 and falls as events and censorings accumulate. The standard-error curve is not monotone: its path depends on both the risk set and the falling survival estimate. Its last value is undefined and is omitted. One event removes a larger fraction of the remaining estimated survival when ten subjects are at risk than when two hundred are at risk. A flat Kaplan-Meier tail means that no further events were observed; it does not establish a cure fraction or show that the hazard has stopped. Check the number still at risk, because a flat segment can occur with either many or few subjects under observation.

Practical Implications

Under the chapter's independent right-censoring and sampling assumptions, Kaplan-Meier provides a nonparametric descriptive survival curve. It suits summarizing time-to-event data, estimating medians or other quantiles, comparing unadjusted groups, and supplying the empirical curve behind a more structured model. Nelson–Aalen is the natural choice when the cumulative hazard is the target, when building the baseline cumulative hazard for a Cox model, or when working with counting-process representations, and the two answer complementary survival and cumulative-hazard questions. Alternatives become preferable when description is not enough: to adjust for covariates, the Cox model extends the risk-set approach through partial likelihood, while a parametric accelerated failure-time model relates covariates to survival time using a censored-data likelihood; when a competing event can preclude the event of interest, coding that competing event as censored and using one minus the ordinary Kaplan–Meier estimate can overestimate the cumulative incidence of interest, so cumulative incidence should instead be estimated with a competing-risk method such as Aalen–Johansen, or reconstructed from all relevant cause-specific hazards.

Data preparation matters more than the estimator itself. Times must be measured from a well-defined origin such as diagnosis, treatment start, or account creation, and the event indicator must be 11 for the event of interest and 00 for right censoring; missing times or missing event indicators cannot be silently ignored. Staggered calendar recruitment can simply produce different administrative right-censoring durations when each subject’s clock starts at enrollment. Delayed entry arises when observation begins after the chosen analysis-time origin, such as enrollment at age 60 on an age time scale. Then risk sets must include only subjects who have entered and remain observed, with an appropriate truncation assumption. This basic implementation does not handle delayed entry. The independent-censoring assumption deserves explicit discussion: in a randomized trial, administrative censoring may be roughly independent of prognosis, whereas in an observational study censoring may be informative if patients who worsen transfer care, stop responding, or are lost to follow-up. No automatic diagnostic can prove independence, but examining censoring patterns over time and across covariates, comparing censored and uncensored subjects on baseline characteristics, and performing sensitivity analyses are reasonable steps, with suitable stratification, censoring weights, or other adjustment considered when censoring and failure depend on shared covariates.

Reporting should make the risk-set mechanics visible. Include the number at risk at several follow-up times, the number of observed events, the amount and pattern of censoring, the tie convention, and the method used for confidence intervals, because those counts show how many observations support the tail. When the curve never reaches or falls below 0.50.5, report the median as not reached together with the last follow-up time rather than extrapolating from a flat tail; when the curve reaches zero because nj=djn_j=d_j, state that Greenwood’s variance is undefined at that point instead of plotting a misleading zero-width interval. Group comparisons based on Kaplan-Meier curves are descriptive, and an ordinary unstratified log-rank test is still unadjusted: if groups differ in baseline covariates, the visual difference may be confounded, so the Cox model is the natural next step. Computationally both estimators are inexpensive. A production implementation sorts the data and processes each event time once, costing O(nlog⁡n)O(n\log n) time and O(n)O(n) memory, while the readable implementation in this chapter rescans the sample at each event time; the dominant practical cost is not computation but careful definition of the event, the time origin, the censoring mechanism, and the risk set.

Summary

Kaplan-Meier and Nelson-Aalen are the two foundational nonparametric estimators in survival analysis. Both are built from risk sets. At each event time tjt_j, njn_j subjects are at risk and djd_j events occur. Kaplan-Meier multiplies conditional survival factors 1−dj/nj1-d_j/n_j to estimate S(t)S(t). Nelson-Aalen adds hazard increments dj/njd_j/n_j to estimate H(t)H(t).

The estimators are closely related but not identical. Kaplan-Meier is the product-limit estimator, and Nelson-Aalen is the additive cumulative-hazard estimator. The exponential transform exp⁡(−H^NA)\exp(-\hat H_{\mathrm{NA}}) approximates Kaplan–Meier over a time range where event fractions and their accumulated second-order error are small, but it is not the same estimator. With ties or small risk sets, the difference can be material.

We used Greenwood’s formula for Kaplan-Meier and a Greenwood-type binomial plug-in variance for Nelson-Aalen, alongside the alternative Aalen variance. Intervals based on them are approximate and pointwise; transformed limits are undefined at the boundary values discussed above. The median is the first time the survival curve reaches or falls below 0.50.5; if the curve stays above 0.50.5 throughout follow-up, the median is not estimable. Tail estimates are limited by the number at risk, the amount of censoring, and the last follow-up time. Independent censoring and adequate follow-up support are key conditions for consistent estimation under the sampling assumptions; finite-sample bias can remain. Estimating cumulative incidence in the presence of competing events requires an appropriate competing-risk method, and delayed entry requires risk sets that account for entry times.

Quiz

Ready to test your understanding? Take this quick quiz to reinforce what you've learned about Kaplan-Meier and Nelson-Aalen estimation.

Kaplan-Meier and Nelson-Aalen Estimation Quiz

Question 1 of 70 of 7 completed
At an event time, which subjects are counted in the risk-set size under the convention used in this chapter?
Join the discussionAsk a question or share what you learned.

Comments

Add to the conversation

A question, a correction, or an example from your own work.

No comments yet. Be the first to share your thoughts!

A different angle
All writing
Machine Learning

Evaluating Survival Predictions

Score censored survival models with concordance and IPCW Brier scores. Learn to compare Cox, Weibull AFT, and Kaplan-Meier forecasts honestly.

77 min read
Machine Learning

Cox Proportional Hazards

Learn how the Cox proportional hazards model estimates covariate effects on hazards using partial likelihood, with NumPy implementation and diagnostics.

57 min read
Machine Learning

Accelerated Failure-Time Models

Learn how AFT models fit censored event-time data, interpret coefficients as time ratios, and compare Weibull and log-normal survival curves.

44 min read

About the author

Reference

Citation details

Cite or share this article.

BIBTEXAcademic
@misc{brenndoerfer2026kaplanmeier, author = {Michael Brenndoerfer}, title = {Kaplan–Meier and Nelson–Aalen Estimation}, year = {2026}, url = {https://mbrenndoerfer.com/writing/kaplan-meier-nelson-aalen-from-scratch}, organization = {mbrenndoerfer.com}, note = {Accessed: 2026-10-28} }
APAAcademic
Michael Brenndoerfer (2026). Kaplan–Meier and Nelson–Aalen Estimation. Retrieved from https://mbrenndoerfer.com/writing/kaplan-meier-nelson-aalen-from-scratch
MLAAcademic
Michael Brenndoerfer. "Kaplan–Meier and Nelson–Aalen Estimation." 2026. Web. October 28, 2026. <https://mbrenndoerfer.com/writing/kaplan-meier-nelson-aalen-from-scratch>.
CHICAGOAcademic
Michael Brenndoerfer. "Kaplan–Meier and Nelson–Aalen Estimation." Accessed October 28, 2026. https://mbrenndoerfer.com/writing/kaplan-meier-nelson-aalen-from-scratch.
HARVARDAcademic
Michael Brenndoerfer (2026) 'Kaplan–Meier and Nelson–Aalen Estimation'. Available at: https://mbrenndoerfer.com/writing/kaplan-meier-nelson-aalen-from-scratch (Accessed: October 28, 2026).
SimpleBasic
Michael Brenndoerfer (2026). Kaplan–Meier and Nelson–Aalen Estimation. https://mbrenndoerfer.com/writing/kaplan-meier-nelson-aalen-from-scratch
Newsletter

Stay up to date

Get new articles, book updates, and notes on AI and technology.

Unsubscribe at any time. Privacy policy