Search code examples
rggplot2r-forestplot

how to align table with forest plot (ggplot2)


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


Solution

  • 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))