Multi-figure panel

Introduction

The purpose of this lesson is combine multiple figures into a single multi-panel figure using patchwork or cowplot.

Why learn to combine figures?

Combining multiple figures is advantageous when preparing results for conference presentations (via poster) or publication. In addition, most scientific journals place limits on the number of figures permitted per publication, ranging from 3-7 figures typically.

Prepping the data

Before using patchwork or cowplot it requires the objects to be saved as ggplot object. Remember to use as.ggplot when required (ex. for pheatmap).

#saveRDS()
#readRDS()

Load Libraries

library(patchwork)
library(cowplot)
library(ggplot2)
library(pheatmap)
library(dplyr)
library(tidyr)
library(pheatmap)
library(ggplotify)

At this point, we have created quite a few plots. These plots were saved as R objects (.rds). To load the data into R, we will use the readRDS() function.

#load the RDS 
boxplots <- readRDS("boxplot_grid.RDS")
volcano <- readRDS("volcano.RDS")
gsea_plot1 <- readRDS("gsea_plot1.RDS")
gsea_plot2 <- readRDS("gsea_plot2.RDS")
pca <- readRDS("pca.RDS")
heatmap <- readRDS("heatmap.RDS")
cor_heatmap <- readRDS("cor_heatmap.RDS")
#view objects
volcano

What is patchwork?

The goal of patchwork is to make it ridiculously simple to combine separate ggplots into the same graphic. As such it tries to solve the same problem as gridExtra::grid.arrange() and cowplot::plot_grid but using an application programming interface (API) that incites exploration and iteration, and scales to arbitrily complex layouts.

Place two plots horizontally

pca + cor_heatmap

Place multiple plots in one grid

We can continue to add plots using the + symbol, and patchwork will try to form a grid, proceeding from left to right row-wise. But things get messy…

pca + cor_heatmap + volcano

The plot layout can be controlled further with additional operators. The | symbol is used to place plots side by side, while the / symbol is used to stack plots vertically.

So for example using the | parameter:

pca | cor_heatmap 

And for vertical layout we would use:

pca / cor_heatmap

Another way to place three plots vertically

pca + cor_heatmap + volcano + plot_layout(ncol = 1)

Adjust sizes of plots

cor_heatmap + volcano + plot_layout(ncol = 2,
                                    widths = c(1,.5),
                                    heights = c(1,.5))

Use of Nested Layouts

(pca + cor_heatmap + volcano) / boxplots

Now lets add row3 for our heatmap:

(pca + cor_heatmap + volcano) / boxplots / heatmap

Now finally, lets add the GSEA plot 1 and 2 to row 3.

(pca + cor_heatmap + volcano) / boxplots / (heatmap | gsea_plot1 | gsea_plot2)

ggsave("Figure_patchwork.pdf", height=6, width=8, dpi=300, units="in", scale = 2)

Not terrible, but we are seeing issues with sizing. Let’s try with cowplots.

What is cowplot?

The cowplot package provides various features that help with creating publication-quality figures, such as a set of themes, functions to align plots and arrange them into complex compound figures, and functions that make it easy to annotate plots and or mix plots with images. The package was originally written for internal use in the Wilke lab, hence the name (Claus O. Wilke’s plot package). — cowplot 1.1.1

Cowplot also has some notable features including label customization, unique plot arrangements, and image drawing.

Using cowplot to arrange figures

The main function to combine figures is plot_grid(). Let’s check out the help documentation using ?plot_grid().

?plot_grid()
# FigABC

FigABC <- plot_grid(pca, cor_heatmap, volcano, 
                    labels = "AUTO",
                    ncol = 3) 

FigABC

This figure isn’t bad already.

# FigD

FigD <- plot_grid(boxplots, labels = "D") 

FigD

gsea_plot1 <- as.ggplot(gsea_plot1)

gsea_plot1

gsea_plot2 <- as.ggplot(gsea_plot2)

gsea_plot2

Go ahead and add the final row with the heatmap, gsea_plot1, and gsea_plot2. Label accordingly.

# FigEFG

FigEFG <- plot_grid(heatmap, gsea_plot1, gsea_plot2,
                   labels = c("E", "F", "G"),
                   nrow = 1) 

FigEFG

Then move on to create the final figure.

# final figure

Final_figure <- plot_grid(FigABC, FigD, FigEFG, nrow = 3) 

