ExpertIndependently reviewed, not yet spot-checked by a human

Time-dependent ROC and cumulative-dynamic AUC(t)

Harrell's C compresses discrimination at every horizon into a single number, but clinical decisions happen at particular horizons — what you tell a patient is their five-year risk. This page unfolds one breast cancer Cox model's discrimination into a curve over time: how a survival dataset is split into cases and controls at a horizon t, why censored patients force inverse probability of censoring weighting, why AUC(t) sits systematically above Harrell's C, and how to read the curve when it turns out flat — the flatness is itself the conclusion, because you cannot know in advance that it will be flat.

One C-statistic cannot show you what it left out

The prediction model chapter and the three pages under it — internal validation, external validation and calibration — all rest on the same Cox model, and the discrimination column carries exactly one number throughout: Harrell’s C.

That number is built like this. Take every pair of patients whose event times can be ordered, check whether the model put the one who failed first ahead of the other, and average over all such pairs. The trouble is in the word all. Discrimination at two years, discrimination at five years, and a pair of patients who both survived into the sixth year before relapsing are all flattened into the same average.

Clinical decisions do not happen at the average over comparable pairs. What you tell a woman who has just had surgery is her probability of recurrence or death within five years, or within two. That is a question about a particular horizon. When a reviewer asks what the five-year AUC is, they are asking the same thing.

This page unfolds that one number into a curve.

AUC(t) asks a question about one horizon

Pin a horizon tt — say five years. Once it is pinned, every patient has one of two identities:

  • case — the event (here recurrence or death, whichever came first) has already happened by tt
  • control — still event-free at tt

With cases and controls you have an ordinary binary classification problem, and therefore the whole apparatus of the ROC curve and AUC. This particular version is called the cumulative/dynamic AUC: cases have accumulated up to tt (cumulative), controls are those event-free at that instant (dynamic). As a probability:

AUC(t)=P(r^i>r^j    Tit,  Tj>t)\mathrm{AUC}(t) = P\bigl(\hat r_i > \hat r_j \;\big|\; T_i \le t,\; T_j > t\bigr)

where r^\hat r is the model’s risk score — for a Cox model, the linear predictor. This is the same sentence as the probability interpretation on that page. The one difference is that who counts as a case now depends on tt: the same patient can be a control at two years and a case at five.

The model is the one from the external validation page

Not introducing a new dataset is deliberate. The whole argument here is one curve versus one number, and the comparison is only honest if the number is the same Harrell’s C the reader already met on the external validation page.

Development is survival::rotterdam (2982 patients, 1713 recurrence-free survival events); validation is survival::gbsg (686 patients, 299 events, longest follow-up 7.28 years). The four covariates are:

  • age -- age at surgery, years, continuous
  • size_mm -- tumour size in millimetres; rotterdam's bands (<=20, 20-50, >50) mapped to their midpoints 15, 35, 60 to match gbsg's millimetres
  • nodes -- number of positive lymph nodes, continuous
  • grade3 -- 1 if tumour grade >= 3, else 0; collapsed because the two cohorts do not grade with the same detail

Unfolding discrimination over time

A line chart. The horizontal axis is the prediction horizon, from 1.0 to 6.0 years. The vertical axis is the time-dependent AUC(t) and spans the full range 0.50 to 1.00 on purpose, rather than being cropped to the narrow band the curve actually occupies, because cropping would turn a flat curve into a dramatic one. A dark blue line with round markers gives AUC(t) at 11 horizons; it drifts gently between 0.669 and 0.711, lowest at 4.5 years and highest at 6.0 years. A pale blue 95% confidence band wraps the line, wider at both ends than in the middle and widest at the right (0.630 to 0.792 at 6.0 years). A red dashed horizontal line marks the same model's Harrell's C at 0.642; the blue line lies above it at every horizon, though the confidence band still covers it at most of them. A brown dotted horizontal line sits at 0.50, labelled at the lower right as a coin flip, meaning no ranking ability at all. A three-item legend sits at the top right, and a subtitle under the title states the range and where the extremes fall.
Discrimination of one model in an external cohort, estimated horizon by horizon with IPCW. The red dashed line is that same model's Harrell's C — one number, spanning every t. Drawing the axis all the way from 0.50 to 1.00 is deliberate: cropping the axis makes every curve look eventful.Plotting script figures/scripts/B5-08-time-dependent-auc.R

