Assess calibration of predicted survival probabilities at a fixed time.
calibration(
surv,
predicted,
time,
*,
n_bins=10,
conf_level=0.95,
format=None,
)
Subjects are grouped into n_bins bins by their predicted survival probability at time. Within each bin the mean prediction is compared against the observed survival, a Kaplan-Meier estimate at time for that bin’s subjects. A well-calibrated model has the observed values close to the predicted ones (points near the diagonal).
Parameters
surv: Surv
-
The Surv response (right-censored or counting-process).
predicted: Any
-
Predicted survival probability at time, one per subject (for example a column of CoxPH.predict(newdata, type="survival", times=[time])).
time: float
-
The horizon at which predictions are assessed.
n_bins: int = 10
-
Number of prediction bins (default 10). Bins are quantile-based. Empty bins are dropped, so ties in predicted may yield fewer rows.
conf_level: float = 0.95
-
Confidence level for the observed (Kaplan-Meier) interval.
format: str | None = None
-
Output format for the returned DataFrame:
None (default), "pandas", "polars", or "pyarrow". None (the default) will auto-detects and prefer Polars if available (falls back to Pandas, then Pyarrow, and raises an error if no DataFrame library is available).
Returns
DataFrame
-
One row per bin with columns
bin, n, predicted (mean), observed, observed_lower, observed_upper. Format depends on the format parameter.
Details
Bins are formed by splitting subjects into n_bins quantile-based groups of their predicted survival probability. Within each bin the Kaplan–Meier estimate at time gives the observed survival, and the mean of the predictions gives the predicted value. A well-calibrated model produces points that lie along the 45-degree diagonal when plotted as observed vs. predicted. Systematic departures from the diagonal indicate over- or under-prediction in that probability range.
Examples
Fit a Cox model on the bundled lung dataset and assess calibration at one year. Each bin’s mean predicted survival is compared against the observed Kaplan–Meier survival for that bin’s subjects:
import greenwood as gw
# Load data and build a right-censored response
lung = gw.load_dataset("lung", backend="polars")
y = gw.Surv.right(lung["time"], event=(lung["status"] == 2))
cox = gw.CoxPH().fit(y, lung[["age", "sex"]])
# Assess one-year calibration across five prediction bins
surv = cox.predict(lung[["age", "sex"]], type="survival", times=[365.0], format="pandas")
predicted = surv.iloc[0, 1:].to_numpy()
gw.calibration(y, predicted, 365.0, n_bins=5, format="polars")
shape: (5, 6)| bin | n | predicted | observed | observed_lower | observed_upper |
|---|
| i64 | i64 | f64 | f64 | f64 | f64 |
| 1 | 45 | 0.275644 | 0.226515 | 0.12563 | 0.408414 |
| 2 | 44 | 0.325993 | 0.363082 | 0.237804 | 0.554357 |
| 3 | 45 | 0.388624 | 0.408116 | 0.284455 | 0.585537 |
| 4 | 45 | 0.485378 | 0.540004 | 0.395497 | 0.73731 |
| 5 | 49 | 0.568638 | 0.514285 | 0.376278 | 0.702909 |