ggsave("Final_figure.pdf", height=6, width=8, dpi=300, units="in", scale = 2)
Final_figure

Session Info

sessionInfo()
## R version 4.4.3 (2025-02-28)
## Platform: x86_64-apple-darwin20
## Running under: macOS Sequoia 15.3
## 
## Matrix products: default
## BLAS:   /Library/Frameworks/R.framework/Versions/4.4-x86_64/Resources/lib/libRblas.0.dylib 
## LAPACK: /Library/Frameworks/R.framework/Versions/4.4-x86_64/Resources/lib/libRlapack.dylib;  LAPACK version 3.12.0
## 
## locale:
## [1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8
## 
## time zone: America/New_York
## tzcode source: internal
## 
## attached base packages:
## [1] stats     graphics  grDevices utils     datasets  methods   base     
## 
## other attached packages:
## [1] ggplotify_0.1.2 tidyr_1.3.1     dplyr_1.1.4     pheatmap_1.0.12
## [5] ggplot2_3.5.2   cowplot_1.1.3   patchwork_1.3.0
## 
## loaded via a namespace (and not attached):
##   [1] tidyselect_1.2.1        farver_2.1.2            blob_1.2.4             
##   [4] R.utils_2.13.0          Biostrings_2.74.1       lazyeval_0.2.2         
##   [7] fastmap_1.2.0           digest_0.6.37           lifecycle_1.0.4        
##  [10] KEGGREST_1.46.0         tidytree_0.4.6          RSQLite_2.3.9          
##  [13] magrittr_2.0.3          compiler_4.4.3          rlang_1.1.6            
##  [16] sass_0.4.10             tools_4.4.3             igraph_2.1.4           
##  [19] yaml_2.3.10             ggtangle_0.0.6          data.table_1.17.0      
##  [22] knitr_1.50              labeling_0.4.3          bit_4.6.0              
##  [25] plyr_1.8.9              RColorBrewer_1.1-3      aplot_0.2.5            
##  [28] BiocParallel_1.40.2     withr_3.0.2             purrr_1.0.4            
##  [31] BiocGenerics_0.52.0     R.oo_1.27.0             grid_4.4.3             
##  [34] stats4_4.4.3            GOSemSim_2.32.0         enrichplot_1.26.6      
##  [37] colorspace_2.1-1        GO.db_3.20.0            scales_1.3.0           
##  [40] cli_3.6.4               rmarkdown_2.29          crayon_1.5.3           
##  [43] ragg_1.4.0              treeio_1.30.0           generics_0.1.3         
##  [46] ggtree_3.14.0           rstudioapi_0.17.1       httr_1.4.7             
##  [49] reshape2_1.4.4          ape_5.8-1               DBI_1.2.3              
##  [52] qvalue_2.38.0           cachem_1.1.0            DOSE_4.0.1             
##  [55] stringr_1.5.1           zlibbioc_1.52.0         splines_4.4.3          
##  [58] parallel_4.4.3          AnnotationDbi_1.68.0    XVector_0.46.0         
##  [61] rmdformats_1.0.4        yulab.utils_0.2.0       vctrs_0.6.5            
##  [64] Matrix_1.7-3            jsonlite_2.0.0          bookdown_0.42          
##  [67] gridGraphics_0.5-1      IRanges_2.40.1          S4Vectors_0.44.0       
##  [70] ggrepel_0.9.6           bit64_4.6.0-1           systemfonts_1.2.2      
##  [73] jquerylib_0.1.4         glue_1.8.0              codetools_0.2-20       
##  [76] stringi_1.8.7           gtable_0.3.6            GenomeInfoDb_1.42.3    
##  [79] UCSC.utils_1.2.0        munsell_0.5.1           tibble_3.2.1           
##  [82] pillar_1.10.2           htmltools_0.5.8.1       fgsea_1.32.0           
##  [85] GenomeInfoDbData_1.2.13 R6_2.6.1                textshaping_1.0.0      
##  [88] evaluate_1.0.3          Biobase_2.66.0          lattice_0.22-7         
##  [91] png_0.1-8               R.methodsS3_1.8.2       memoise_2.0.1          
##  [94] ggfun_0.1.8             bslib_0.9.0             Rcpp_1.0.14            
##  [97] fastmatch_1.1-6         nlme_3.1-168            xfun_0.52              
## [100] fs_1.6.6                pkgconfig_2.0.3