Skip to content

Cox Forest Plot

A Kaplan-Meier log-rank tells you two arms differ. It cannot tell you the treatment effect once you account for the fact that one arm happened to be younger, or sicker at baseline. That is the Cox model’s job, and the forest plot is how you show it: one hazard ratio per covariate, each with a confidence interval, all on a log axis with the null hazard ratio at 1.

This page fits a Cox model on the same trial data as the Kaplan-Meier page, adjusting the treatment effect for age, sex, and stage. R uses survminer::ggforest, Python uses lifelines. Both run in the pinned pubplot container and return the same hazard ratios.

survival_trial.csv again: time, event, arm, plus the covariates age, sex, and stage. The model is Surv(time, event) ~ arm + age + sex + stage.

Read a forest plot by the confidence interval, not the point. A hazard ratio whose interval crosses 1 is compatible with no effect. Here the treated arm sits at 0.54 with an interval from 0.34 to 0.88, clear of 1, so the benefit survives adjustment.

library(survival)
library(survminer)
for (col in c("arm", "sex", "stage")) df[[col]] <- factor(df[[col]])
cox <- coxph(Surv(time, event) ~ arm + age + sex + stage, data = df)
ggforest(cox, data = df) # rows, reference levels, HR + CI, and global stats

Cox forest plot, treated hazard ratio 0.54 clear of the null, other covariates spanning one (R)

Both fits return the same ratios: treated 0.54 (0.34 to 0.88), age 1.00, male 1.21, and the two higher stages near 1. The R panel is fuller. ggforest prints the reference level for each factor, the group sizes, and the global model statistics below the plot. The lifelines version shows the estimated terms only, which is tighter but leaves the reader to infer the reference. When you build the Python forest by hand, add the reference rows if the journal expects them.

  • Use a log x-axis. A hazard ratio of 0.5 and one of 2.0 are the same size effect in opposite directions; only a log scale places them symmetrically around 1.
  • Draw the null line at 1 and let the reader judge each interval against it.
  • Label with the hazard ratio and its interval, not the p-value alone. The interval shows both the effect and its precision.
  • State the reference level for every categorical covariate. A hazard ratio is meaningless without the group it compares against.

The runnable scripts and fixture are in the companion code repo under guides/figures/cox-forest/.