The log-rank p-value tells you whether two survival curves differ; the hazard ratio summarises the difference as a single ratio – but only if the hazards stay proportional. When curves cross, converge, or separate late, a reviewer increasingly asks for an absolute effect measure in the units of the study: months of life gained, or the survival probability at a clinically meaningful time. This article covers the three survminer draws for you: restricted mean survival time, milestone survival, and landmark analysis.
library(survminer)
library(survival)
# Readable arm names for every figure.
lung$sex <- factor(lung$sex, labels = c("Male", "Female"))
fit <- survfit(Surv(time, status) ~ sex, data = lung)A Cox hazard ratio assumes the effect of a group is a constant multiplier of the hazard over all follow-up. Under non-proportional hazards that single number averages together periods where one arm does better and periods where it does worse, and it is no longer a faithful summary. The absolute measures below make no proportional-hazards assumption – they read the difference directly off the Kaplan-Meier curves.
The restricted mean survival time up to a horizon τ is the area under the survival curve from 0 to τ – the average survival time a subject can expect over the first τ units of follow-up. The difference in RMST between two arms is an absolute benefit measured in time units (here, days), and unlike the hazard ratio it needs no proportional-hazards assumption.
ggrmst() shades the area between the two curves up to τ
and annotates the difference with its confidence interval and
p-value:
Here the curves do not cross, so the shaded region equals the ΔRMST: over the first 500 days, women survive on average about 82 days longer than men. Because RMST is an area, it stays well defined even when the curves cross – exactly the case where a hazard ratio struggles. (When the curves do cross, the shaded band shows the total separation and the reported difference, which nets the two directions, can be smaller than the band.)
ggrmst_difference() returns the numbers behind the
figure: the per-arm RMST with its standard error and confidence
interval, and the between-arm difference with its confidence interval
and p-value.
ggrmst_difference(fit, data = lung, tau = 500)
#> strata rmst se lower upper tau p.value
#> 1 sex=Male 278.76323 14.35456 250.62881 306.8976 500 NA
#> 2 sex=Female 360.91095 16.80102 327.98156 393.8403 500 NA
#> 3 sex=Female - sex=Male 82.14772 22.09813 38.83617 125.4593 500 0.0002012794Choosing τ. RMST is only defined up to a horizon
where all arms still have subjects at risk, so τ must lie within the
follow-up of every arm. If you omit tau, survminer uses the
largest commonly admissible time; if you pass a τ beyond an arm’s
support, it stops with a clear message rather than extrapolating. Report
τ alongside the estimate – “the RMST difference at τ = 500 days” – since
the value depends on it.
A milestone is the survival probability at a fixed clinical timepoint
– one-year survival, two-year survival – and the quantity a reader often
wants is the between-arm difference at that time, with
a confidence interval. ggmilestone() marks each timepoint
and reports that difference:
ggmilestone(fit, data = lung, milestone.times = c(180, 360, 540),
palette = "jco", legend.labs = c("Male", "Female"))Each dashed line is a milestone; the caption gives, per timepoint, the survival in each arm and their difference with a 95% confidence interval and p-value. The difference is the second arm minus the first – Female − Male here – so the positive values mean women do better at every timepoint, in the same direction as the positive Female − Male RMST above. Every difference row is labelled with its contrast, so the direction never has to be inferred. The full table is also attached to the plot:
p <- ggmilestone(fit, data = lung, milestone.times = c(180, 360, 540))
attr(p$plot, "milestone.table")
#> strata time surv se lower upper
#> 1 sex=Male 180 0.6444650 0.04078643 0.56928419 0.7295743
#> 2 sex=Male 360 0.3552929 0.04372995 0.27913826 0.4522240
#> 3 sex=Male 540 0.1897299 0.03851382 0.12745240 0.2824384
#> 4 sex=Female 180 0.8424017 0.03868096 0.76989981 0.9217311
#> 5 sex=Female 360 0.5604284 0.05856414 0.45663639 0.6878120
#> 6 sex=Female 540 0.3677781 0.06303285 0.26284513 0.5146024
#> 7 sex=Female - sex=Male 180 0.1979367 0.05621165 0.08776390 0.3081095
#> 8 sex=Female - sex=Male 360 0.2051355 0.07308945 0.06188285 0.3483882
#> 9 sex=Female - sex=Male 540 0.1780482 0.07386782 0.03326992 0.3228265
#> p.value
#> 1 NA
#> 2 NA
#> 3 NA
#> 4 NA
#> 5 NA
#> 6 NA
#> 7 0.0004294757
#> 8 0.0050061644
#> 9 0.0159367005A milestone that falls beyond an arm’s follow-up is returned as
NA with a warning rather than silently dropped.
When the groups you compare are defined by something that happens after baseline – responded to treatment by week 8, received a transplant, achieved remission – a naive Kaplan-Meier from time zero is biased. To be classified in the “responder” group a subject must first survive long enough to respond, so that group is credited with “immortal” time it was guaranteed to survive. This is immortal-time (guarantee-time) bias, and it makes the post-baseline group look artificially better.
The standard remedy is a landmark analysis: pick a
landmark time L after which group membership is fixed, keep
only subjects still event-free and at risk at L, and reset
the clock to time - L. gglandmark() does
exactly this:
lung has no post-baseline grouping variable, so we apply
the landmark to sex only to show the mechanics – being a
fixed baseline covariate, sex does not itself
suffer immortal-time bias; in practice the grouping would be the
post-baseline event (responder, transplant, remission).
lm <- gglandmark(fit, data = lung, landmark.time = 180, palette = "jco",
legend.labs = c("Male", "Female"))
# the function's long auto-subtitle is replaced by a short one; the details
# are in the text below
lm$plot <- lm$plot + ggplot2::labs(subtitle = "Landmark at day 180")
lmThe curves now start at L (shown as time 0 on a “Time
since landmark” axis), restricted to the subjects still at risk then –
at day 180, 159 subjects remain at risk, and one subject with an event
exactly at L is excluded so each curve correctly begins at
S = 1. Any log-rank p-value or risk table you request is computed on
this landmark cohort.
Use a landmark analysis whenever the grouping variable is measured
after follow-up begins; choose L from clinical reasoning
(when the classifying event has typically occurred), not from the
data.
ggcoxnph()), the
diagnostic that tells you when to reach for the absolute
measures above.