Week 1, Session 5 — ggplot2 grammar and multi-panel layouts

Course 1 — #courses

Author

R. Heller

Note

Workflow labs use the variant template: Goal → Approach → Execution → Check → Report.

Learning objectives

  • Build a ggplot by naming data, aesthetics, geoms, and scales rather than by calling a plot type.
  • Use faceting to display the same relationship across subgroups.
  • Combine several plots into a single figure with patchwork.

Prerequisites

Labs 1.2 through 1.4.

Background

ggplot2 is built on a grammar of graphics. Rather than call a function named “scatterplot” or “boxplot”, you declare what the plot contains: a dataset, mappings from variables to aesthetics (x, y, colour, size, shape), and one or more geoms that render those mappings. The result is that any plot you can picture can be built by the same small vocabulary. Once the vocabulary is second nature, you will spend less time searching for a plotting function and more time thinking about what the graph should show.

Multi-panel figures are the standard unit of publication. Faceting breaks one plot into small multiples — the same relationship repeated across a grouping variable — which is almost always a better answer than crowding more colours onto a single panel. The patchwork package takes independent ggplots and assembles them into a single figure using +, |, and / operators, so you can build a three-panel manuscript figure without leaving R.

A common beginner mistake is to treat ggplot as a collection of geom_* functions. It is cleaner to think of a plot as a data-plus- aesthetics object to which geoms are added. Changing the underlying data is then a substitution, not a rewrite.

Setup

library(tidyverse)
library(palmerpenguins)
library(patchwork)
set.seed(42)
theme_set(theme_minimal(base_size = 12))

1. Goal

Build a three-panel figure that describes bill morphology and body mass in the penguins dataset, using a scatter plot, a boxplot, and a faceted scatter plot, assembled with patchwork.

2. Approach

p <- penguins |>
  drop_na(bill_length_mm, bill_depth_mm, body_mass_g, species, sex)

The plot object is built in three lines: data, aesthetics, geom.

p1 <- p |>
  ggplot(aes(bill_length_mm, bill_depth_mm, colour = species)) +
  geom_point(alpha = 0.7) +
  labs(x = "Bill length (mm)",
       y = "Bill depth (mm)",
       colour = NULL)
p1

3. Execution

p2 <- p |>
  ggplot(aes(species, body_mass_g, fill = species)) +
  geom_boxplot(alpha = 0.6, colour = "grey30") +
  labs(x = NULL, y = "Body mass (g)") +
  theme(legend.position = "none")
p2

p3 <- p |>
  ggplot(aes(bill_length_mm, body_mass_g, colour = sex)) +
  geom_point(alpha = 0.6) +
  facet_wrap(~ species) +
  labs(x = "Bill length (mm)",
       y = "Body mass (g)",
       colour = NULL)
p3

Faceting shows the same relationship in three panels, one per species. The within-species slopes are visually separable from the between-species differences.

4. Check

Assemble the three panels with patchwork.

(p1 | p2) / p3 +
  plot_annotation(
    title = "Palmer penguins: bill morphology and body mass",
    tag_levels = "A"
  )

The layout operators: | places side-by-side, / stacks, + groups. plot_annotation() adds title and the familiar A/B/C labels used in manuscripts.

5. Report

A three-panel figure was assembled from the palmerpenguins data (n = 333 complete cases) using ggplot2 and patchwork. Panel A shows a clear separation of bill morphology among the three species. Panel B shows Gentoo as markedly heavier than the other two. Panel C, faceted by species, reveals a within-species positive association between bill length and body mass, with a sex offset visible in each panel.

Within-group and between-group variation are separate features of the data. Faceting is the simplest way to let a reader see both at once.

Live-build the figure one geom at a time, adding the facet at the end. Resist the temptation to show a finished plot straight away; the pedagogy is in watching a plot grow.

Common pitfalls

  • Using colour to encode more than one thing at a time (group and magnitude — pick one).
  • Mapping a continuous variable to shape.
  • Overlaying more than four or five categories on a single scatter plot; use faceting instead.
  • Defaulting to theme_grey(); the in-house convention is theme_minimal(base_size = 12).

Further reading

  • Wickham H. ggplot2: Elegant Graphics for Data Analysis, chapters on the grammar and on faceting.
  • Pedersen T. patchwork package documentation.

Session info

sessionInfo()
R version 4.4.1 (2024-06-14)
Platform: x86_64-pc-linux-gnu
Running under: Ubuntu 24.04.4 LTS

Matrix products: default
BLAS:   /usr/lib/x86_64-linux-gnu/openblas-pthread/libblas.so.3 
LAPACK: /usr/lib/x86_64-linux-gnu/openblas-pthread/libopenblasp-r0.3.26.so;  LAPACK version 3.12.0

locale:
 [1] LC_CTYPE=C.UTF-8       LC_NUMERIC=C           LC_TIME=C.UTF-8       
 [4] LC_COLLATE=C.UTF-8     LC_MONETARY=C.UTF-8    LC_MESSAGES=C.UTF-8   
 [7] LC_PAPER=C.UTF-8       LC_NAME=C              LC_ADDRESS=C          
[10] LC_TELEPHONE=C         LC_MEASUREMENT=C.UTF-8 LC_IDENTIFICATION=C   

time zone: UTC
tzcode source: system (glibc)

attached base packages:
[1] stats     graphics  grDevices utils     datasets  methods   base     

other attached packages:
 [1] patchwork_1.3.2      palmerpenguins_0.1.1 lubridate_1.9.5     
 [4] forcats_1.0.1        stringr_1.6.0        dplyr_1.2.1         
 [7] purrr_1.2.2          readr_2.2.0          tidyr_1.3.2         
[10] tibble_3.3.1         ggplot2_4.0.3        tidyverse_2.0.0     

loaded via a namespace (and not attached):
 [1] gtable_0.3.6       jsonlite_2.0.0     compiler_4.4.1     tidyselect_1.2.1  
 [5] scales_1.4.0       yaml_2.3.12        fastmap_1.2.0      R6_2.6.1          
 [9] labeling_0.4.3     generics_0.1.4     knitr_1.51         htmlwidgets_1.6.4 
[13] pillar_1.11.1      RColorBrewer_1.1-3 tzdb_0.5.0         rlang_1.2.0       
[17] stringi_1.8.7      xfun_0.57          S7_0.2.2           otel_0.2.0        
[21] timechange_0.4.0   cli_3.6.6          withr_3.0.2        magrittr_2.0.5    
[25] digest_0.6.39      grid_4.4.1         hms_1.1.4          lifecycle_1.0.5   
[29] vctrs_0.7.3        evaluate_1.0.5     glue_1.8.1         farver_2.1.2      
[33] rmarkdown_2.31     tools_4.4.1        pkgconfig_2.0.3    htmltools_0.5.9