1

I am new to R and have created a forest/interval plot and am including a table beside the plot with my confidence intervals and risk ratios. My issue is that the RR and CIs do not line up exactly with the horizontal grid lines on the plot. I have tried to use the patchwork solution as seen here, but that doesn't seem to work for me: grid.arrange: change location table values to align with forest plot in R

Any help is appreciated.

#read in packages
library(dplyr)
library(ggplot2)

tester <- data.frame(
  treatmentgroup = c("TreatmentA","TreatmentB","TreatmentC","TreatmentD","TreatmentE","TreatmentF",                   "TreatmentA","TreatmentB","TreatmentC","TreatmentD","TreatmentE","TreatmentF"),

 rr = c(1.12, 1.9, 1.05, 0.76, 1.5, 1.11, 1.67, 0.78, 2.89, 3.2, 1.33, 1.29),
  low_ci = c(0.71, 0.76, 0.78, 0.48, 0.91, 0.73, 1, 0.34, 0.75, 1, 1.18, 0.18),
  up_ci = c(1.6, 1.7, 2.11, 1.4, 1.5, 1.7, 2.6, 3.1, 9.3, 9.4, 1.9, 2),
  RR_ci = c("1.12 (0.71, 1.6)","1.9 (0.76, 1.7)","1.05 (0.78, 2.1)","0.76 (0.48, 1.4)","1.5 (0.91, 1.5)","1.11 (0.73, 1.7)",
            "1.67 (1, 2.6)","0.78 (0.34, 3.1)","2.89 (0.75, 9.3)","3.2 (1, 9.4)","1.33 (1.18, 1.9)","1.29 (0.18, 2)"),
  ci = c("0.71, 1.6",
         "0.76, 1.7",
         "0.78, 2.1",
         "0.48, 1.4",
         "0.91, 1.5",
         "0.73, 1.7",
         "1, 2.6",
         "0.34, 3.1",
         "0.75, 9.3",
         "1, 9.4",
         "1.18, 1.9",
         "0.18, 2"),
  X = c("COPD", "COPD","COPD","COPD","COPD","COPD", "Cancer", "Cancer","Cancer","Cancer","Cancer","Cancer"),
  no=c(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12)
)

  
  # Reduce the opacity of the grid lines: Default is 255
  col_grid <- rgb(235, 235, 235, 100, maxColorValue = 255)
  
  forest = ggplot(data=tester,
             aes(x = treatmentgroup,y = rr, ymin = low_ci, ymax = up_ci ))+
    geom_pointrange(aes(col=treatmentgroup))+
    geom_hline(yintercept =1, colour='red')+
    xlab('Treatment')+ ylab("RR (95% Confidence Interval)")+
    geom_errorbar(aes(ymin=low_ci, ymax=up_ci,col=treatmentgroup),width=0,cex=1)+ 
    facet_wrap(~X,strip.position="top",nrow=9,scales = "free_y") +
    list(theme_classic()+ 
           theme(panel.background = element_blank(),strip.background = element_rect(colour=NA, fill=NA),
                 strip.placement = "outside",
                 strip.text.y = element_text( face="bold", size=12),
                 panel.grid.major.y = element_line(colour=col_grid, size=0.5),
                 strip.text = element_text (face="bold"),
                 panel.border = element_rect(fill = NA, color = "black"), 
                 legend.position = "none",
                 #legend.title = element_blank(),legend.position="bottom", strip.text = element_text(face="bold", size=9), 
                 axis.text=element_text(face="bold"),axis.title = element_text(face="bold"),
                 plot.title = element_text(face = "bold", hjust = 0.5,size=13)))+
    coord_flip()
  forest
  
  
  
  
  ## Create the table-base pallete
  table_base <- ggplot(tester, aes(y=rr)) +
    ylab(NULL) + xlab("  ") + 
    theme(plot.title = element_text(hjust = 0.5, size=12), 
          axis.text.x = element_text(color="white", hjust = 0.5, size = 25), ## This is used to help with alignment
          axis.line = element_blank(),
          axis.text.y = element_blank(), 
          axis.ticks = element_blank(),
          axis.title.y = element_blank(), 
          legend.position = "none",
          panel.background = element_blank(), 
          panel.border = element_blank(), 
          panel.grid.major = element_blank(),
          panel.grid.minor = element_blank(), 
          plot.background = element_blank())
  
  ## RR point estimate table
  tab1 <- table_base + 
    labs(title = "space") +
    geom_text(aes(y = no, x = 1, label = sprintf("%0.1f", round(rr, digits = 1))), size = 3) + ## decimal places
    ggtitle("rr")
  
  ## 95% ci table
  tab2 <- table_base +
    geom_text(aes(y = no, x = 1, label = ci), size = 3) + 
    ggtitle("95% CI")
  
  ## Merge tables with plot
  lay <-  matrix(c(1,1,1,1,1,1,1,1,1,1,2,3,3), nrow = 1)
  grid.arrange(forest, tab1, tab2, layout_matrix = lay)
  
  library(patchwork)
  forest + tab1 + tab2 + plot_layout(widths = c(10, 1, 3))