Why the whole curve sits above Harrell’s C

Most readers’ first reaction to this figure is to ask which of the two numbers is wrong. Of the 11 horizons on the grid, 11 have an AUC(t) point estimate above the same model’s Harrell’s C (0.642); even the lowest point on the curve, at 4.5 years, is 0.028 above it, and the largest gap anywhere is 0.069.

Nothing is wrong. The two quantities are not estimating the same thing.

  • AUC(t) asks one question only: does a patient who failed before tt have a higher score than a patient still event-free at tt? It never compares two patients who both failed before tt — at that horizon they belong to the same class.
  • Harrell’s C does compare them. It averages over every orderable pair, including pairs like one relapse in year five and another in year six. Ranking two patients whose prognosis is equally poor and whose event times differ by months is close to a coin flip, and in a long-follow-up cohort there are many such pairs, pulling the average down towards 0.5.

There is a second source: Harrell’s C depends on the censoring distribution, because which pairs count as comparable depends on who was censored when, whereas the AUC(t) here has already corrected for censoring through IPCW.

A consistent direction is not the same as a detectable gap at each horizon, though: at 8 of the 11 horizons the 95% confidence interval still covers Harrell’s C. The defensible sentence is therefore that AUC(t) sits systematically above Harrell’s C — not that every horizon differs from it by a statistically distinguishable amount, which this data cannot support.

The flatness is the conclusion

Now face the shape of this curve honestly.

For this model, in this external cohort, the 11 horizons give AUC(t) values between 0.669 (at 4.5 years) and 0.711 (at 6.0 years) — a range of 0.042 and a standard deviation of 0.014.

Quoting the range on its own overstates how much the curve moves. The 95% confidence intervals at the two extremes overlap (0.620–0.719 against 0.630–0.792), which means the visible wobble lies inside the curve’s own uncertainty: no variation of AUC(t) over time was detected in this data. That is not the same statement as AUC(t) being constant. None of the 11 intervals is narrow, so a moderate amount of variation cannot be ruled out either.

The three horizons an oncology paper would actually quote are closer still:

HorizonAUC(t)95% CIStill at riskCumulative events
2.0 years0.6740.624–0.723458165
3.0 years0.6940.649–0.739331224
5.0 years0.6750.620–0.731121285

Those three differ by 0.020.

Why the confidence band flares at the right

Laying the whole grid out shows where the range comes from:

HorizonAUC(t)95% CICI widthStill at riskCumulative events
1.0 years0.6840.607–0.7610.15460256
1.5 years0.6870.632–0.7420.110530117
2.0 years0.6740.624–0.7230.098458165
2.5 years0.6810.634–0.7270.093383205
3.0 years0.6940.649–0.7390.090331224
3.5 years0.6890.644–0.7350.091277245
4.0 years0.6730.626–0.7210.095228263
4.5 years0.6690.620–0.7190.099183274
5.0 years0.6750.620–0.7310.111121285
5.5 years0.7080.647–0.7700.12374290
6.0 years0.7110.630–0.7920.16236296

The last two columns explain the flare on the right of the figure. The further out the horizon, the fewer patients remain at risk — from 602 down to 36 — so there are fewer patients available on the control side, while the IPCW weights simultaneously grow larger. Both make the estimate less stable. The widest interval on the whole curve is at 6.0 years, spanning 0.162, several times the range of 0.042 quoted above.

