In this post, you will learn the nuts and bolts of arranging multiple ggplots with the package patchwork
!
Welcome to the first post of 2025! I’m looking forward to writing some interesting posts this year, and I’ll kick it off here with a handy post: arranging multiple ggplots with the extension package patchwork
.
Oftentimes, we have multiple related plots and we would like to combine them into one single figure. The traditional way to do it is to save the individual plots and manually merge them in other software programs like PowerPoint. This method can be convenient; however, it is not reproducible and we will need to redo everything again if we change the component plots. A more “modern” way is to merge the plots in R and output them as a single figure. If you do it in this way, then the next time when you modify your component plots, the final figure will be automatically updated. Also, you can avoid the hassle of aligning the plots or adjusting the aspect ratios, and you will always get the exact same output.
There are several extension packages that can arrange multiple ggplots. In fact, I’ve written a post on this topic a few years ago. In that post, I introduced two packages ggpubr
and cowplot
. These two packages provide basic functionality for plot arrangements, but in this follow-up post I will dive deeper into a much more powerful package patchwork
. Thanks to this awesome package, arranging multiple ggplots has become easier than ever, especially when we want to make complex multipanel figures!
Get some plots on hand? Time to lay them out!
patchwork
patchwork
offers a wide variety of functions for arranging and polishing multipanel figures. In the following sections, I’ll walk you through them and show you some examples.
Let’s start by creating some example plots:
library(tidyverse)
library(patchwork)
### Create some example plots
p1 <- ggplot(iris) +
geom_point(aes(x = Sepal.Length, y = Sepal.Width))
p2 <- ggplot(iris) +
geom_point(aes(x = Sepal.Length, y = Sepal.Width, color = Species))
p3 <- ggplot(iris) +
geom_boxplot(aes(x = Species, y = Sepal.Length))
p4 <- ggplot(iris) +
geom_boxplot(aes(x = Species, y = Sepal.Length, fill = Species))
The simplest way to combine these plots is using “+
”: this results in “plot packing” where the plots are arranged in a grid-like fashion and the function will make the figure as close to a square as possible.
### Plot packing
p1 + p2 + p3 + p4
Besides “+
”, we can use “|
” to stack the plots horizontally and “/
” to stack the plots vertically.
### Horizontal stacking
p1 | p2 | p3 | p4
### Vertical stacking
p1 / p2 / p3 / p4
When we have a mix of “+
”, “|
”, and “/
”, “+
” goes first, then “/
”, and finally “|
”. If we want to change the precedence, we can use parentheses. Compared the following two figures:
### Default precedence
p1 / p2 | p3 + p4
### Indicate precedence with parentheses
p1 / (p2 | p3) + p4
In the first specification (the default precedence), p3 and p4 are packed first, then p1 and p2 are stacked vertically, and finally these two composite plots are stacked horizontally to produce the final figure. In the second specification, p2 and p3 are stacked horizontally first, then the composite plot is packed with p4, and finally it is vertically stacked with p1.
When have multiple ggplots stored in a list, we can simply pass the list to the function wrap_plot()
, which will pack all the plots in the list. This is essentially the same as “p1 + p2 + … + pn”, but it saves us the time of having to pull the individual plots out and add “+
” between them.
### A list of ggplots
p_list <- list(p1, p2, p3, p4)
### Wrap the plots
wrap_plots(p_list)
Now that we know the basics, let’s take a step further to change the layout design using plot_layout()
, where we can specify the number of rows/columns and their relative heights/widths. Moreover, we can create extra white space by adding empty plots using plot_spacer()
:
### Change the layout design and add empty plots
p1 + plot_spacer() + p2 + p3 + p4 + plot_layout(ncol = 2, widths = c(2, 1))
Another way to specify the layout design is to use a matrix with letters denoting different panels (“#” denotes white space):
### Create a layout matrix
layout_matrix <- "
#AA##CC#
BBBBDDDD
BBBBDDDD
"
p1 + p2 + p3 + p4 + plot_layout(design = layout_matrix)
Yet another way to specify the layout design is to create a set of coordinates for the panels using area()
:
### Create a set of coordinates for the panels
layout_coord <- c(area(t = 1, b = 7, l = 1, r = 7),
area(t = 2, b = 6, l = 8, r = 12),
area(t = 3, b = 5, l = 13, r = 15))
p1 + p2 + p3 + plot_layout(design = layout_coord)
Phew, that’s a lot! We’re done with plot layout. Next, we’ll move on to our second topic: plot alignment.
A neat thing about patchwork
is that the plots are automatically aligned by their panel borders:
### Default plot alignment by panel borders
p1 / p2 / p3 # the legend of p2 takes up extra space
However, this default behavior can sometimes be a bit troublesome because the legend (like the one above) or long axis text will take up extra space. To deal with this issue, we can use the function free()
to “free” the plot from the alignment constraints. There are three ways to realign the plots:
### 1. Free the right panel border of p2
p1 / free(p2, type = "panel", side = "r") / p3 # the right panel border of p2 is no longer forced to be aligned with those of p1 and p3
### Create a plot with long y-axis text
p2_long_axis_text <- p2 + scale_y_continuous(labels = ~ format(as.numeric(.x), nsmall = 5))
### Default alignment
p1 / p2_long_axis_text / p3 # extra white space between y-axis title and text in p1 and p3
### 2. Free the y-axis title of p1
free(p1, type = "label", side = "l") / p2_long_axis_text / p3 # no extra space between y-axis title and text in p1
plot_spacer()
and free the plot so that the legend or long axis text can take up the designated space:### Default alignment
p2 + plot_spacer() + p1 + p3 # the legend of p2 creates a space gap between p1 and p3
### 3. Free the space to the right of p2
free(p2, type = "space", side = "r") + plot_spacer() + p1 + p3 # now the legend of p2 can take up the white space created by plot_spacer()
Cool. This is a good segue into our third topic: legends and axes.
patchwork
provides some basic means for adjusting legend positions. As we saw earlier, when we combine plots with and without legends, it can lead to white gaps between panels. A solution to this is to collect and place all the legends on the right using the argument “guides = ‘collect’” in plot_layout()
:
### Place the legends on the right
p1 + p3 + p2 + p4 + plot_layout(guides = "collect")
A useful feature of this argument is that the duplicates will be removed automatically when there are multiple same legends:
### Duplicate legends are removed
p1 + p3 + p2 + p2 + plot_layout(guides = "collect")
We can also use guide_area()
to create a designated area for the legend(s):
### Create a designated area for the legend(s)
p1 + p3 + p2 + guide_area() + plot_layout(guides = "collect")
Similar to legends, for plots that share the same axes, we can remove duplicate title and text using the argument “axes = ‘collect’” or “axis_titles = ‘collect’” in plot_layout()
:
### Remove duplicate axis title and text
p1 + p2 + p3 + p4 + plot_layout(axes = "collect")
### Remove duplicate axis title only
p1 + p2 + p3 + p4 + plot_layout(axis_titles = "collect")
We can add a title, a subtitle, a caption, and panel tags to the patchwork figure using the function plot_annotation()
. The text appearance can be adjusted via the “theme =” argument:
### Add a title, a subtitle, a caption, and tag labels
p1 + p2 + p3 + p4 +
plot_annotation(title = "This is a patchwork figure",
subtitle = "There are four panels",
caption = "Dataset: iris",
tag_levels = c("a"), # there are several built-in tag labels available
tag_prefix = "(",
tag_suffix = ")",
theme = theme(plot.title = element_text(size = 15, hjust = 0.5),
plot.subtitle = element_text(size = 12),
plot.caption = element_text(face = "italic"),
plot.tag.position = c(0.1, 1),
plot.tag = element_text(size = 13, hjust = 0)))
The last (but certainly not least) thing we can do is to add an inset plot to the figure. This can easily be done via the function inset_element()
. Just pass the inset plot to the function and specify the desired location (using the relative coordinates from 0 to 1), and you’re all set!
p1 + inset_element(p3, top = 0.95, bottom = 0.55, left = 0.55, right = 0.95)
To summarize what we did, we first learned several ways to arrange multiple ggplots. We then learned how to adjust plot alignments and modify legends and axes. Finally, we learned how to add text annotations and inset plots. patchwork
is such a handy package that I use it all the time when combining ggplots. I believe you will join me after reading this post!
Hope you learn something useful from this post and don’t forget to leave your comments and suggestions below if you have any!
If you see mistakes or want to suggest changes, please create an issue on the source repository.