example of misalignment

Roaring
  • 59
  • 7
  • If I'm understanding correctly, it sounds like you want to force the plot origin to start at zero. Add `+ scale_y_continuous(expand = c(0, 0), limits = c(0,10))` after `coord_flip()` to your `forest` plot. https://stackoverflow.com/questions/13701347/force-the-origin-to-start-at-0 – jrcalabrese Aug 05 '22 at 15:31
  • Thank you for your response. I tried your suggestion but unfortunately that did not work for me. I just want the RR and 95%CI values in the table to line up exactly with the corresponding Treatment lines. So for example, under the COPD box, the RR and CI values for Treatment F are not aligned with the forest plot line. I would also like there to be more of a separation between the Cancer and COPD values in the RR/CI table on the right (e.g., 1.1 (0.73,1.7) should be slightly lower so it aligns with TreatmentF in COPD) – Roaring Aug 05 '22 at 15:39
  • I understand, I see that the table is slightly off. You could make both plot-table hybrids separately: plot the cancer separately from the COPD, and then merge the cancer table and cancer plot, then merge the COPD table and COPD plot, and then use `patchwork` to stack together. Or, you could skip the table and use `geom_label` to plot your CIs directly. Try adding `+ scale_y_continuous(expand = c(0, 0), limits = c(0,12)) + geom_label(aes(x = treatmentgroup, y = 10, label = RR_ci), hjust = 0)` to the end of `forest`. – jrcalabrese Aug 05 '22 at 16:20

1 Answers1

1

The main issue IMHO is that you made your table columns as two separate plots. Instead one option would be to make you table plot as one plot and importantly to facet by X too in the table plot. Otherwise the table plot is lacking the strip texts and without IMHO it's nearly impossible to align the table rows with the point ranges. The rest is styling where it's important to not get simply rid of theme elements, e.g. for the alignment it's important that there are strip boxes so we can't use element_blank but instead have to use empty strings for the strip texts.

tester <- data.frame(
  treatmentgroup = c("TreatmentA", "TreatmentB", "TreatmentC", "TreatmentD", "TreatmentE", "TreatmentF", "TreatmentA", "TreatmentB", "TreatmentC", "TreatmentD", "TreatmentE", "TreatmentF"),
  rr = c(1.12, 1.9, 1.05, 0.76, 1.5, 1.11, 1.67, 0.78, 2.89, 3.2, 1.33, 1.29),
  low_ci = c(0.71, 0.76, 0.78, 0.48, 0.91, 0.73, 1, 0.34, 0.75, 1, 1.18, 0.18),
  up_ci = c(1.6, 1.7, 2.11, 1.4, 1.5, 1.7, 2.6, 3.1, 9.3, 9.4, 1.9, 2),
  RR_ci = c(
    "1.12 (0.71, 1.6)", "1.9 (0.76, 1.7)", "1.05 (0.78, 2.1)", "0.76 (0.48, 1.4)", "1.5 (0.91, 1.5)", "1.11 (0.73, 1.7)",
    "1.67 (1, 2.6)", "0.78 (0.34, 3.1)", "2.89 (0.75, 9.3)", "3.2 (1, 9.4)", "1.33 (1.18, 1.9)", "1.29 (0.18, 2)"
  ),
  ci = c(
    "0.71, 1.6",
    "0.76, 1.7",
    "0.78, 2.1",
    "0.48, 1.4",
    "0.91, 1.5",
    "0.73, 1.7",
    "1, 2.6",
    "0.34, 3.1",
    "0.75, 9.3",
    "1, 9.4",
    "1.18, 1.9",
    "0.18, 2"
  ),
  X = c("COPD", "COPD", "COPD", "COPD", "COPD", "COPD", "Cancer", "Cancer", "Cancer", "Cancer", "Cancer", "Cancer"),
  no = c(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12)
)