And 6.0 years is exactly where the global maximum sits. A good part of that range is therefore noise at a single point rather than the model genuinely improving late. The first habit to build when reading an AUC(t) curve is to check how many patients remain at that horizon before reading anything into its height.

What the ROC curve at one horizon looks like

AUC(t) is an area, and underneath the area there is a curve. Pinned at 5.0 years:

A square ROC plot with the horizon in the main title: t = 5.0 years. The subtitle spells out the two classes — cases are patients who had a recurrence or died by 5.0 years, controls are those still event-free at 5.0 years. The horizontal axis is 1 minus specificity, the false positive rate, and the vertical axis is sensitivity; both run from 0 to 1. A dark blue step curve leaves the bottom-left corner and climbs steeply, reaching close to four-tenths sensitivity while the false positive rate is still low, then levels off into a short plateau across the middle before rising again to meet the top-right corner at 1, 1. The area between the curve and the horizontal axis is filled pale blue; that area is AUC(t). A grey dashed diagonal is the chance line, AUC 0.5. Three annotations sit at the lower right: AUC(5.0 y) = 0.675 in red, then 95% CI 0.620 to 0.731, then 121 still at risk at 5.0 years, with the chance-line legend below them.
The horizon goes in the main title rather than only in the caption: a time-dependent ROC with no time attached is the most common way this plot gets misread. The steps are fine because 285 cumulative events support this slice, and the 121 patients still at risk make up the control side.Plotting script figures/scripts/B5-08-time-dependent-auc.R

The plot looks identical to an ordinary ROC curve, but three things about the data underneath it differ, and all three change how it should be read:

Ordinary ROCTime-dependent ROC
Who is a caseKnown and fixedDepends on t; the same patient can switch sides between horizons
Patients with unknown outcomeUsually excluded, and the count should be statedKept, carried by IPCW weights
Is one plot enoughYes — that is all the information there isNo; every t has its own

Running it yourself

library(survival)
library(timeROC)
data(cancer, package = "survival")

# Cohort harmonisation, character for character as on the external validation page
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)

v   <- c("age", "size_mm", "nodes", "grade3")
fit <- coxph(Surv(rfs_time, rfs_event) ~ age + size_mm + nodes + grade3, data = rot)

# Risk score as X %*% beta rather than the centred predict(), matching B5-05
lp <- as.numeric(as.matrix(ext[, v]) %*% coef(fit))

# One number
concordance(Surv(ext$rfs_time, ext$rfs_event) ~ lp, reverse = TRUE)

# One curve. weighting = "marginal" is the Kaplan-Meier censoring weight (IPCW)
grid <- seq(1, 6, by = 0.5) * 365.25
roc  <- timeROC(T = ext$rfs_time, delta = ext$rfs_event, marker = lp,
                cause = 1, times = grid, weighting = "marginal", iid = TRUE)

auc <- as.numeric(roc$AUC)
se  <- as.numeric(roc$inference$vect_sd_1)   # only present with iid = TRUE
cbind(years = grid / 365.25, auc = auc,
      lcl = auc - 1.96 * se, ucl = auc + 1.96 * se)

# How many patients remain at each horizon -- read this before reading the curve
sapply(grid, function(t) sum(ext$rfs_time >= t))

# ROC coordinates at one horizon, to plot yourself
k <- which(roc$times == 5 * 365.25)
plot(roc$FP[, k], roc$TP[, k], type = "l", xlim = c(0, 1), ylim = c(0, 1))
abline(0, 1, lty = 2)

Verified with R 4.6.0, survival 3.8.6 and timeROC 0.4.1. The iid = TRUE argument is not optional here: without it timeROC returns no standard errors, and there is no confidence band to draw. cause = 1 refers to the event code, not to the first event type in a competing risks analysis (for AUC(t) under competing risks, delta has to be coded with several event types).

