---
title: "Forest and Estimation Plots with ggestimates()"
output: rmarkdown::html_vignette
vignette: >
  %\VignetteIndexEntry{Forest and Estimation Plots with ggestimates()}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

```{r setup, include = FALSE}
# The "from a fitted model" section uses datarium's example data; skip it
# gracefully if datarium is not installed so the vignette still builds.
has_datarium <- requireNamespace("datarium", quietly = TRUE)
knitr::opts_chunk$set(
  collapse = TRUE,
  comment = "#>",
  fig.width = 6.5,
  fig.height = 3.6,
  dpi = 96,
  message = FALSE,
  warning = FALSE
)
```

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.

```{r libs}
library(ggpubr)
```

## From a table of estimates

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.

```{r or}
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.

## From a fitted model

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.

```{r model, eval = has_datarium}
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")
```

```{r model-note, echo = FALSE, eval = !has_datarium, results = "asis"}
cat("> The fitted-model example needs the **datarium** package, which is not",
    "installed, so its figure is not shown here.")
```

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.

## Mean differences and linear coefficients

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.

```{r md}
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)")
```

## Sorting, banding, and the estimate column

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`:

```{r styling, fig.height = 3.8}
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.

## A meta-analysis forest

The same primitive draws a meta-analysis: one row per study plus a pooled
estimate. Weight the study markers by their precision with `size =`.

```{r meta, fig.height = 3.8}
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.