# Reduce the opacity of the grid lines: Default is 255
col_grid <- rgb(235, 235, 235, 100, maxColorValue = 255)

library(dplyr, warn = FALSE)
library(ggplot2)
library(patchwork)

forest <- ggplot(
  data = tester,
  aes(x = treatmentgroup, y = rr, ymin = low_ci, ymax = up_ci)
) +
  geom_pointrange(aes(col = treatmentgroup)) +
  geom_hline(yintercept = 1, colour = "red") +
  xlab("Treatment") +
  ylab("RR (95% Confidence Interval)") +
  geom_errorbar(aes(ymin = low_ci, ymax = up_ci, col = treatmentgroup), width = 0, cex = 1) +
  facet_wrap(~X, strip.position = "top", nrow = 9, scales = "free_y") +
  theme_classic() +
  theme(
    panel.background = element_blank(), strip.background = element_rect(colour = NA, fill = NA),
    strip.text.y = element_text(face = "bold", size = 12),
    panel.grid.major.y = element_line(colour = col_grid, size = 0.5),
    strip.text = element_text(face = "bold"),
    panel.border = element_rect(fill = NA, color = "black"),
    legend.position = "none",
    axis.text = element_text(face = "bold"),
    axis.title = element_text(face = "bold"),
    plot.title = element_text(face = "bold", hjust = 0.5, size = 13)
  ) +
  coord_flip()

dat_table <- tester %>%
  select(treatmentgroup, X, RR_ci, rr) %>%
  mutate(rr = sprintf("%0.1f", round(rr, digits = 1))) %>%
  tidyr::pivot_longer(c(rr, RR_ci), names_to = "stat") %>%
  mutate(stat = factor(stat, levels = c("rr", "RR_ci")))

table_base <- ggplot(dat_table, aes(stat, treatmentgroup, label = value)) +
  geom_text(size = 3) +
  scale_x_discrete(position = "top", labels = c("rr", "95% CI")) +
  facet_wrap(~X, strip.position = "top", ncol = 1, scales = "free_y", labeller = labeller(X = c(Cancer = "", COPD = ""))) +
  labs(y = NULL, x = NULL) +
  theme_classic() +
  theme(
    strip.background = element_blank(),
    panel.grid.major = element_blank(),
    panel.border = element_blank(),
    axis.line = element_blank(),
    axis.text.y = element_blank(),
    axis.text.x = element_text(size = 12),
    axis.ticks = element_blank(),
    axis.title = element_text(face = "bold"),
  )

forest + table_base + plot_layout(widths = c(10, 4))

stefan
  • 90,330
  • 6
  • 25
  • 51
  • Thanks for your response! I've tried running your suggested code and I get a few errors, specifically with 'dat_table <- tester |>' block of code. One of the errors is that "Unexpected '>' in dat_table <-tester |>" – Roaring Aug 08 '22 at 21:32
  • Hm. I just run my code again and it worked fine with the example data provided. I also made an edit and included the data in the reprex. Maybe you could check first that the reprex runs fine. – stefan Aug 08 '22 at 21:46
  • I've just tried to run your entire updated code and these are the errors I receive for that one dat_table block of code: Error: unexpected '>' in "dat_table <- tester |>" Error: unexpected '>' in " select(treatmentgroup, X, RR_ci, rr) |>" Error: unexpected '>' in " mutate(rr = sprintf("%0.1f", round(rr, digits = 1))) |>" Error: unexpected '>' in " tidyr::pivot_longer(c(rr, RR_ci), names_to = "stat") |>" Error in as.character(x) : cannot coerce type 'closure' to vector of type 'character' – Roaring Aug 08 '22 at 21:50
  • Okay. The issue is that I use the native pipe `|>` introduced with R 4.1.0. So I guess you are using R < 4.1.0. But that's easy to fix. You can simply replace `|>` by the `magrittr` pipe `%>%`. I just made an edit to fix that. – stefan Aug 08 '22 at 21:57
  • Ok! thank you kindly for all of your help and patience. I'll have to make sure to update my R. – Roaring Aug 08 '22 at 21:59