A forest plot
shows a point estimate and its confidence interval for each of several
groups or model terms on one axis, against a reference line – the
standard way to report odds/hazard/risk ratios, regression coefficients,
or a meta-analysis. ggestimates() draws one from a data
frame of pre-computed estimates: you supply the
numbers, it draws a publication-ready figure with an optional right-hand
column of the estimate and interval.
Because it takes ready-made values, ggestimates() fits
any source – a model you fitted, a table from a paper, a
meta-analysis – with no modeling dependency of its own.
The simplest case: a data frame with an estimate and its confidence
bounds. For ratios (odds/hazard/risk ratios) set
log.scale = TRUE so the axis is symmetric around the null,
and put the reference line at 1.
or <- data.frame(
term = c("Age (per year)", "Male sex", "BMI", "Current smoker", "Treatment"),
estimate = c(1.03, 1.45, 0.98, 2.10, 0.62),
conf.low = c(0.99, 1.05, 0.94, 1.40, 0.45),
conf.high = c(1.07, 2.00, 1.02, 3.15, 0.85)
)
ggestimates(or, label = "term", ref.line = 1, log.scale = TRUE,
xlab = "Odds ratio (95% CI)")Each row shows the point estimate (filled square) and its 95% interval; the right-hand column prints the same numbers, and the dashed line marks the null (no effect). An interval that crosses the line is not significant at that level.
The usual workflow: fit a model, pull out the coefficients and
confidence intervals, and hand them to ggestimates(). Here
we model survival on the Titanic with logistic regression and plot the
odds ratios – all in base R.
data("titanic.raw", package = "datarium")
fit <- glm(Survived ~ Class + Sex + Age, data = titanic.raw,
family = binomial)
# Odds ratios = exp(coefficients); 95% CI = exp(confint)
est <- data.frame(
term = c("2nd class", "3rd class", "Crew", "Female", "Adult"),
estimate = exp(coef(fit))[-1],
conf.low = exp(confint.default(fit))[-1, 1],
conf.high = exp(confint.default(fit))[-1, 2]
)
ggestimates(est, label = "term", ref.line = 1, log.scale = TRUE,
sort = "estimate", xlab = "Odds ratio of survival (95% CI)",
title = "Titanic survival")Read against the reference groups (1st class, male, child): women had
far higher odds of survival, while 3rd-class passengers and adults had
lower odds. The same recipe works for any model – use
exp(coef)/exp(confint) for a Cox or logistic
model, or the raw coefficients (below) for a linear one.
For estimates on the original (additive) scale –
mean differences, linear-model coefficients – keep
log.scale = FALSE (the default) and put the reference line
at 0.
md <- data.frame(
group = c("Low dose", "Medium dose", "High dose"),
estimate = c(1.8, 3.6, 5.9),
conf.low = c(0.4, 2.1, 4.0),
conf.high = c(3.2, 5.1, 7.8)
)
ggestimates(md, label = "group", color = "group", palette = "jco",
ref.line = 0, xlab = "Mean difference vs control (95% CI)")Order the rows by their estimate with sort = "estimate",
add zebra banding to guide the eye across a wide
figure, and tune the right-hand estimate column with
ci.text.title and digits:
ggestimates(or, label = "term",
ref.line = 1, log.scale = TRUE,
sort = "estimate", descending = TRUE,
banding = TRUE,
ci.text.title = "OR (95% CI)", digits = 2,
xlab = "Odds ratio (95% CI)")Turn the estimate column off with ci.text = FALSE,
weight the point size by a column (e.g. a study’s sample size) with
size =, and see ?ggestimates for the full
argument list.
The same primitive draws a meta-analysis: one row per study plus a
pooled estimate. Weight the study markers by their precision with
size =.
meta <- data.frame(
study = c("Study A", "Study B", "Study C", "Study D", "Pooled"),
estimate = c(0.82, 0.75, 0.91, 0.68, 0.79),
conf.low = c(0.61, 0.55, 0.70, 0.48, 0.70),
conf.high = c(1.10, 1.02, 1.18, 0.96, 0.89),
weight = c(0.8, 1.0, 0.9, 0.7, 2.2)
)
ggestimates(meta, label = "study", size = "weight",
ref.line = 1, log.scale = TRUE,
ci.text.title = "RR (95% CI)",
xlab = "Risk ratio (95% CI)")The larger pooled marker sits below the individual studies with a tighter interval – the familiar bottom row of a meta-analysis forest.