Visualizing many profiles with {patchwork}

How to visualize multiple lake profiles with {ggplot2} and {patchwork}

By Cory Sauve

December 6, 2021

Overview

For this post, we’re going to build off of the Visualizing Lake Profiles post and learn how to make subplots with patchwork. We’ll also add some annotations to the final plots.

Some housekeeping

We’ll be using the r4es package for the dataset in this post. It’s currently available on my GitHub and can be downloaded by:

# install.packages("devtools")
devtools::install_github("corysauve/r4es")

A quick refresher

Last time, we made a temperature profile using ggplot2:

library(r4es)
library(ggplot2)

ggplot(data = university_lake, aes(x = temp_c, y = depth)) + 
  geom_point() +
  geom_path() + 
  scale_y_reverse() +
  labs(
    x = "Temperature (°C)", 
    y = "Depth (m)"
  ) +
  theme_light() +
  theme(
    axis.text = element_text(size = 12), 
    axis.title = element_text(size = 14),
    plot.caption = element_text(hjust = -1, vjust = -10, size = 14), 
    plot.margin = unit(c(1, 1, 1.5, 1), "cm")
  )

This time we’ll visualize additional parameters and compose them together into one plot.

Let’s first remind ourselves what the data look like:

dplyr::glimpse(university_lake)
#> Rows: 9
#> Columns: 28
#> $ lake_name          <chr> "University", "University", "University", "Universi~
#> $ depth              <dbl> 0, 1, 2, 3, 4, 5, 6, 7, 8
#> $ temp_c             <dbl> 25.96, 25.32, 24.60, 23.33, 16.82, 14.35, 12.31, 10~
#> $ do_mgl             <dbl> 11.89, 14.78, 11.75, 2.53, 0.40, 0.00, 0.00, 0.00, ~
#> $ do_sat_per         <dbl> 162.2, 183.7, 143.2, 30.0, 0.4, 0.0, 0.0, 0.0, 0.0
#> $ cond_umhos         <dbl> 310.2, 298.6, 340.4, 386.0, 390.8, 375.8, 359.9, 35~
#> $ light_sur_mmol     <dbl> 1817, 3609, 1900, 2528, 3719, 1754, 1519, 1934, 2176
#> $ light_dep_mmol     <dbl> 1817.00, 259.10, 23.21, 4.38, 0.00, 0.00, 0.00, 0.0~
#> $ ph                 <dbl> 8.9, 8.9, 7.4, 8.0, 7.0, 7.0, 6.5, 6.8, 6.5
#> $ alk_mgl            <dbl> 95, 91, 110, 102, 164, 152, 171, 170, 206
#> $ turb_ntu           <dbl> 11.5, 12.0, 10.2, 11.6, 31.4, 23.4, 29.6, 32.8, 38.9
#> $ srp_mgl            <dbl> 0.004, 0.009, 0.005, 0.021, 0.008, 0.019, 0.056, 0.~
#> $ tp_mgl             <dbl> 0.049, 0.048, 0.050, 0.052, 0.037, 0.050, 0.079, 0.~
#> $ nh3_mgl            <dbl> 0.015, 0.015, 0.015, 0.015, 0.129, 0.548, 0.905, 1.~
#> $ no3_mgl            <dbl> 0.015, 0.010, 0.011, 0.010, 0.014, 0.016, 0.012, 0.~
#> $ tn_mgl             <dbl> 1.153, 1.132, 0.933, 0.955, 0.940, 0.985, 1.352, 1.~
#> $ chla_ugl           <dbl> 49.837, 61.845, 69.054, 72.105, 24.923, 15.433, 8.6~
#> $ dolichospermum_nul <dbl> 56560.0, 147280.0, 45749.3, 68381.0, 20821.0, 21903~
#> $ aphanizomenon_nul  <dbl> 13440.0, 19040.0, 7780.5, 0.0, 5923.0, 5227.0, 2976~
#> $ microcystis_nul    <dbl> 1120.0, 1120.0, 778.1, 0.0, 2513.0, 12196.0, 16505.~
#> $ ceratium_nul       <dbl> 3800, 408, 773, 305, 0, 0, 0, 0, 0
#> $ nauplii_nul        <dbl> 16.8, 26.1, 0.3, 12.0, 0.0, 9.5, 0.0, 0.0, 5.3
#> $ bosmina_nul        <dbl> 1.2, 1.1, 0.2, 4.5, 1.1, 2.4, 1.4, 3.9, 0.0
#> $ calanoid_nul       <dbl> 12.0, 13.5, 1.2, 22.5, 3.4, 11.8, 0.0, 3.9, 16.0
#> $ cyclopoid_nul      <dbl> 3.0, 2.3, 0.4, 18.0, 9.1, 49.7, 7.0, 19.5, 32.0
#> $ chaoborus_nul      <dbl> 0.0, 0.0, 0.0, 0.0, 0.0, 0.6, 0.5, 0.0, 0.1
#> $ orgn_mgl           <dbl> 1.123, 1.107, 0.907, 0.930, 0.797, 0.421, 0.435, 0.~
#> $ light_level_per    <dbl> 100.0, 7.2, 1.2, 0.2, 0.0, 0.0, 0.0, 0.0, 0.0

