Brier score, NRI and IDI
All three scores exist to answer the same question — is the new model better than the old one? The Brier score folds discrimination and calibration into one number, has no scale of its own, and is simply wrong if you compute it directly on censored survival data. The NRI compresses improvement into a sum of two proportions, at the cost of a sign that depends on which cut-points you picked. This page computes all three, plus the decision curve, on one pair of models and one set of patients, and lets them contradict each other.
What these three scores add to the C-index
The C-index only looks at ranking. Calibration only looks at the scale. Once both have been checked, a paper usually reports three more numbers:
- Brier score — one overall score that mixes discrimination and calibration together
- NRI (net reclassification improvement) — how many patients the new model moves into the right band
- IDI (integrated discrimination improvement) — how much further apart the mean predictions of the two outcome groups have been pulled
All three are computed here on exactly the same two models and the same patients as
the decision curve page: developed on
survival::rotterdam (2982 patients, 1713 events),
validated on survival::gbsg (686 patients, 299 events),
horizon 5 years.
| Model | Predictors | C-index | 95% CI for the C-index | Mean predicted 5-year risk |
|---|---|---|---|---|
| A: eight predictors, carried over as published | 8 | 0.6617 | 0.628 to 0.695 | 43.1% |
| B: four predictors, recalibrated in this cohort | 4 | 0.6416 | 0.605 to 0.679 | 51.3% |
Model A has the higher C-index by 0.020, but it was never recalibrated: it predicts 43.1% on average, where this cohort actually runs at 50.8% — it underpredicts systematically. Every tension on this page grows out of that.
The Brier score: two things inside one number
The Brier score is the mean squared error between predicted probability and observed outcome:
is 0 or 1 and is the model’s probability. Lower is better; the range is 0 to 1.
It decomposes into a calibration term and a discrimination term (the calibration-refinement decomposition of DeGroot and Fienberg 1983; Murphy 1973 is the three-term version, which carries an extra uncertainty term that depends only on the event rate). So a Brier score that improves may mean better ranking, or merely a corrected scale — the number alone cannot tell you which. And it has no scale of its own. Is 0.228 good or bad? The only way to answer is against a reference. The reference used here is written into the stats file: hand every patient this cohort’s own Kaplan-Meier 5-year risk of 50.8%. A model that cannot beat that has told the clinic nothing it did not already know from the base rate.
Computing the Brier score directly on censored data is wrong
The awkward part of a 5-year risk model is that some patients were never followed for five years. In this cohort, 280 of 686 were censored before the 5-year mark, or 40.8%. The remaining 406 have a known 5-year status: 285 had the event and 121 did not.
The intuitive fix is to score only the patients whose status is known. That is complete-case scoring, and it is not the same estimate with more noise — it estimates a different quantity: among the patients who were followed the event rate is 70.2%, while this cohort’s 5-year risk is 50.8%.
The correct approach is IPCW (inverse probability of censoring weighting): estimate the censoring distribution with a reverse Kaplan-Meier, then weight everyone who still carries information:
Patients with the event before get , patients still event-free at get , and patients censored earlier get a weight of zero — they are not discarded, they are spoken for by the patients like them who were followed.
figures/scripts/B5-10-brier-nri-idi.R| Scoring | Model A | Model B | Reference | Patients scored | Verdict |
|---|---|---|---|---|---|
| IPCW weighted | 0.2282 | 0.2272 | 0.2499 | 686 | A beats the reference, B beats the reference |
| Complete cases only | 0.2530 | 0.2231 | 0.2092 | 406 | A loses to the reference, B loses to the reference |
Scaled Brier: a reference gives it a scale
Divide the Brier score by the reference Brier score and subtract from one, and you get the scaled Brier score (also called the Brier skill score):
Zero means no better than handing everyone the base rate; one means perfect. This is the number you can actually use to say how much a model adds.
| Quantity | Model A (eight predictors) | Model B (four, recalibrated) |
|---|---|---|
| IPCW Brier | 0.2282 (0.210 to 0.248) | 0.2272 (0.213 to 0.238) |
| Scaled Brier | 0.0866 (0.006 to 0.157) | 0.0906 (0.044 to 0.148) |
| C-index | 0.6617 | 0.6416 |
The C-index favours model A; the Brier score favours model B. The two scaled-Brier intervals overlap heavily, but interval overlap is not an argument — the two estimates come from the same patients, so their errors are almost perfectly correlated and what matters is the interval on the difference itself. Resampled inside the same bootstrap, A minus B is −0.004, 95% interval −0.045 to +0.024, which crosses zero. The defensible statement is therefore that no difference in overall accuracy between the two models was detected in this cohort — not that B is better.
Recomputed at every horizon, which also shows where the estimate stops being trustworthy
The Brier score belongs to one horizon; change the horizon and the answer changes with it:
| Horizon (years) | Model A | Model B | Reference | Scaled A | Scaled B | At risk | G(t) |
|---|---|---|---|---|---|---|---|
| 1 | 0.0782 | 0.0763 | 0.0773 | −0.011 | +0.013 | 602 | 0.958 |
| 1.5 | 0.1373 | 0.1387 | 0.1463 | +0.061 | +0.052 | 530 | 0.940 |
| 2 | 0.1755 | 0.1766 | 0.1894 | +0.073 | +0.068 | 458 | 0.895 |
| 2.5 | 0.2002 | 0.2005 | 0.2184 | +0.083 | +0.082 | 383 | 0.824 |
| 3 | 0.2042 | 0.2070 | 0.2296 | +0.111 | +0.098 | 331 | 0.751 |
| 3.5 | 0.2130 | 0.2164 | 0.2400 | +0.112 | +0.098 | 277 | 0.673 |
| 4 | 0.2219 | 0.2232 | 0.2465 | +0.100 | +0.095 | 228 | 0.595 |
| 4.5 | 0.2246 | 0.2261 | 0.2491 | +0.098 | +0.092 | 183 | 0.504 |
| 5 | 0.2282 | 0.2272 | 0.2499 | +0.087 | +0.091 | 121 | 0.359 |
| 5.5 | 0.2191 | 0.2189 | 0.2485 | +0.118 | +0.119 | 74 | 0.233 |
| 6 | 0.2217 | 0.2156 | 0.2433 | +0.089 | +0.114 | 36 | 0.125 |
The reclassification table: how it is laid out and how to read it
The NRI and the IDI are both built on a reclassification table. Cut the predicted risks into bands, then look at which band each patient falls into under the old and the new model.
The layout is fixed throughout this page: the old model (B) runs down the rows, one row per old band; the new model (A) runs across the columns. Patients on the diagonal are the ones the two models agree about, cells above the diagonal are patients the new model moved up, and cells below it are patients it moved down.
The event group and the non-event group must be tabulated separately. Moving a patient up is the right call for someone who went on to have the event and the wrong call for someone who did not; a single combined table hides who was helped and who was harmed.
These tables are counts, so they can only hold the 406 patients whose 5-year status is known. The 280 censored earlier do not fit into integer cells. That has consequences, and the last section measures them with IPCW weights.
Cut-points 10% / 20%
A generic textbook low/intermediate/high split, carried over without checking it against this cohort.
| Had the event by 5 years (n = 285) — old model B ↓, new model A → | under 10% | 10% to 20% | 20% and over | Row total |
|---|---|---|---|---|
| under 10% | 0 | 0 | 0 | 0 |
| 10% to 20% | 0 | 0 | 0 | 0 |
| 20% and over | 0 | 0 | 285 | 285 |
| Moved up / down / unchanged | 0 / 0 / 285 | |||
| Still event-free at 5 years (n = 121) — old model B ↓, new model A → | under 10% | 10% to 20% | 20% and over | Row total |
|---|---|---|---|---|
| under 10% | 0 | 0 | 0 | 0 |
| 10% to 20% | 0 | 0 | 0 | 0 |
| 20% and over | 0 | 0 | 121 | 121 |
| Moved up / down / unchanged | 0 / 0 / 121 | |||
Cut-points 15% / 30%
A moderate split, still below the cohort's 5-year risk.
| Had the event by 5 years (n = 285) — old model B ↓, new model A → | under 15% | 15% to 30% | 30% and over | Row total |
|---|---|---|---|---|
| under 15% | 0 | 0 | 0 | 0 |
| 15% to 30% | 0 | 0 | 0 | 0 |
| 30% and over | 0 | 30 | 255 | 285 |
| Moved up / down / unchanged | 0 / 30 / 255 | |||
| Still event-free at 5 years (n = 121) — old model B ↓, new model A → | under 15% | 15% to 30% | 30% and over | Row total |
|---|---|---|---|---|
| under 15% | 0 | 0 | 0 | 0 |
| 15% to 30% | 0 | 0 | 0 | 0 |
| 30% and over | 0 | 38 | 83 | 121 |
| Moved up / down / unchanged | 0 / 38 / 83 | |||
Cut-points 30% / 60%
The outer pair of the four key thresholds the decision curve on B5-07 is drawn around.
| Had the event by 5 years (n = 285) — old model B ↓, new model A → | under 30% | 30% to 60% | 60% and over | Row total |
|---|---|---|---|---|
| under 30% | 0 | 0 | 0 | 0 |
| 30% to 60% | 30 | 170 | 0 | 200 |
| 60% and over | 0 | 16 | 69 | 85 |
| Moved up / down / unchanged | 0 / 46 / 239 | |||
| Still event-free at 5 years (n = 121) — old model B ↓, new model A → | under 30% | 30% to 60% | 60% and over | Row total |
|---|---|---|---|---|
| under 30% | 0 | 0 | 0 | 0 |
| 30% to 60% | 38 | 76 | 0 | 114 |
| 60% and over | 0 | 2 | 5 | 7 |
| Moved up / down / unchanged | 0 / 40 / 81 | |||
Cut-points 50% / 60% (⚠ chosen after seeing the answer)
CHOSEN AFTER SEEING THE ANSWER. Included only to show that a cut-point pair exists that reverses the sign of the NRI on this same pair of models. It is not a clinically motivated split and must not be read as one.
| Had the event by 5 years (n = 285) — old model B ↓, new model A → | under 50% | 50% to 60% | 60% and over | Row total |
|---|---|---|---|---|
| under 50% | 128 | 0 | 0 | 128 |
| 50% to 60% | 51 | 21 | 0 | 72 |
| 60% and over | 0 | 16 | 69 | 85 |
| Moved up / down / unchanged | 0 / 67 / 218 | |||
| Still event-free at 5 years (n = 121) — old model B ↓, new model A → | under 50% | 50% to 60% | 60% and over | Row total |
|---|---|---|---|---|
| under 50% | 89 | 0 | 0 | 89 |
| 50% to 60% | 21 | 4 | 0 | 25 |
| 60% and over | 0 | 2 | 5 | 7 |
| Moved up / down / unchanged | 0 / 23 / 98 | |||
How the NRI is computed
Add the net movement of the two tables and you have the categorical NRI:
First term: for the event group, moving up is right, so it is up minus down. Second term: for the non-event group, moving down is right, so it is down minus up. Each term runs from −1 to +1, and the sum from −2 to +2.
figures/scripts/B5-10-brier-nri-idi.R| Cut-points | Event term | Non-event term | NRI | 95% CI | Where the cut-points came from |
|---|---|---|---|---|---|
| 10% / 20% | +0.000 | +0.000 | +0.000 | +0.000 to +0.000 | specified in advance |
| 15% / 30% | −0.105 | +0.314 | +0.209 | +0.095 to +0.307 | specified in advance |
| 30% / 60% | −0.161 | +0.331 | +0.169 | +0.038 to +0.304 | specified in advance |
| 50% / 60% | −0.235 | +0.190 | −0.045 | −0.148 to +0.102 | ⚠ chosen after seeing the answer |
Why the NRI comes out positive here
Notice something first: apart from the scheme in which nobody moved at all, every scheme has a negative event term and a large positive non-event term. That is not a coincidence. It is the direct consequence of model A underpredicting — it pushes almost every patient down a band.
Take the 15/30 scheme. The patients moved down are
30 who had the event and 38 who did not.
The first group was harmed (they were going to have the event, and their risk was lowered);
the second was helped. The NRI adds the two proportions directly:
- Event group: 30 out of 285, recorded as −0.105
- Non-event group: 38 out of 121, recorded as +0.314
- Sum: +0.209
The cut-points decide everything
One pair of models, one set of patients. Change the cut-points and the NRI runs from +0.000 to +0.209, and the last scheme is negative.
| Cut-points | NRI | Net benefit difference at the lower cut | At the upper cut |
|---|---|---|---|
| 10% / 20% | +0.000 | +0.00 | +0.00 |
| 15% / 30% | +0.209 | +0.00 | +6.12 |
| 30% / 60% | +0.169 | +6.12 | +3.04 |
| 50% / 60% ⚠ | −0.045 | −23.55 | +3.04 |
The net benefit difference is in net true positives per 1000 patients, model A minus model B, defined exactly as on the decision curve page.
Three things are worth taking one at a time:
The first scheme has an NRI of exactly +0.000, because nobody moved. Look back at its two tables: all 285 event patients and all 121 non-event patients land in the top band under both models. Both cut-points sit below this cohort’s risk distribution, so the scheme asked nothing at all — which is what cut-points carried over from another disease and another population, never checked against the local data, look like.
The last scheme reverses the sign of the NRI, and it was chosen after the answer was known. That
scheme carries a chosen_to_demonstrate flag in the stats file, and the marks in the two
tables above and the asterisk on the figure are rendered from it. It is left on the page rather than
tidied away because this choice is exactly what the page is criticising: when a paper reports one
scheme, the reader has no way of knowing how many were tried.
Within one scheme, the NRI and the net benefit need not share a sign. At the lower cut of the
50/60 scheme, model A’s net benefit is lower than model B’s by
23.55 per 1000, while at the upper cut it is 3.04 higher.
figures/scripts/B5-10-brier-nri-idi.R| Threshold | Net benefit, A | Net benefit, B | A minus B (per 1000) | Bootstrap 95% CI |
|---|---|---|---|---|
| 10% | 0.4537 | 0.4537 | +0.00 | +0.00 to +0.00 |
| 15% | 0.4216 | 0.4216 | +0.00 | +0.00 to +0.00 |
| 20% | 0.3854 | 0.3854 | +0.00 | +0.00 to +0.00 |
| 30% | 0.3038 | 0.2977 | +6.12 | −24.19 to +35.36 |
| 40% | 0.1925 | 0.1966 | −4.12 | −49.03 to +47.03 |
| 50% | 0.0982 | 0.1218 | −23.55 | −67.11 to +25.64 |
| 60% | 0.0647 | 0.0617 | +3.04 | −40.25 to +31.03 |
The IDI: no cut-points, but not free either
The IDI drops cut-points entirely and compares the mean predicted risk of the two outcome groups:
Equivalently, it is the increase in the discrimination slope — the mean prediction among events minus the mean prediction among non-events.
| Quantity | Model A (new) | Model B (old) |
|---|---|---|
| Mean predicted risk, events | 48.6% | 55.0% |
| Mean predicted risk, non-events | 36.5% | 46.9% |
| Discrimination slope | 0.1213 | 0.0813 |
The IDI is 0.0400, bootstrap 95% CI 0.0139 to 0.0563. That interval excludes zero, so it can be stated plainly: model A separates the event and non-event groups further than model B does.
The complete-case tables are biased: the IPCW-weighted comparison
Every reclassification table above holds integer counts, so none of them can hold the 280 patients censored early. Recomputing the same NRI and IDI with IPCW weights shows what that costs:
| Cut-points | NRI (complete cases) | NRI (IPCW weighted) | Difference |
|---|---|---|---|
| 10% / 20% | +0.000 | +0.000 | +0.000 |
| 15% / 30% | +0.209 | +0.200 | −0.009 |
| 30% / 60% | +0.169 | +0.166 | −0.004 |
| 50% / 60% | −0.045 | −0.047 | −0.002 |
The IDI behaves the same way: 0.0400 on complete cases against 0.0371 weighted.
The gaps are small, but they point the same way and they are not rounding: apart from the scheme that moved nobody at all, where both are exactly zero, the complete-case version is a little more optimistic every time. After weighting the effective sample sizes are 348.701 events and 337.107 non-events, both larger than the 285 and 121 of the integer tables — the missing patients are back, and they pull the answer down.
In this dataset the bias is too small to change any conclusion. Whether it stays small depends on how heavy the censoring is and on whether censoring is related to the predictions, and both are things a paper has to report rather than assume.
Running it yourself
library(survival)
data(cancer, package = "survival")
# The same cohort harmonisation and model fit as B5-05, spelled out so
# this block runs on its own.
H <- 5 * 365.25
rot <- rotterdam
rot$rfs_time <- pmin(rot$rtime, rot$dtime)
rot$rfs_event <- as.integer(rot$recur == 1 | rot$death == 1)
rot$size_mm <- c("<=20" = 15, "20-50" = 35, ">50" = 60)[as.character(rot$size)]
rot$grade3 <- as.integer(rot$grade >= 3)
ext <- gbsg
ext$rfs_time <- ext$rfstime; ext$rfs_event <- ext$status
ext$size_mm <- ext$size; ext$grade3 <- as.integer(ext$grade >= 3)
# The two candidate models, defined exactly as this page's figure script does.
BIG <- c("age", "meno", "size_mm", "grade3", "nodes", "log_pgr", "log_er", "hormon")
SMALL <- c("age", "size_mm", "nodes", "grade3")
rot$log_pgr <- log1p(rot$pgr); rot$log_er <- log1p(rot$er)
ext$log_pgr <- log1p(ext$pgr); ext$log_er <- log1p(ext$er)
keep <- c("rfs_time", "rfs_event", BIG)
dev <- rot[complete.cases(rot[, keep]), keep]
ext <- ext[complete.cases(ext[, keep]), keep]
# Breslow cumulative baseline hazard, written out: basehaz(centered = FALSE)
# does not match predict(type = "lp"), which is centred, and pairing them
# leaves every predicted risk short by a constant factor.
breslow_h0 <- function(time, event, lp, h = H) {
o <- order(time); time <- time[o]; event <- event[o]; r <- exp(lp[o])
at_risk <- rev(cumsum(rev(r)))
sum(1 / at_risk[event == 1 & time <= h])
}
form_of <- function(v) as.formula(paste("Surv(rfs_time, rfs_event) ~",
paste(v, collapse = " + ")))
fit_big <- coxph(form_of(BIG), data = dev)
fit_small <- coxph(form_of(SMALL), data = dev)
h0_big <- breslow_h0(dev$rfs_time, dev$rfs_event,
as.numeric(as.matrix(dev[, BIG]) %*% coef(fit_big)))
lp_big <- as.numeric(as.matrix(ext[, BIG]) %*% coef(fit_big))
lp_small <- as.numeric(as.matrix(ext[, SMALL]) %*% coef(fit_small))
# A: eight predictors, exactly as it left development. B: four predictors,
# then recalibrated to this cohort.
pred_A <- 1 - exp(-h0_big * exp(lp_big))
sl_B <- unname(coef(coxph(Surv(rfs_time, rfs_event) ~ lp_small, data = ext))[1])
h0_B <- breslow_h0(ext$rfs_time, ext$rfs_event, sl_B * lp_small)
pred_B <- 1 - exp(-h0_B * exp(sl_B * lp_small))
# 1. Censoring distribution: a Kaplan-Meier on the flipped event indicator
Gfit <- survfit(Surv(rfs_time, 1 - rfs_event) ~ 1, data = ext)
G_at <- function(u) summary(Gfit, times = u, extend = TRUE)$surv
G_minus <- function(u) sapply(u, function(x) # left limit, for the cases
summary(Gfit, times = max(0, x - 1e-8), extend = TRUE)$surv)
# 2. IPCW weights. Patients censored early get zero: not discarded, but
# represented by the patients like them who were followed.
ipcw <- function(d, t) {
is_case <- d$rfs_time <= t & d$rfs_event == 1
is_ctrl <- d$rfs_time > t
w <- numeric(nrow(d))
w[is_case] <- 1 / G_minus(d$rfs_time[is_case])
w[is_ctrl] <- 1 / G_at(t)
list(w = w, case = is_case, ctrl = is_ctrl)
}
# 3. The IPCW Brier score and its reference
brier_ipcw <- function(pred, t = H) {
z <- ipcw(ext, t)
contrib <- numeric(nrow(ext))
contrib[z$case] <- (1 - pred[z$case])^2
contrib[z$ctrl] <- pred[z$ctrl]^2
mean(z$w * contrib)
}
base <- 1 - summary(survfit(Surv(rfs_time, rfs_event) ~ 1, ext),
times = H)$surv
1 - brier_ipcw(pred_A) / brier_ipcw(rep(base, nrow(ext))) # scaled Brier
# 4. The complete-case version, for contrast. Only the `known` line differs.
known <- ext$rfs_time > H | (ext$rfs_time <= H & ext$rfs_event == 1)
y <- as.integer(ext$rfs_time <= H & ext$rfs_event == 1)
mean((y[known] - pred_A[known])^2) # the reference becomes the followed-up event rate,
# which is not this cohort's risk
# 5. The NRI: band, count the moves, add the two proportions
band <- function(p, cuts) cut(p, breaks = c(-Inf, cuts, Inf), labels = FALSE)
nri <- function(cuts) {
a <- band(pred_A, cuts); b <- band(pred_B, cuts)
ev <- known & y == 1; ne <- known & y == 0
(sum(a[ev] > b[ev]) - sum(a[ev] < b[ev])) / sum(ev) + # events: up minus down
(sum(a[ne] < b[ne]) - sum(a[ne] > b[ne])) / sum(ne) # non-events: down minus up
}
sapply(list(c(.10, .20), c(.15, .30), c(.30, .60), c(.50, .60)), nri)
# A new answer for every scheme -- a paper reporting one leaves the reader
# unable to tell how many were tried.
# 6. The IDI: no cut-points, but no absolute levels either
ev <- known & y == 1; ne <- known & y == 0
(mean(pred_A[ev]) - mean(pred_B[ev])) - (mean(pred_A[ne]) - mean(pred_B[ne]))Verified on R 4.6.0 with survival 3.8.6. No extra packages: the point of this page is to write the IPCW weights and the NRI sum out once, and remembering an argument name would not serve that.
import numpy as np
from lifelines import KaplanMeierFitter
# The same cohort harmonisation and model fit as B5-05, spelled out so
# this block runs on its own.
import pandas as pd
from lifelines import CoxPHFitter
RD = "https://vincentarelbundock.github.io/Rdatasets/csv/"
rot = pd.read_csv(RD + "survival/rotterdam.csv")
ext = pd.read_csv(RD + "survival/gbsg.csv")
rot["rfs_time"] = rot[["rtime", "dtime"]].min(axis=1)
rot["rfs_event"] = ((rot["recur"] == 1) | (rot["death"] == 1)).astype(int)
rot["size_mm"] = rot["size"].map({"<=20": 15, "20-50": 35, ">50": 60})
rot["grade3"] = (rot["grade"] >= 3).astype(int)
ext["rfs_time"] = ext["rfstime"]
ext["rfs_event"] = ext["status"]
ext["size_mm"] = ext["size"]
ext["grade3"] = (ext["grade"] >= 3).astype(int)
v = ["age", "size_mm", "nodes", "grade3"]
beta = (CoxPHFitter()
.fit(rot[v + ["rfs_time", "rfs_event"]], "rfs_time", "rfs_event")
.params_[v].to_numpy())
H = 5 * 365.25
G = KaplanMeierFitter().fit(ext["rfs_time"], 1 - ext["rfs_event"]) # censoring
def ipcw(t=H):
case = (ext["rfs_time"].values <= t) & (ext["rfs_event"].values == 1)
ctrl = ext["rfs_time"].values > t
w = np.zeros(len(ext)) # censored early stay at 0
w[case] = 1 / G.predict(ext["rfs_time"].values[case] - 1e-8).values
w[ctrl] = 1 / float(G.predict(t))
return w, case, ctrl
def brier_ipcw(pred, t=H):
w, case, ctrl = ipcw(t)
c = np.zeros(len(ext))
c[case] = (1 - pred[case]) ** 2
c[ctrl] = pred[ctrl] ** 2
return float(np.mean(w * c))
def nri(cuts, pred_new, pred_old, y, sel):
a, b = np.digitize(pred_new, cuts), np.digitize(pred_old, cuts)
ev, ne = sel & (y == 1), sel & (y == 0)
return ((a[ev] > b[ev]).sum() - (a[ev] < b[ev]).sum()) / ev.sum() + \
((a[ne] < b[ne]).sum() - (a[ne] > b[ne]).sum()) / ne.sum()The Python side estimates the censoring distribution with lifelines and does the rest in numpy. scikit-survival's brier_score() computes the IPCW Brier directly, but it hides the weights — and the weights are what this page is about.
Five things to ask when reading a paper
- Is there a reference next to the Brier score? Without one it has no scale. At minimum there should be a scaled Brier score, or the score of a model that hands everyone the prevalence.
- How was censoring handled? A Brier score reported on survival data with no mention of IPCW is usually complete-case scoring, and that can be wrong down to the sign.
- Where did the NRI cut-points come from? Were they specified in advance, clinically motivated, and is one scheme reported? Several schemes with only the best discussed looks identical on paper to one scheme with several tried.
- Are the two NRI components reported separately? The sum alone hides an event term that got worse, which is what three of the four schemes on this page do.
- Is a decision curve or net benefit reported alongside? The NRI is a unitless sum; net benefit has units and is tied to a trade-off someone had to state out loud.
Common misuses
| Misuse | Why it is wrong |
|---|---|
| Computing the Brier score directly on censored data | Complete-case scoring changes the estimand; on this page it reverses the sign of the conclusion |
| Reporting a Brier score with no reference | It has no scale, so the reader cannot tell how much the model adds |
| Reading an improved Brier score as improved calibration | It mixes discrimination and calibration; either one can be responsible |
| Choosing cut-points after seeing the NRI | The same pair of models runs from zero to twenty per cent and then turns negative |
| Reporting only the NRI total | It hides components pointing in opposite directions — events harmed, non-events helped |
| Reading the NRI as “how many more patients were treated correctly” | It sums two proportions with different denominators; that sum is nobody’s head count |
| Reporting an NRI or IDI on a poorly calibrated model | Shifting the whole scale is enough to manufacture a positive value with no change in ranking |
| Using the IDI in place of a calibration plot | It reads only the difference of two means, never whether the absolute levels are right |
| Drawing conclusions from IPCW estimates late in follow-up | When G(t) is small a handful of patients dominate the estimate |
| Comparing NRI values across studies | Cut-points, risk distribution and censoring all differ, so the numbers are not comparable |
What this page concludes
One pair of models, one set of patients, one 5-year horizon:
- The C-index favours model A
- The Brier and scaled Brier scores favour model B
- The NRI runs from +0.000 to +0.209 across four schemes, and the scheme chosen after the fact gives −0.045
- The net benefit difference changes sign 10 times across the threshold grid, with intervals that generally cross zero
None of these scores is wrong. They answer different questions, and most of those are not the question the clinic is asking. The clinical question is whether deciding by this model beats current practice, and that one is answered by the decision curve — which presupposes that the model’s scale is right in the first place, the business of the calibration page. The Brier score, the NRI and the IDI belong beside those two as supporting description, not in place of them.
Reproducing every number on this page
/opt/homebrew/bin/Rscript figures/scripts/B5-10-brier-nri-idi.RRead the figure
The answer comes from the same statistical output that produced this page's figures, not from a number typed in beside them.
Same predictions, same patients: under IPCW both models beat the reference, and under complete-case scoring both lose to it. Where does the reversal come from?
Show the answer and why
Correct answer: The complete-case reference Brier falls to 0.209, because it is the event rate among those who were followed up, which is not this cohort's five-year risk
The reversal is not in the models, it is in the reference. The complete-case reference is the event rate among those who were followed up, 0.702, so a roughly even-odds cohort is swapped for a high-risk one, the prediction that gives everyone the base rate and says nothing suddenly looks accurate, the reference Brier falls to 0.209, and both real models lose to it. Blaming a model does not work either: model A's complete-case Brier of 0.253 is indeed worse, but in the same table model B gets better - one goes up and one goes down, and the only cell they share is the reference. Nor is this extra noise: discarding four in ten patients estimates a different quantity, not the same one with wider error.
The two models' scaled Brier intervals overlap heavily. Does that establish that there is no difference in overall accuracy between them?
Show the answer and why
Correct answer: No, and what to look at is the interval of the difference itself - its upper bound is 0.024, its lower bound is negative, so it crosses zero
The two estimates come from the same patients, so their errors are almost perfectly correlated and whether the marginal intervals overlap has no fixed relationship to the interval of the difference - the difference has to be resampled inside the same bootstrap and read on its own. That interval has an upper bound of 0.024 and a negative lower bound, so it crosses zero, and the sentence to write is that no difference in overall accuracy between the two models was detected in this cohort. 0.006 is the lower bound of model A's own interval, which speaks to A beating the reference, a separate question from A against B. 0.091 is model B's point estimate, and taking one point falling inside another interval as evidence of equivalence is exactly the move to avoid: claiming equivalence needs a non-inferiority design and a margin set in advance.
The same pair of models and the same patients give four different NRIs under four sets of cut-points. What does that say about a paper reporting one set?
Show the answer and why
Correct answer: A set of cut-points exists that turns the NRI into -0.045, and it was chosen after seeing the answer
The sign and size of an NRI are as much a property of the cut-points as of the models. The set at 0.000 does not show the models are equivalent; it shows nobody moved at all - both cut-points sit below this cohort's risk distribution, both models put every patient into the top band, and that set asked nothing. A set of cut-points carried over from another disease without ever being checked against the local data looks exactly like this. 0.209 is the largest of the four, and reporting the largest is precisely the practice this page is criticising. And -0.045 is the set that flips the sign; it carries a chosen-after-seeing-the-answer flag in the statistical output, and it is left on the page so the reader can see how far that kind of choosing can go.
Under the 15% and 30% cut-points, model A moves 30 patients who did have an event (285 in that group) and 38 who did not (121 in that group) down one band, and the NRI is reported as 0.209. Why does that number read like a substantial improvement?
Show the answer and why
Correct answer: The non-event term is 0.314, and its denominator is under half the event group's, so each such patient carries more than twice the weight
The NRI adds two proportions with different denominators. The event group holds 285 patients and the non-event group only 121, more than a two-to-one ratio, so the non-event term of 0.314 is 38 over a small denominator while the event term of -0.105 is 30 over a large one. Converted back to heads, this set of cut-points takes 30 patients who did have an event and lowers their risk, in exchange for lowering the risk of 38 who did not - and it is reported as 0.209. So far fewer harmed than helped is reading a proportion as a head count. Both positive so it is robust does not hold either: 0.169 and 0.209 being positive guarantees nothing about a third set, and this page has one that is negative. How many false alarms you will trade for one missed case is a clinical trade to be stated out loud - a threshold probability forces you to state it, and the NRI does not.
The IDI is 0.0400 with a bootstrap 95% interval of 0.0139 to 0.0563, which does not cross zero. Does that establish that model A is better than model B?
Show the answer and why
Correct answer: Only that A separates the two groups more than B does. A's mean prediction in the non-event group moves -0.1044, the event group moves the same way, and the IDI looks only at the difference
The IDI does not depend on cut-points, but it is still a difference of two means, so a shift of the whole scale passes straight through it. Model A gives a lower mean risk in both groups - the non-event group moves -0.1044 and the event group moves the same way - and the IDI reads only the difference between those two shifts, indifferent to both absolute levels having drifted off the real risk. So an interval clear of zero supports one sentence only: A separates events from non-events more than B does. 0.4859 is A's mean prediction in the event group, and it is lower than B's rather than higher, which is what under-prediction looks like. 0.1213 is A's discrimination slope; a discrimination slope measures separation, calibration is a different thing, and model A on this page is the textbook case of a positive IDI with poor calibration.
The last row of the Brier curve is at six years, and its scores look respectable. Why should it not be read?
Show the answer and why
Correct answer: G(t) at six years is down to 0.125, so every survivor's weight is blown up around eightfold and the estimate is decided by very few people
The IPCW weight is 1 over G(t), and the smaller G(t) is the harder it magnifies. At six years G(t) is down to 0.125 with only a few dozen left in the risk set, every remaining person is weighted around eightfold, and the wobble is far larger than it looks. At five years 0.359 still holds up, and the rule of thumb is to stop reading once it falls below 0.2 - 0.125 is already past that line, so saying six years has not yet reached it has it exactly backwards. 0.222 is model A's Brier in that row; it being below the five-year value is not the model becoming accurate, it is a small heavily weighted remnant of the cohort, and a score carried by thirty-odd people has lost the basis for any comparison.
The NRI under one set of cut-points is 0.209, while the net-benefit difference between the same two models changes sign 10 times across the threshold grid. How should the conclusion be written?
Show the answer and why
Correct answer: The bootstrap interval for that cell runs from -67.1 up into positive values, crossing zero - so what to write is that no net-benefit difference was detected
Almost every row of these differences has an interval crossing zero, and the fifty per cent cell running from -67.1 to 25.6 is one of them, so the sentence to write is that at most thresholds no net-benefit difference was detected, and the direction of the difference itself keeps changing with the threshold. Picking the most extreme cell is the same disease in another form: -23.5 is the most negative of the seven cut-points reported, and reporting it is methodologically no different from the NRI picking its prettiest set of cut-points - and the full threshold grid runs further down still, so even being the most extreme is something that was selected. Nor does a positive upper bound make the models equally good - an interval crossing zero means these data cannot tell them apart, and claiming equivalence needs a non-inferiority design and a margin set in advance.
Chapters that use this method
Sources and licences
This page is original writing