Aalen-Johansen estimator of cumulative incidence functions for competing risks.
AalenJohansen(
*,
conf_level=0.95,
)
In survival analysis, competing risks occur when subjects can experience multiple types of events (e.g., progression to malignancy vs. death from other causes), and experiencing one event precludes the others. The Aalen-Johansen estimator extends the Kaplan-Meier approach to this setting by estimating the cumulative incidence function (CIF) for each cause: the probability of experiencing that specific cause by time t, accounting for competition from other causes.
Unlike naive estimates that ignore censoring or competing events, the Aalen-Johansen CIF correctly accounts for both. It is computed using transition probabilities between states via generalized Kaplan-Meier estimates. Call fit() with a multi-state Surv response (built with Surv.multistate()) to obtain estimates for each competing cause. Results are returned as tidy DataFrames with one row per combination of stratum, cause, and time.
The estimator uses the formula \(\mathrm{CIF}_j(t) = \sum_{s \le t} \hat{S}(s^-) P_{0j}(s)\), where \(\hat{S}(s^-)\) is the probability of being event-free before time \(s\), and \(P_{0j}(s)\) is the transition probability from censoring to cause \(j\). Confidence intervals use the Greenwood-style variance estimator on the complementary log-log scale for improved coverage.
Parameters
conf_level: float = 0.95
-
Confidence level for the (Wald) confidence intervals (default 0.95).
Returns
Fitted estimator
-
Call fit() to produce a fitted estimator with cached results (
states_, and internal transition matrices), accessible as tidy DataFrames.
Details
Call fit(surv, by=...) with a multi-state Surv response (built with Surv.multistate, where event codes are 0 for censoring and 1..K for the competing causes). Results are tidy frames via to_frame() (optionally format=) with one row per stratum, cause, and time.
Examples
The bundled mgus2 dataset follows monoclonal-gammopathy patients who may progress to plasma-cell malignancy ("pcm") or die first, a competing-risks setup. Build the competing-risks response by combining the progression and death indicators into a single cause code (0 censored, 1 progression, 2 death), then fit the estimator. Printing the fitted object reports the final cumulative incidence for each cause.
import greenwood as gw
mg = gw.load_dataset("mgus2", backend="pandas")
etime = mg["ptime"].where(mg["pstat"] == 1, mg["futime"])
cause = mg["pstat"].where(mg["pstat"] == 1, 2 * mg["death"])
cr = gw.Surv.multistate(etime, event=cause, states=("pcm", "death"))
aj = gw.AalenJohansen().fit(cr)
aj
AalenJohansen (Aalen-Johansen cumulative incidence)
states: pcm, death
n = 1384
final CIF
pcm 0.1613
death 0.8387
Methods
|
Name
|
Description
|
|
fit()
|
Fit cumulative incidence functions to a competing-risks response.
|
|
to_frame()
|
Return cumulative-incidence estimates as a DataFrame.
|
fit()
Fit cumulative incidence functions to a competing-risks response.
Computes the cumulative incidence function (CIF) for each cause-of-interest from a multi-state Surv response. Unlike Kaplan-Meier (which handles only a single event type), the Aalen-Johansen estimator accounts for competing events: subjects who experience a competing cause are removed from the risk set, preventing overly optimistic estimates of the probability of experiencing the target cause. Results are stored in the fitted object; access them via to_frame() (optionally format=).
The Aalen-Johansen estimator generalizes both Kaplan-Meier and Nelson-Aalen to the competing-risks setting. For each cause \(j\), it estimates \(F_j(t)\), the cumulative probability of experiencing cause \(j\) by time \(t\), accounting for all competing causes. The CIFs sum to the overall event probability at any time. Pass by= to produce separate CIF estimates per group (stratified competing-risks analysis).
Parameters
surv: Surv
-
A multi-state Surv response built with Surv.multistate(). Must have multiple causes-of-interest. Raises ValueError if a single-event response is passed (use KaplanMeier for that).
by: Any = None
-
Optional grouping variable (e.g., a column or array). Produces one set of cumulative incidence functions per unique value of
by. Default (None): fit a single, unstratified set of CIFs.
Returns
AalenJohansen
-
The fitted estimator object itself (for method chaining) with cached cumulative incidence results (time arrays, CIF per cause, confidence bands) accessible via to_frame() (optionally
format=).
Details
The Aalen-Johansen estimator is a product-integral estimator of the CIF: \(F_j(t) = \int \hat{S}_{-}(u) \, dM_j(u)\), where \(\hat{S}_{-}(u)\) is the estimated probability of surviving (remaining uncensored) just before \(u\), and \(M_j(u)\) is the counting process for cause \(j\). It reduces to Kaplan-Meier when there is only one cause and no censoring.
Left truncation is not yet supported. Multi-state responses must be built with Surv.multistate().
Examples
Fit cumulative incidence functions on the competing-risks mgus2 dataset, where subjects can experience plasma-cell malignancy (pcm) or death:
import greenwood as gw
mg = gw.load_dataset("mgus2", backend="pandas")
etime = mg["ptime"].where(mg["pstat"] == 1, mg["futime"])
cause = mg["pstat"].where(mg["pstat"] == 1, 2 * mg["death"])
cr = gw.Surv.multistate(etime, event=cause, states=("pcm", "death"))
aj = gw.AalenJohansen().fit(cr)
aj
AalenJohansen (Aalen-Johansen cumulative incidence)
states: pcm, death
n = 1384
final CIF
pcm 0.1613
death 0.8387
Fit stratified cumulative incidence functions by sex to compare risk accumulation:
aj_stratified = gw.AalenJohansen().fit(cr, by=mg["sex"])
aj_stratified
AalenJohansen (Aalen-Johansen cumulative incidence)
states: pcm, death
strata: 2
to_frame()
Return cumulative-incidence estimates as a DataFrame.
Exports the Aalen-Johansen fit with one row per cause and time point, including the risk set, cumulative-incidence estimate, standard error, confidence limits, and optional strata labels.
Parameters
format: str | None = None
-
Output format:
None (default), "pandas", "polars", or "pyarrow". When None, a backend is auto-detected (Polars, then Pandas, then PyArrow).
Returns
pandas.DataFrame, polars.DataFrame, or pyarrow.Table
-
A tidy table with columns
cause, time, n_risk, estimate, std_error, conf_low, conf_high, and optionally strata.
Raises
ImportError
-
If the requested (or, when auto-detecting, any) DataFrame library is not installed.
Examples
Fit the estimator on the competing-risks mgus2 data and export the cumulative- incidence functions as a Polars frame:
import greenwood as gw
mg = gw.load_dataset("mgus2", backend="pandas")
etime = mg["ptime"].where(mg["pstat"] == 1, mg["futime"])
cause = mg["pstat"].where(mg["pstat"] == 1, 2 * mg["death"])
cr = gw.Surv.multistate(etime, event=cause, states=("pcm", "death"))
aj = gw.AalenJohansen().fit(cr)
aj.to_frame(format="polars")
shape: (536, 7)| cause | time | n_risk | estimate | std_error | conf_low | conf_high |
|---|
| str | f64 | f64 | f64 | f64 | f64 | f64 |
| "pcm" | 1.0 | 1384.0 | 0.0 | 0.0 | 0.0 | 0.0 |
| "pcm" | 2.0 | 1341.0 | 0.001446 | 0.001022 | 0.0 | 0.003449 |
| "pcm" | 3.0 | 1311.0 | 0.001446 | 0.001022 | 0.0 | 0.003449 |
| "pcm" | 4.0 | 1296.0 | 0.002169 | 0.001251 | 0.0 | 0.004621 |
| "pcm" | 5.0 | 1279.0 | 0.002892 | 0.001444 | 0.000062 | 0.005723 |
| … | … | … | … | … | … | … |
| "death" | 341.0 | 5.0 | 0.784208 | 0.020933 | 0.743179 | 0.825237 |
| "death" | 350.0 | 4.0 | 0.784208 | 0.020933 | 0.743179 | 0.825237 |
| "death" | 373.0 | 3.0 | 0.784208 | 0.020933 | 0.743179 | 0.825237 |
| "death" | 394.0 | 2.0 | 0.784208 | 0.020933 | 0.743179 | 0.825237 |
| "death" | 424.0 | 1.0 | 0.838708 | 0.028288 | 0.783265 | 0.894152 |
Request a different backend with format=:
aj.to_frame(format="pandas")
|
cause |
time |
n_risk |
estimate |
std_error |
conf_low |
conf_high |
| 0 |
pcm |
1.0 |
1384.0 |
0.000000 |
0.000000 |
0.000000 |
0.000000 |
| 1 |
pcm |
2.0 |
1341.0 |
0.001446 |
0.001022 |
0.000000 |
0.003449 |
| 2 |
pcm |
3.0 |
1311.0 |
0.001446 |
0.001022 |
0.000000 |
0.003449 |
| 3 |
pcm |
4.0 |
1296.0 |
0.002169 |
0.001251 |
0.000000 |
0.004621 |
| 4 |
pcm |
5.0 |
1279.0 |
0.002892 |
0.001444 |
0.000062 |
0.005723 |
| ... |
... |
... |
... |
... |
... |
... |
... |
| 531 |
death |
341.0 |
5.0 |
0.784208 |
0.020933 |
0.743179 |
0.825237 |
| 532 |
death |
350.0 |
4.0 |
0.784208 |
0.020933 |
0.743179 |
0.825237 |
| 533 |
death |
373.0 |
3.0 |
0.784208 |
0.020933 |
0.743179 |
0.825237 |
| 534 |
death |
394.0 |
2.0 |
0.784208 |
0.020933 |
0.743179 |
0.825237 |
| 535 |
death |
424.0 |
1.0 |
0.838708 |
0.028288 |
0.783265 |
0.894152 |
536 rows × 7 columns