time_dependent_auc()

IPCW (Uno) time-dependent AUC at specified times.

Usage

Source

time_dependent_auc(
    surv,
    marker,
    times,
    *,
    data=None,
)

Computes the cumulative-dynamic AUC at each requested time using the inverse-probability- of-censoring-weighted (IPCW) estimator of Uno et al. (2011). At each time t, cases are subjects who experienced an event by t and controls are subjects still at risk after t. The AUC measures how well the marker separates the two groups, correcting for censoring bias via IPCW weights.

Interpretation:

  • 0.5: Random discrimination (marker carries no prognostic information at t).
  • 0.5: Better-than-random. The marker ranks earlier-failing subjects higher.

  • 1.0: Perfect discrimination at t.
  • AUC tends to vary with t. Use integrated_auc() for a single summary.

Higher marker = higher risk convention: the marker should be on a scale where larger values indicate greater hazard (e.g., a Cox linear predictor, a predicted cumulative incidence, or a biomarker positively associated with failure). To use a lower-is-worse marker (e.g., predicted survival probability), negate it first.

Parameters

surv: Surv | Outcome | str

A right-censored Surv response. An Outcome or a formula response such as 'Surv(time, status == 2)' is also accepted, with its columns read from data.

marker: Any

Risk score for each subject (one value per observation). Higher values should indicate higher risk (earlier expected failure). Accepts a 1-D array, pandas/Polars Series, or Python sequence.

times: Any

Evaluation times where the AUC is computed. 1-D array-like. Times before the first event or after the last observation yield nan.

data: Any = None
A data frame (pandas, Polars, PyArrow, DuckDB, a lazy frame, …) holding the columns named by the response and marker. When surv is an Outcome or a formula, rows with a missing value in any column used are dropped first, along with the matching rows of any arrays passed alongside.

Returns

ndarray
AUC at each requested time, shape (len(times),). Values are in [0, 1] or nan when a time has no cases or no controls.

Details

Uno et al. (2011) estimator: For time t let

  • cases: \mathcal{C}(t) = \{i : T_i \le t,\; \Delta_i = 1\}
  • controls: \mathcal{K}(t) = \{j : T_j > t\}
  • IPCW weight for case i: w_i = \hat{G}(T_i-)^{-2}, where \hat{G} is the Kaplan-Meier estimate of the censoring survival function.

\widehat{AUC}(t) = \frac{\displaystyle\sum_{i \in \mathcal{C}(t)} w_i \Bigl[\#\{j\in\mathcal{K}(t):\eta_j < \eta_i\} + \tfrac{1}{2}\#\{j\in\mathcal{K}(t):\eta_j = \eta_i\}\Bigr]} {|\mathcal{K}(t)| \cdot \displaystyle\sum_{i \in \mathcal{C}(t)} w_i}

When G(t) = 1 (no censoring) the estimator reduces to the empirical AUC of the binary problem “case vs. control at t”.

Relationship to concordance: The Harrell C-statistic is closely related to the time-averaged AUC across all event times. Use integrated_auc() to obtain a single time-averaged summary that is directly comparable to the C-index.

Examples

Fit a Cox model on the lung dataset and compute its time-dependent AUC using the linear predictor as the risk marker.

import greenwood as gw

# Load data and name the response columns
lung = gw.load_dataset("lung", backend="polars")
death = gw.Outcome.surv(time="time", event="status")
cox = gw.CoxPH().fit(death, covariates=["age", "sex"], data=lung)

# Compute time-dependent AUC at three clinically relevant horizons
lp = cox.predict(type="lp")
auc = gw.time_dependent_auc(death, marker=lp, times=[180, 365, 540], data=lung)
auc
array([0.64152627, 0.5943264 , 0.6023971 ])

Compare discrimination of two models via integrated_auc():

# Summarize discrimination as a single time-averaged AUC
ibs = gw.integrated_auc(death, marker=lp, times=[180, 365, 540], data=lung)
ibs
0.6084157733720682