How this page connects

  • How Harrell’s C is computed, and why apparent performance is always too high — see internal validation. This page only spreads the same quantity along the time axis; none of the optimism problem goes away. Computing AUC(t) in the development data is just as optimistic.
  • Where the model, the cohorts and that Harrell’s C come from — see external validation, which also shows that three validation designs cannot be told apart on discrimination at all — what separates them is calibration.
  • Good discrimination does not mean the probabilities are right — see calibration. AUC(t), like Harrell’s C, only sees the ranking: multiply everyone’s predicted risk by a constant and this page’s curve does not move a digit, while every predicted probability is now wrong. A handsome AUC(t) curve cannot vouch for calibration.
  • The ordinary ROC and AUC — see ROC curves and AUC. The probability interpretation is the same sentence; what differs is whether case and control status changes with time.
  • What censoring is, and what independent censoring assumes — see censoring and follow-up time. IPCW puts that assumption to work inside the estimator; it does not remove it.
  • The clinical setting for the whole chapter — see prediction model studies.

Four questions to ask of an AUC(t) in a paper

  1. Which horizons, and why those? Reporting the horizon whose AUC came out prettiest is the same problem as picking a threshold after the fact (see choosing a cut-off). Horizons should be set by the clinical question.
  2. How many patients remain at risk there? Papers rarely say, yet it determines the width of the interval. Reading a conclusion off the rightmost point without a numbers-at-risk row is the most common misreading of this figure.
  3. How was censoring handled? Look for IPCW or an equivalent weighting. A methods section saying only that ROC analysis was performed usually means censored patients were counted as controls or deleted.
  4. Was calibration reported alongside? An external validation reporting only AUC(t) still covers only one of the three things a validation study has to report.

Common misuses

MisuseWhy it is wrong
Reporting a time-dependent AUC without stating the horizonAUC(t) is undefined without t; nobody can reproduce or compare it
Counting censored patients as controlsAssumes everyone lost to follow-up stayed well, inflating discrimination
Deleting censored patients insteadThe remainder no longer represents the cohort, and dropout tracks prognosis
Choosing the horizon with the highest AUC after seeing themSame problem as picking a cut-off after the fact; the reported value is no longer unbiased
Drawing conclusions from the far end of the follow-upFew patients remain at risk there; that stretch is noise, not signal
Reading a flat curve as proof that AUC(t) does not vary with timeOnly that no variation was detected; the intervals are not narrow enough to rule out moderate variation
Deciding the curve is unnecessary because one model’s was flatFlatness is something the analysis reveals, not something to assume beforehand
Cross-checking AUC(t) against Harrell’s C as if they were the same numberThey estimate different quantities; disagreement is expected
Cropping the vertical axis to the range the curve occupiesAny flat curve then looks eventful
Skipping calibration because the AUC(t) curve looks goodDiscrimination is completely immune to shifting or rescaling the risk scale
Computing AUC(t) in the development data and calling it validationThe optimism is still there; changing the metric does not remove it

Reproducing every number on this page

/opt/homebrew/bin/Rscript figures/scripts/B5-08-time-dependent-auc.R

Read the figure

The answer comes from the same statistical output that produced this page's figures, not from a number typed in beside them.

The whole AUC(t) curve sits above the same model's Harrell's C, with a largest gap of 0.069. Which reading is right?

Show the answer and why

Correct answer: Harrell's C is 0.642, and it compares two patients who both had their event, which AUC(t) never does - the two are not the same quantity

AUC(t) asks one thing: does a patient who had the event before the horizon score higher than one who was still event-free at it. It never compares two patients who have both already had the event, and Harrell's C does - those pairs are close to a coin flip, they are numerous in a long-followed cohort, and they pull the average down. So the gap between 0.642 and the curve is not an arithmetic error, it is two different quantities; Harrell's C is also affected by the censoring distribution, while this page's AUC(t) has already corrected for censoring with IPCW. As for the upper bound 0.675, most of the curve runs above it, but sitting above it does not mean the two are separable: at several grid points the AUC(t) interval still contains Harrell's C, so the sentence to write is that AUC(t) runs systematically higher, not that every point differs distinguishably. 0.686 is the curve's own mean.

