The layout of the forest plot is determined by the dataset provided. All elements of the forest plot are placed in cells, making it easy to edit any element in the plot given its row and column. The graphical parameters of each element can be further customized in its respective cell. Here, we will start with a simple meta-analysis forest plot.
The plotting steps demonstrated in this vignette may not necessarily be optimal. There are other R packages that may be better suited for the plots demonstrated here. Please choose the one that best suits your needs. The final plot is shown below:
We will start by drawing a forest plot using an example from metafor, which is similar to the plot above. First, we load the data and draw a simple forest plot.
library(grid)
library(forestploter)
# Read meta-analysis example data
dt <- read.csv(system.file("extdata", "metadata.csv", package = "forestploter"))
str(dt)
#> 'data.frame': 9 obs. of 16 variables:
#> $ author : chr "Amore-Coffea 2000" "Deliciozza 2004" "Kahve-Paradiso 2002" "Mama-Kaffa 1999" ...
#> $ ai : int 2 10 0 12 3 19 4 8 NA
#> $ n1i : int 31 40 0 53 15 68 35 35 277
#> $ ci : int 10 9 0 9 1 9 2 6 NA
#> $ n2i : int 34 40 0 61 17 64 37 37 290
#> $ weights: num 10.19 18.37 NA 19.91 5.37 ...
#> $ orci : chr "0.17 [0.03, 0.83]" "1.15 [0.41, 3.22]" "Not Estimated" "1.69 [0.65, 4.40]" ...
#> $ rb.a : chr "?" "+" "-" "-" ...
#> $ rb.b : chr "?" "?" "-" "-" ...
#> $ rb.c : chr "?" "?" "?" "?" ...
#> $ rb.d : chr "?" "?" "+" "-" ...
#> $ rb.e : chr "-" "-" "+" "-" ...
#> $ rb.f : chr "+" "+" "+" "+" ...
#> $ est : num 0.166 1.148 NA 1.691 4 ...
#> $ lb : num 0.033 0.409 NA 0.65 0.369 ...
#> $ ub : num 0.829 3.219 NA 4.4 43.383 ...
# Prepare a blank column for the CI
dt$cicol <- paste(rep(" ", 20), collapse = " ")
# Select some columns for plotting; this will serve as the skeleton of the forest plot
dt_fig <- dt[, c(1:7, 17, 8:13)]
colnames(dt_fig) <- c("Study or Subgroup",
"Events", "Total", "Events", "Total",
"Weight",
"", "",
LETTERS[1:6])
dt_fig$Weight <- sprintf("%0.1f%%", dt_fig$Weight)
dt_fig$Weight[dt_fig$Weight == "NA%"] <- ""
# Convert NA to a blank string
dt_fig[is.na(dt_fig)] <- ""
# Set background to white and summary diamond to black
tm <- forest_theme(core = list(bg_params = list(fill = c("white"))),
summary_col = "black",
arrow_label_just = "end",
arrow_type = "closed")
p <- forest(dt_fig,
est = dt$est,
lower = dt$lb,
upper = dt$ub,
sizes = sqrt(dt$weights / 100),
is_summary = c(rep(FALSE, nrow(dt) - 1), TRUE),
ci_column = 8,
ref_line = 1,
x_trans = "log",
arrow_lab = c("Favours caffeine", "Favours decaf"),
xlim = c(0.05, 100),
ticks_at = c(0.1, 1, 10, 100),
theme = tm)
p
The package provides several functions to modify the forest plot. Below are the functions to edit various aspects of the plot:
edit_plot
: Used to change graphical parameters of text,
background, and CI (e.g., color or font face of specific cells).add_text
: Adds text to specific rows/columns. For
complex text alignment, leave some rows/columns blank and use this
function.insert_text
: Inserts a row before or after a specific
row and adds text. Useful for inserting text between groups.add_border
: Adds a border to specific cells.add_grob
: Adds different graphical objects
(grobs).Below, we make the total row text bold, change the color of the diamond shape, and modify the background color of the total row. We also align text in the last six columns to the center.
# Change font face
g <- edit_plot(p, row = 9,
gp = gpar(fontface = "bold"))
# Change color
g <- edit_plot(g, col = 8, row = 9, which = "ci",
gp = gpar(col = "blue", fill = "blue"))
# Change the background of the total row
g <- edit_plot(g, col = 1:7,
row = 9,
which = "background",
gp = gpar(fill = "#f6eff7"))
# Align text to center
g <- edit_plot(g, col = 9:14,
which = "text",
hjust = unit(0.5, "npc"),
x = unit(0.5, "npc"))
g
For text alignment: - hjust = unit(0, "npc")
and
x = unit(0, "npc")
align text to the left. -
hjust = unit(0.5, "npc")
and
x = unit(0.5, "npc")
center-align text. -
hjust = unit(1, "npc")
and
x = unit(0.9, "npc")
align text to the right.
In this step, we add some text to the header and total number of events in the data.
# Add or insert some text to the header on top of CI columns
g <- add_text(g, text = "IV, Random, 95% CI",
part = "header",
col = 7:8,
gp = gpar(fontface = "bold"))
g <- insert_text(g, text = "Odds ratio",
part = "header",
col = 7:8,
gp = gpar(fontface = "bold"))
# Group outcomes
g <- add_text(g, text = "Caffeine",
part = "header",
row = 1,
col = 2:3,
gp = gpar(fontface = "bold"))
g <- add_text(g, text = "Decaf",
part = "header",
row = 1,
col = 4:5,
gp = gpar(fontface = "bold"))
# Add text on the top of the risk of bias data
g <- add_text(g, text = "Risk of Bias",
part = "header",
row = 1,
col = 9:14,
gp = gpar(fontface = "bold"))
# Insert event count
g <- insert_text(g,
text = c("Total events:"),
row = 9,
col = 1,
before = FALSE,
just = "left")
# Note: The row counts need to add one to account for
# `insert_text` in the previous step
g <- add_text(g, text = "58",
col = 2,
row = 10,
just = "left")
g <- add_text(g, text = "46",
col = 4,
row = 10,
just = "left")
g
In the next step, we add some borders to the header. The default is to add a border at the bottom of the cell(s).
# Add or insert some text to the header
g <- add_border(g,
part = "header",
row = 1,
col = 9:14,
gp = gpar(lwd = .5))
g <- add_border(g,
part = "header",
row = 2,
gp = gpar(lwd = 1))
g
In the next step, we add a rounded rectangle border with a dashed line around the risk of bias data. Then, we draw a circle grob with different colors at the bottom of the text.
g <- add_grob(g,
row = 1:c(nrow(dt_fig) - 1),
col = 9:14,
order = "background",
gb_fn = roundrectGrob,
r = unit(0.05, "snpc"),
gp = gpar(lty = "dotted",
col = "#bdbdbd"))
# Draw a circle grob; you can also draw a `pointsGrob`
cols <- c("#eeee00", "#00cc00", "#cc0000")
symb <- c("?", "+", "-")
for(i in seq_along(symb)){
pos <- which(dt_fig == symb[i], arr.ind = TRUE)
for(j in 1:nrow(pos)){
g <- add_grob(g,
row = pos[j, 1],
col = pos[j, 2],
order = "background",
gb_fn = circleGrob,
r = 0.4,
gp = gpar(fill = cols[i]))
}
}
g
The text we are going to create involves math expressions and
multiple lines. Any one of these can easily be done with
add_text
and setting parse = TRUE
. We can use
the code below to achieve what we want. The line break below is based on
the solution here
using the atop
function. You can also use the latex2exp
package for math expressions.
txt <- bquote(atop(paste("Heterogeneity: ", tau^2, " = 0.22; ",
chi^2, " = 9.39, df = 6 (P = 0.15) ",
I^2, " = 36%"),
"Total for overall effect: Z = 1.15 (P = 0.25)"))
add_text(g, text = txt,
col = 1:6,
row = 11,
just = "left",
parse = TRUE,
gp = gpar(fontsize = 8))
We can see that the second line is not left-aligned. We can use
add_grob
to take advantage of other packages that create
grobs
objects. Now we will use the gridtext package
to generate complex text and add it to the plot with
add_grob
. More details about the package can be found here. You can write
markdown or HTML text and render it to the plot. Currently, it does not
support math equations. See other Unicode math symbols here.
txt <- "Heterogeneity: τ<sup>2</sup> = 0.22; χ<sup>2</sup> = 9.39,
df = 6 (P = 0.15); I<sup>2</sup> = 36%<br><span style='color:blue'>**Total for overall effect:**</span> Z = 1.15 (P = 0.25)"
add_grob(g,
row = 11,
col = 1:6,
order = "background",
gb_fn = gridtext::richtext_grob,
text = txt,
gp = gpar(fontsize = 8),
hjust = 0, vjust = 1, halign = 0, valign = 1,
x = unit(0, "npc"), y = unit(1, "npc"))