Dissolved oxygen

Dissolved oxygen and temperature are commonly plotted on the same figure in limnology. The major flaw with doing this on the same axis is that it is difficult to interpret since each have different scales. Instead, plotting each parameter as a different subplot allows for both to be interpreted by the reader simultaneously while not running into a scale issue.

ggplot2 doesn’t allow you to even plot two lines of differing scales on the same plot (for good reason). We could accomplish this using base graphics, but should we? I think not.

Since we already have our temperature plot, we just need to replicate the same thing but with do_mgL. Most of our elements can be copied, but pay close attention to:

  • aes(x = ...)
  • labs(x = "...")

Let’s make the initial plot:

ggplot(data = university_lake, aes(x = do_mgl, y = depth)) + 
  geom_point() + 
  geom_path() +
  scale_y_reverse(name = "Depth (m)") +
  labs(
    x = "Dissolved Oxygen (mg/L)", 
    y = "Depth (m)"
  ) + 
  theme_light() +
  theme(
    axis.text = element_text(size = 12), 
    axis.title = element_text(size = 14),
    plot.caption = element_text(hjust = -1, vjust = -10, size = 14), 
    plot.margin = unit(c(1, 1, 1.5, 1), "cm")
  )

Looks good! Now we need to save each subplot as an object to ensemble our patchwork:

p_temp <- 
  ggplot(data = university_lake, aes(x = temp_c, y = depth)) + 
  geom_point() +
  geom_path() + 
  scale_y_reverse() +
  labs(
    x = "Temperature (°C)", 
    y = "Depth (m)"
  ) +
  theme_light() +
  theme(
    axis.text = element_text(size = 12), 
    axis.title = element_text(size = 14),
    plot.caption = element_text(hjust = -1, vjust = -10, size = 14), 
    plot.margin = unit(c(1, 1, 1.5, 1), "cm")
  ) 
  
p_do <- 
  ggplot(data = university_lake, aes(x = do_mgl, y = depth)) + 
  geom_point() + 
  geom_path() +
  scale_y_reverse(name = "Depth (m)") +
  labs(
    x = "Dissolved Oxygen (mg/L)", 
    y = "Depth (m)"
  ) + 
  theme_light() +
  theme(
    axis.text = element_text(size = 12), 
    axis.title = element_text(size = 14),
    plot.caption = element_text(hjust = -1, vjust = -10, size = 14), 
    plot.margin = unit(c(1, 1, 1.5, 1), "cm")
  )

Creating our patchwork

Now that we have our figures saved as objects (p_temp, p_do), it’s time to make our final plot with the patchwork package.

We are only going to scratch the surface of what patchwork can do. Our use case is going to be quite simple (two side-by-side plots) so stay tuned for more complicated figure compositions in the future.

To combine our figures, we simply add them together:

library(patchwork) 

p_temp + p_do

Congrats! We have the start of our final figure. It’s really remarkable how simple patchwork makes this task. We can expand our initial plot to make it cleaner.

Combining axis titles

In my opinion, when subplots share the same y-axis it is best to only show one. I generally preserve the left subplot and remove the right subplot:

# p_temp stays the same, so no need to override
p_do <- 
  ggplot(data = university_lake, aes(x = do_mgl, y = depth)) + 
  geom_point() + 
  geom_path() +
  scale_y_reverse(name = "") +
  labs(
    x = "Dissolved Oxygen (mg/L)", 
    y = ""
  ) + 
  theme_light() +
  theme(
    axis.text = element_text(size = 12), 
    axis.title = element_text(size = 14),
    plot.caption = element_text(hjust = -1, vjust = -10, size = 14), 
    plot.margin = unit(c(1, 1, 1.5, 1), "cm")
  )

p_temp + p_do

Simplifying the theme

You may have noticed that we have some duplicated code in theme(). Perhaps a cleaner alternative is to create each figure and then apply the theme after we create our patchwork:

p_temp <- 
  ggplot(data = university_lake, aes(x = temp_c, y = depth)) + 
  geom_point() +
  geom_path() + 
  scale_y_reverse() +
  labs(
    x = "Temperature (°C)", 
    y = "Depth (m)"
  )

p_do <- 
  ggplot(data = university_lake, aes(x = do_mgl, y = depth)) + 
  geom_point() + 
  geom_path() +
  scale_y_reverse(name = "") +
  labs(
    x = "Dissolved Oxygen (mg/L)", 
    y = "Depth (m)"
  ) 

plot <- p_temp + p_do 

plot & 
  plot_annotation(caption = "Fig 1: Temperature and oxygen profiles, University Lake, IN.") &
  theme_light() &
  theme(
    axis.text = element_text(size = 12), 
    axis.title = element_text(size = 14),
    plot.caption = element_text(hjust = 0, vjust = -1, size = 14), 
    plot.margin = unit(c(1, 1, 1.5, 1), "cm")
  )