Across the grid, AUC(t) has a range of only 0.042 and a standard deviation of 0.014. What does that mean?

Show the answer and why

Correct answer: The widest confidence interval on the same curve is 0.162 on its own, several times the range - the curve's wobble sits inside its own uncertainty

The range is a difference between two point estimates, and every point estimate carries its own uncertainty. The widest interval on this curve is 0.162, several times the range of 0.042, and the intervals at the minimum and the maximum overlap, so what to write is that no change in AUC(t) over time was detected in these data - not that it moves but only a little, which treats an undetected difference as a measured one. The maximum 0.711 does fall at the last time point, but that point has the widest interval on the curve, so using it to claim better long-term ranking is taking noise for signal. Equally, none of this means AUC(t) certainly does not move: no interval here is narrow, and a moderate change cannot be ruled out either. 0.669 is the minimum.

The widest confidence interval on the curve, 0.162, is at the last time point, and so is the global maximum. What should you do first when reading this figure?

Show the answer and why

Correct answer: Check how many people are left at that time point - only 36 remain at the last one - because a good part of that maximum is noise

The later the time, the fewer people remain in the risk set - from 602 down to 36 - so there are fewer controls to pair with, while the IPCW weights grow at the same time, and both make the estimate less stable. That is why the interval at the last time point is 0.162 wide, several times the range of the whole curve, and why the global maximum happens to land there. The cumulative event count of 296 is indeed the largest on the curve, but AUC(t) pairs an event with someone still event-free, and with only 36 left on the control side no number of events can make up for it. The first habit in reading an AUC(t) curve is to check how many people are left at a time point before reading its height.

The AUC(t) grid stops at six years and is not drawn out to eight. Why?

Show the answer and why

Correct answer: The longest follow-up in this external cohort is 7.28 years, so an eight-year horizon is not observable in these patients at all

The grid stops at six years because anything beyond it is extrapolation rather than estimation: the longest follow-up in this external cohort is 7.28 years, so an eight-year horizon is not observable in these patients. 4.50 years is the median follow-up, and a median cannot set the upper limit - it has not even seen half the cohort out, while the limit is a question about whether anyone is left in the data. 0.16 is the interval width at six years; pushing an IPCW estimate past the data does not raise an error, it just hands you a number whose confidence interval is too wide to argue with, which is exactly what this family of pages exists to prevent.

Fix the time point at five years and draw the ROC curve, then look at the area under it. What is that area saying?

Show the answer and why

Correct answer: 0.675 is the probability that a patient with an event by five years scores higher than one still event-free at five years

All three options hold the same number; what is being chosen is what it means. It is a probability: pick at random one patient who had the event before the horizon and one who was still event-free at it, and the chance the model scores the first higher is 0.675. Reading it as accuracy is wrong - accuracy needs a cut-off first, so that every patient is called positive or negative, whereas an ROC curve is what you get by drawing every cut-off at once, and talking about a share classified correctly before any cut-off has been chosen asks a question this curve does not answer. Reading it as calibration is wrong too: the area sees only the ordering, so halve every predicted risk and nothing about who ranks above whom changes, the area does not move at all, and the calibration has collapsed. Ranking and calibration are two questions, one model can rank tolerably and still be badly calibrated, and that is exactly what the calibration page measured on this same model.

Sources and licences

This page is original writing

Report a content problem

The statistics on this site are written by AI and reviewed by AI; a human only spot-checks. What you can see may be what we cannot.

The more specific, the more fixable — e.g. which sentence disagrees with which textbook or paper.

Needed only if you want a reply; reports without it are still read.

Sent along with your report

These are attached automatically. You can drop any of them.