11  Multipanel Figures with ggplot2

Example of a two-panel figure: a scatterplot of petal length against sepal length for the three iris species, with setosa forming a separate cluster of short petals and versicolor and virginica overlapping as petal length rises with sepal length, next to boxplots of sepal length by iris species, increasing from setosa to versicolor to virginica

Multipanel figures — where several plots are arranged together into a single figure — are a common way to present related results side by side. In R with ggplot2, there are two main approaches:

  1. Faceting, which splits a single dataset into panels based on the values of one or more variables.
  2. Combining separate plots using the patchwork package, which lets you arrange completely independent ggplots together.

Before you get started, read the page on the basics of plotting with ggplot and install the package ggplot2.

For the examples on this page we will use the iris dataset (already built into R) and the mpg dataset from ggplot2.

data(iris)
data(mpg)

11.1 Faceting with facet_wrap

Faceting is ideal when you want to make the same type of plot for subsets of your data. facet_wrap takes a single grouping variable and wraps the panels into rows and columns automatically.

The formula syntax ~ variable specifies which variable to split on. For example, to make a separate scatter plot for each iris species:

p <- ggplot(iris, aes(Sepal.Length, Petal.Length)) +
  geom_point() +
  theme_classic()

p + facet_wrap(~ Species)

Three facets side by side, one per iris species, each a scatterplot of petal length against sepal length on the same axes

By default facet_wrap tries to make the panel layout as square as possible. You can control the number of rows or columns with nrow or ncol:

p + facet_wrap(~ Species, ncol = 1)

The same three facets stacked in a single column

You can add scales = "free" if you want each panel to use its own axis limits, rather than sharing the same scale across all panels. This is useful when the ranges differ greatly between groups, but makes direct visual comparison harder.

p + facet_wrap(~ Species, scales = "free")

The same three facets side by side, each with its own axis ranges, so the setosa panel zooms in on its small petals

11.2 Faceting with facet_grid

facet_grid lays panels out in a strict grid defined by two variables — one for rows and one for columns. Use the formula rows ~ cols. To facet only on one dimension, replace the other with a dot (.).

Here we use the mpg dataset, splitting by drive type (drv) in rows and number of cylinders (cyl) in columns:

ggplot(mpg, aes(displ, hwy)) +
  geom_point() +
  theme_classic() +
  facet_grid(drv ~ cyl)

A grid of scatterplots of highway fuel economy (hwy) against engine displacement (displ) for cars, with rows for drive type (4, f, r) and columns for number of cylinders (4, 5, 6, 8); some combinations have few or no cars

facet_grid is useful when you have two categorical variables and want to see every combination. Empty panels appear where no data exist for a given combination.

To facet on just one variable across rows with facet_grid, use . ~ variable (or variable ~ . for columns):

ggplot(mpg, aes(displ, hwy)) +
  geom_point() +
  theme_classic() +
  facet_grid(. ~ drv)

Three facets side by side of highway fuel economy against engine displacement for 4-wheel, front-wheel and rear-wheel drive cars; economy falls as displacement increases

11.3 Customising facet labels and strips

By default the facet labels are the values of the grouping variable. You can change the strip background and text colour inside a theme() call:

p + facet_wrap(~ Species) +
  theme(
    strip.background = element_rect(fill = "steelblue"),
    strip.text = element_text(colour = "white", face = "bold")
  )

The three iris facets with the facet labels in white bold text on steel-blue strips

To rename the labels without changing the underlying data, supply a named character vector to the labeller argument:

species_labels <- c(
  setosa = "I. setosa",
  versicolor = "I. versicolor",
  virginica = "I. virginica"
)

p + facet_wrap(~ Species, labeller = labeller(Species = species_labels)) +
  theme(strip.text = element_text(face = "italic"))

The three iris facets with the facet labels changed to italic species names: I. setosa, I. versicolor and I. virginica

11.4 Combining separate plots with patchwork

Faceting requires all panels to share the same underlying ggplot call and the same geom type. When you want to combine different plots — perhaps a scatter plot next to a bar plot — use the patchwork package.

Install it once with install.packages("patchwork"), then load it:

First, create two separate ggplot objects:

p1 <- ggplot(iris, aes(Sepal.Length, Petal.Length, colour = Species)) +
  geom_point() +
  theme_classic()

p2 <- ggplot(iris, aes(Species, Sepal.Length, fill = Species)) +
  geom_boxplot(show.legend = FALSE) +
  theme_classic()

The | operator places plots side by side, and / stacks them vertically:

p1 | p2

Two plots side by side: a scatterplot of petal length against sepal length for the three iris species, with setosa forming a separate cluster of short petals and versicolor and virginica overlapping as petal length rises with sepal length, and boxplots of sepal length by iris species, increasing from setosa to versicolor to virginica

p1 / p2

The same two plots stacked vertically, the scatterplot above the boxplots

You can mix | and / and use brackets to control grouping. For example, to put two narrow plots on the right and one tall plot on the left:

p3 <- ggplot(iris, aes(Petal.Width, Petal.Length, colour = Species)) +
  geom_point() +
  theme_classic()

p1 | (p2 / p3)

A three-panel layout: the iris scatterplot of petal against sepal length on the left, and on the right the sepal length boxplots stacked above a scatterplot of petal length against petal width coloured by species, with setosa separate at small values and versicolor and virginica rising together

11.5 Adding panel labels with patchwork

Publication figures often label each panel with a letter (A, B, C…). patchwork makes this straightforward with plot_annotation:

(p1 | (p2 / p3)) +
  plot_annotation(tag_levels = "A")

The same three-panel layout with the panels tagged A, B and C

You can also add a shared title across all panels:

(p1 | p2) +
  plot_annotation(
    title = "Iris sepal and petal measurements",
    theme = theme(plot.title = element_text(size = 14, face = "bold"))
  )

The iris scatterplot and boxplots side by side under the bold title 'Iris sepal and petal measurements'

11.6 Collecting shared legends

When panels share the same colour or fill aesthetic, patchwork can merge their legends into one with plot_layout(guides = "collect"):

(p1 | p3) +
  plot_layout(guides = "collect")

Two iris scatterplots side by side, petal length against sepal length and against petal width, sharing a single species legend on the right

Combine with plot_annotation to get a clean, publication-ready layout in a few lines of code:

(p1 | p3) +
  plot_layout(guides = "collect") +
  plot_annotation(
    tag_levels = "A",
    title = "Iris petal and sepal relationships"
  )

The same two scatterplots tagged A and B under the title 'Iris petal and sepal relationships', with one shared species legend

11.7 Further help

Pedersen, T L (2024) patchwork: The Composer of Plots. R package. https://patchwork.data-imaginist.com

Wickham, H (2016) ggplot2: Elegant Graphics for Data Analysis. Springer-Verlag New York.

Author: Environmental Computing

Year: 2025