Skip to content
Permalink
Branch: master
Find file Copy path
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
247 lines (195 sloc) 10.7 KB
---
title: 'The small multiples plot: how to combine ggplot2 plots with one shared axis'
author: Ariel Muldoon
date: '2019-05-13'
slug: small-multiples-plot
output:
blogdown::html_page:
toc: true
categories:
- r
tags:
- ggplot2
- tidyr
draft: FALSE
description: "There are a variety of ways to combine ggplot2 plots with a single shared axis, but things can get tricky if you want a lot of control over all plot elements. I show three approaches to make such a plot: using facets, with package cowplot, and with package egg."
---
```{r setup, echo = FALSE}
knitr::opts_chunk$set(comment = "#")
```
There are a variety of ways to combine **ggplot2** plots with a single shared axis. However, things can get tricky if you want a lot of control over all plot elements.
I demonstrate three different approaches for this:
1. Using facets, which is built in to **ggplot2** but doesn't allow much control over the non-shared axes.
2. Using package **cowplot**, which has a lot of nice features but the plot spacing doesn't play well with a single shared axis.
3. Using package **egg**, which allows nice spacing for plots with a shared axis.
# Load R packages
I'll be plotting with **ggplot2**, reshaping with **tidyr**, and combining plots with package **egg**.
I'll also be using package **cowplot** version 0.9.4 to combine individual plots into one, but will use the package functions via `cowplot::` instead of loading the package. (I believe the next version of **cowplot** will not be so opinionated about the theme.)
```{r, message = FALSE}
library(ggplot2) # v. 3.1.1
library(tidyr) # v. 0.8.3
library(egg) # v. 0.4.2
```
# The set-up
Here's the scenario: we have one response variable (`resp`) that we want to plot against three other variables and combine them into a single "small multiples" plot.
I'll call the three variables `elev`, `grad`, and `slp`. You'll note that I created these variables to have very different scales.
```{r}
set.seed(16)
dat = data.frame(elev = round( runif(20, 100, 500), 1),
resp = round( runif(20, 0, 10), 1),
grad = round( runif(20, 0, 1), 2),
slp = round( runif(20, 0, 35),1) )
head(dat)
```
# Using facets for small multiples
One good option when we want to make a similar plot for different groups (in this case, different variables) is to use *faceting* to make different panels within the same plot.
Since the three variables are currently in separate columns we'll need to *reshape* the dataset prior to plotting. I'll use `gather()` from **tidyr** for this.
```{r}
datlong = gather(dat, key = "variable", value = "value", -resp)
head(datlong)
```
Now I can use `facet_wrap()` to make a separate scatterplot of `resp` vs each variable. The argument `scales = "free_x"` allows the x axis scales to differ for each variable but leaves a single y axis.
```{r}
ggplot(datlong, aes(x = value, y = resp) ) +
geom_point() +
theme_bw() +
facet_wrap(~variable, scales = "free_x")
```
I can use the facet strips to give the appearance of axis labels, as shown in [this Stack Overflow answer](https://stackoverflow.com/a/37574221/2461552).
```{r}
ggplot(datlong, aes(x = value, y = resp) ) +
geom_point() +
theme_bw() +
facet_wrap(~variable, scales = "free_x", strip.position = "bottom") +
theme(strip.background = element_blank(),
strip.placement = "outside") +
labs(x = NULL)
```
That's a pretty nice plot to start with. However, controlling the axis breaks in the individual panels [can be complicated](https://stackoverflow.com/questions/51735481/ggplot2-change-axis-limits-for-each-individual-facet-panel), which is something we'd commonly want to do.
In that case, it may make more sense to create separate plots and then combine them into a small multiples plot with an add-on package.
# Using **cowplot** to combine plots
Package **cowplot** is a really nice package for combining plots, and has lots of bells and whistles along with some pretty thorough [vignettes](https://cran.r-project.org/web/packages/cowplot/index.html).
The first step is to make each of the three plots separately. If doing lots of these we'd want to use some sort of loop to make a list of plots [as I've demonstrated previously](https://aosmith.rbind.io/2018/08/20/automating-exploratory-plots/). Today I'm going to make the three plots manually.
```{r}
elevplot = ggplot(dat, aes(x = elev, y = resp) ) +
geom_point() +
theme_bw()
gradplot = ggplot(dat, aes(x = grad, y = resp) ) +
geom_point() +
theme_bw() +
scale_x_continuous(breaks = seq(0, 1, by = 0.2) )
slpplot = ggplot(dat, aes(x = slp, y = resp) ) +
geom_point() +
theme_bw() +
scale_x_continuous(breaks = seq(0, 35, by = 5) )
```
The function `plot_grid()` in **cowplot** is for combining plots. To make a single row of plots I use `nrow = 1`.
The `labels` argument puts separate labels on each panel for captioning.
```{r}
cowplot::plot_grid(elevplot,
gradplot,
slpplot,
nrow = 1,
labels = "auto")
```
But we want a single shared y axis, not a separate y axis on each plot. I'll remake the combined plot, this time removing the y axis elements from all but the first plot.
```{r}
cowplot::plot_grid(elevplot,
gradplot + theme(axis.text.y = element_blank(),
axis.ticks.y = element_blank(),
axis.title.y = element_blank() ),
slpplot + theme(axis.text.y = element_blank(),
axis.ticks.y = element_blank(),
axis.title.y = element_blank() ),
nrow = 1,
labels = "auto")
```
This makes panels different sizes, though, which isn't ideal. To have all the plots the same width I need to align them vertically with `align = "v"`.
```{r}
cowplot::plot_grid(elevplot,
gradplot +
theme(axis.text.y = element_blank(),
axis.ticks.y = element_blank(),
axis.title.y = element_blank() ),
slpplot +
theme(axis.text.y = element_blank(),
axis.ticks.y = element_blank(),
axis.title.y = element_blank() ),
nrow = 1,
labels = "auto",
align = "v")
```
But, unfortunately, this puts the axis space back between the plots to make them all the same width. It turns out that **cowplot** isn't really made for plots with a single shared axis. The **cowplot** package author points us to package **egg** for this [in this Stack Overflow answer](https://stackoverflow.com/a/47615304/2461552).
# Using **egg** to combine plots
Package **egg** is another nice alternative for combining plots into a small multiples plot. The function in this package for combining plots is called `ggarrange()`.
Here are the three plots again.
```{r}
elevplot = ggplot(dat, aes(x = elev, y = resp) ) +
geom_point() +
theme_bw()
gradplot = ggplot(dat, aes(x = grad, y = resp) ) +
geom_point() +
theme_bw() +
scale_x_continuous(breaks = seq(0, 1, by = 0.2) )
slpplot = ggplot(dat, aes(x = slp, y = resp) ) +
geom_point() +
theme_bw() +
scale_x_continuous(breaks = seq(0, 35, by = 5) )
```
The `ggarrange()` function has an `nrow` argument so I can keep the plots in a single row.
The panel spacing is automagically the same here after I remove the y axis elements, and things look pretty nice right out of the box.
```{r}
ggarrange(elevplot,
gradplot +
theme(axis.text.y = element_blank(),
axis.ticks.y = element_blank(),
axis.title.y = element_blank() ),
slpplot +
theme(axis.text.y = element_blank(),
axis.ticks.y = element_blank(),
axis.title.y = element_blank() ),
nrow = 1)
```
We can bring panes closer by removing some of the space around the plot margins with the `plot.margin` in `theme()`. I'll set the spacing for right margin of the first plot, both left and right margins of the second, and the left margin of the third.
```{r}
ggarrange(elevplot +
theme(axis.ticks.y = element_blank(),
plot.margin = margin(r = 1) ),
gradplot +
theme(axis.text.y = element_blank(),
axis.ticks.y = element_blank(),
axis.title.y = element_blank(),
plot.margin = margin(r = 1, l = 1) ),
slpplot +
theme(axis.text.y = element_blank(),
axis.ticks.y = element_blank(),
axis.title.y = element_blank(),
plot.margin = margin(l = 1) ),
nrow = 1)
```
# Adding plot labels with tag_facet() in **egg**
You'll see there is a `labels` argument in `ggarrange()` documentation, but it didn't work well for me out of the box with only one plot with a y axis. However, we can get tricky with `egg::tag_facet()` if we add a facet strip to each of the individual plots.
It'd make sense to build these plots outside of `ggarrange()` and then add the tags and combine them instead of nesting everything like I did here, since the code is now a little hard to follow.
```{r}
ggarrange(tag_facet(elevplot +
theme(axis.ticks.y = element_blank(),
plot.margin = margin(r = 1) ) +
facet_wrap(~"elev"),
tag_pool = "a"),
tag_facet(gradplot +
theme(axis.text.y = element_blank(),
axis.ticks.y = element_blank(),
axis.title.y = element_blank(),
plot.margin = margin(r = 1, l = 1) ) +
facet_wrap(~"grad"),
tag_pool = "b" ),
tag_facet(slpplot +
theme(axis.text.y = element_blank(),
axis.ticks.y = element_blank(),
axis.title.y = element_blank(),
plot.margin = margin(l = 1) ) +
facet_wrap(~"slp"),
tag_pool = "c"),
nrow = 1)
```
We might want to add a right y axis to the right-most plot. In this case we'd want to change the axis ticks length to 0 via `theme()` elements. This can be done [separately per axis in the development version of **ggplot2**](https://github.com/tidyverse/ggplot2/pull/2934), and will be included in version 3.2.0.
You can’t perform that action at this time.