Plotting with ggplot2

Author

Charlotte Soneson

Published

October 23, 2024

A grammar of graphics

  • ggplot2 is an implementation of “The Grammar of Graphics” (Wilkinson 2005)
  • The idea is that each graph is built from the same few components - like a language, where sentences consist of components such as verbs, nouns, etc.
  • This is different from the “base” plotting framework in R, where each type of plot is generated with a specific function, and different plot functions can have quite different syntax, argument order etc.

Let’s make a thought experiment

  • Imagine that you have a plot on your screen.
  • What three things would you need to give/tell me in order for me to recreate the ‘gist’ of your plot (not bothering with details like matching the colors, labels and similar)?

Building a ggplot2 plot

  • The main components used by ggplot2 plots specify the:
    1. Data: data source (typically a data.frame or similar)
    2. Aesthetics: which variables to use (columns in the data.frame), and for what (e.g. x-axis, shape or colour)
    3. Geoms: how to represent values (e.g. points, bars, etc.)
    4. Scales: how the data values are mapped to the visual values of an aesthetic
    5. Stats: whether/how the data should be summarized (e.g. bins, mean, etc.)
    6. Facets: creates subplots based on grouped data
    7. Coordinate system: how data coordinates are mapped to the plane of the graphic (e.g. Cartesian or polar)
    8. Theme: finer controls of the display
  • The plot itself if constructed as a combination of layers, combined using the + operator. Each layer combines one or more components.

From https://r.qcbs.ca/workshop03/book-en/grammar-of-graphics-gg-basics.html

A note on data formatting

As discussed last week, to use data with ggplot2, it should be in the form of a tidy data.frame or a tibble.

In particular, each variable should be in a separate column, and all values of a given variable should be in the same column.

From the perspective of ggplot2, a variable is something that you would put on one of the axes in a plot, or use for coloring the points, etc.

:scale 40%

From https://www.rforecology.com/post/a-simple-introduction-to-ggplot2/

Example data

For this lecture, we will use the Palmer penguins data set, containing records of 344 penguins from three different species, collected from 3 islands in the Palmer Archipelago, Antarctica.

library(tidyverse)
dat <- read_csv("data/penguins.csv")
dat <- dat |> mutate(index = seq_along(species))
head(dat)
# A tibble: 6 × 9
  species island bill_length_mm bill_depth_mm flipper_length_mm body_mass_g
  <chr>   <chr>           <dbl>         <dbl>             <dbl>       <dbl>
1 Adelie  Torge…           39.1          18.7               181        3750
2 Adelie  Torge…           39.5          17.4               186        3800
3 Adelie  Torge…           40.3          18                 195        3250
4 Adelie  Torge…           NA            NA                  NA          NA
5 Adelie  Torge…           36.7          19.3               193        3450
6 Adelie  Torge…           39.3          20.6               190        3650
# ℹ 3 more variables: sex <chr>, year <dbl>, index <int>

Scatter plots

  • Volcano plots
  • Correlation plots
  • MA plots
  • Manhattan plots
  • PCA/tSNE/UMAP plots
  • Mean-variance plots
  • GC bias plots

Let’s start by creating the following plot:

library(ggplot2)
ggplot(data = dat) + 
  aes(x = flipper_length_mm) + 
  aes(y = bill_length_mm) + 
  geom_point(size = 5, 
             color = "cornflowerblue",
             shape = 16) +
  scale_x_continuous(limits = c(150, 250)) + 
  labs(x = "Flipper length (mm)") + 
  labs(y = "Bill length (mm)") + 
  labs(title = "Bill length vs flipper length") + 
  theme_bw() + 
  theme(axis.text = element_text(size = 15)) + 
  theme(axis.title = element_text(size = 18)) + 
  theme(title = element_text(size = 20)) 
Warning: Removed 2 rows containing missing values or values outside the scale range
(`geom_point()`).

Lessons learned

  • A ggplot is initialized by the ggplot() function, which is typically also where the data is provided.
  • Calls to aes() determine which columns in the data are mapped to each of the axes in the plot.
  • The geom determines what type of plot to make. It will inherit the information about which column to represent on each of the axes from the previous lines (we will see later that different geoms can use different data sets and aesthetics).
  • In this case, the color and shape of the points are constant (independent of the data), and thus the corresponding specification is not wrapped in a call to aes(). We will see later how to e.g. color the points according to a column in the data.
  • General plot appearance is set via the theme family of functions.

Exercise 1: Create the following plot

library(ggplot2)
ggplot(data = dat) + 
  aes(x = body_mass_g) + 
  aes(y = bill_length_mm) + 
  geom_point(size = 5, 
             color = "green",
             shape = 3) +
  labs(x = "Body mass (g)") +
  labs(y = "Bill length (mm)") +
  labs(title = "Bill length vs body mass") +
  theme_bw() +
  theme(axis.text = element_text(size = 15)) +
  theme(axis.title = element_text(size = 18)) +
  theme(title = element_text(size = 20))
Warning: Removed 2 rows containing missing values or values outside the scale range
(`geom_point()`).

Simplifications and alternative formulations

library(ggplot2)
ggplot(data = dat) + 
  aes(x = flipper_length_mm) + 
  aes(y = bill_length_mm) + 
  geom_point(size = 5, 
             color = "cornflowerblue",
             shape = 16) +
  labs(x = "Flipper length (mm)") +
  labs(y = "Bill length (mm)") +
  labs(title = "Bill length vs flipper length") +
  theme_bw() +
  theme(axis.text = element_text(size = 15)) +
  theme(axis.title = element_text(size = 18)) +
  theme(title = element_text(size = 20)) 
Warning: Removed 2 rows containing missing values or values outside the scale range
(`geom_point()`).

library(ggplot2)
ggplot(data = dat, 
       aes(x = flipper_length_mm,
           y = bill_length_mm)) + 
  geom_point(size = 5, 
             color = "cornflowerblue",
             shape = 16) +
  labs(x = "Flipper length (mm)") +
  labs(y = "Bill length (mm)") +
  labs(title = "Bill length vs flipper length") +
  theme_bw() +
  theme(axis.text = element_text(size = 15)) +
  theme(axis.title = element_text(size = 18)) +
  theme(title = element_text(size = 20)) 
Warning: Removed 2 rows containing missing values or values outside the scale range
(`geom_point()`).

library(ggplot2)
ggplot(data = dat) + 
  geom_point(aes(x = flipper_length_mm,
                 y = bill_length_mm),
             size = 5, 
             color = "cornflowerblue",
             shape = 16) +
  labs(x = "Flipper length (mm)") +
  labs(y = "Bill length (mm)") +
  labs(title = "Bill length vs flipper length") +
  theme_bw() +
  theme(axis.text = element_text(size = 15)) +
  theme(axis.title = element_text(size = 18)) +
  theme(title = element_text(size = 20)) 
Warning: Removed 2 rows containing missing values or values outside the scale range
(`geom_point()`).

library(ggplot2)
ggplot(data = dat) + 
  geom_point(aes(x = flipper_length_mm,
                 y = bill_length_mm),
             size = 5, 
             color = "cornflowerblue",
             shape = 16) +
  labs(x = "Flipper length (mm)",
       y = "Bill length (mm)",
       title = "Bill length vs flipper length") +
  theme_bw() +
  theme(axis.text = element_text(size = 15)) +
  theme(axis.title = element_text(size = 18)) +
  theme(title = element_text(size = 20)) 
Warning: Removed 2 rows containing missing values or values outside the scale range
(`geom_point()`).

library(ggplot2)
ggplot(data = dat) + 
  geom_point(aes(x = flipper_length_mm,
                 y = bill_length_mm),
             size = 5, 
             color = "cornflowerblue",
             shape = 16) +
  labs(x = "Flipper length (mm)",
       y = "Bill length (mm)",
       title = "Bill length vs flipper length") +
  theme_bw() +
  theme(axis.text = element_text(size = 15),
        axis.title = element_text(size = 18),
        title = element_text(size = 20)) 
Warning: Removed 2 rows containing missing values or values outside the scale range
(`geom_point()`).

Lessons learned

  • If the aes() is defined inside the ggplot() call, or separately, the subsequent geoms will inherit the definition (unless instructed not to). If it is defined inside the geom, it will only be visible to that layer.

Coloring/shaping by variable

library(ggplot2)
ggplot(data = dat) + 
  geom_point(aes(x = flipper_length_mm,
                 y = bill_length_mm, 
                 color = species),
             size = 5) + 
  scale_color_manual(
    values = c(Adelie = "darkorange", 
               Chinstrap = "purple", 
               Gentoo = "cyan4"),
    name = "Species"
  ) + 
  labs(x = "Flipper length (mm)",
       y = "Bill length (mm)",
       title = "Bill length vs flipper length") +
  theme_bw() +
  theme(axis.text = element_text(size = 15),
        axis.title = element_text(size = 18),
        title = element_text(size = 20),
        legend.text = element_text(size = 18)) 
Warning: Removed 2 rows containing missing values or values outside the scale range
(`geom_point()`).

library(ggplot2)
ggplot(data = dat) + 
  geom_point(aes(x = flipper_length_mm,
                 y = bill_length_mm, 
                 shape = species),
             size = 5) +
  scale_shape_manual(
    values = c(Adelie = 16, 
               Chinstrap = 6, 
               Gentoo = 3),
    name = "Species"
  ) +
  labs(x = "Flipper length (mm)",
       y = "Bill length (mm)",
       title = "Bill length vs flipper length") +
  theme_bw() +
  theme(axis.text = element_text(size = 15),
        axis.title = element_text(size = 18),
        title = element_text(size = 20),
        legend.text = element_text(size = 18)) 
Warning: Removed 2 rows containing missing values or values outside the scale range
(`geom_point()`).

Lessons learned

  • To color/size/shape the points (in this case) by a column in the data, we need to include the specification in the aes() call, as this instructs ggplot2 to fetch the information from the provided data set.
  • The scale_ family of functions can be used to set colors, shapes etc either manually, or by using a predefined palette (a set of colors). See e.g. scale_color_discrete(), scale_color_continuous(), scale_color_brewer(), scale_color_gradient(), …

Adding fixed lines

library(ggplot2)
ggplot(data = dat) + 
  geom_point(aes(x = flipper_length_mm,
                 y = bill_length_mm, 
                 color = species),
             size = 5) +
  scale_color_manual(
    values = c(Adelie = "darkorange", 
               Chinstrap = "purple", 
               Gentoo = "cyan4"),
    name = "Species"
  ) +
  theme_bw() + 
  geom_hline(yintercept = 42) + 
  geom_vline(xintercept = 205, linewidth = 2, 
             linetype = "dashed") + 
  geom_abline(slope = 0.25, intercept = -7, 
              color = "blue") 
Warning: Removed 2 rows containing missing values or values outside the scale range
(`geom_point()`).

Adding regression lines

library(ggplot2)
ggplot(data = dat,
       aes(x = flipper_length_mm,
           y = bill_length_mm)) + 
  geom_point(size = 5) +
  labs(x = "Flipper length (mm)",
       y = "Bill length (mm)",
       title = "Bill length vs flipper length") +
  theme_bw() +
  theme(axis.text = element_text(size = 15),
        axis.title = element_text(size = 18),
        title = element_text(size = 20),
        legend.text = element_text(size = 18)) + 
  geom_smooth(method = "lm", se = FALSE, linewidth = 2) 
`geom_smooth()` using formula = 'y ~ x'
Warning: Removed 2 rows containing non-finite outside the scale range
(`stat_smooth()`).
Warning: Removed 2 rows containing missing values or values outside the scale range
(`geom_point()`).

library(ggplot2)
ggplot(data = dat,
       aes(x = flipper_length_mm,
           y = bill_length_mm,
           shape = species)) + 
  geom_point(size = 5) +
  labs(x = "Flipper length (mm)",
       y = "Bill length (mm)",
       title = "Bill length vs flipper length") +
  theme_bw() +
  theme(axis.text = element_text(size = 15),
        axis.title = element_text(size = 18),
        title = element_text(size = 20),
        legend.text = element_text(size = 18)) + 
  geom_smooth(method = "lm", se = FALSE, linewidth = 2) 
`geom_smooth()` using formula = 'y ~ x'
Warning: Removed 2 rows containing non-finite outside the scale range
(`stat_smooth()`).
Warning: Removed 2 rows containing missing values or values outside the scale range
(`geom_point()`).

library(ggplot2)
ggplot(data = dat,
       aes(x = flipper_length_mm,
           y = bill_length_mm,
           color = species)) + 
  geom_point(size = 5) +
  labs(x = "Flipper length (mm)",
       y = "Bill length (mm)",
       title = "Bill length vs flipper length") +
  theme_bw() +
  theme(axis.text = element_text(size = 15),
        axis.title = element_text(size = 18),
        title = element_text(size = 20),
        legend.text = element_text(size = 18)) +
  geom_smooth(method = "lm", se = FALSE, linewidth = 2) 
`geom_smooth()` using formula = 'y ~ x'
Warning: Removed 2 rows containing non-finite outside the scale range
(`stat_smooth()`).
Warning: Removed 2 rows containing missing values or values outside the scale range
(`geom_point()`).

library(ggplot2)
ggplot(data = dat,
       aes(x = flipper_length_mm,
           y = bill_length_mm)) + 
  geom_point(aes(color = species),
             size = 5) +
  labs(x = "Flipper length (mm)",
       y = "Bill length (mm)",
       title = "Bill length vs flipper length") +
  theme_bw() +
  theme(axis.text = element_text(size = 15),
        axis.title = element_text(size = 18),
        title = element_text(size = 20),
        legend.text = element_text(size = 18)) +
  geom_smooth(method = "lm", se = FALSE, linewidth = 2) 
`geom_smooth()` using formula = 'y ~ x'
Warning: Removed 2 rows containing non-finite outside the scale range
(`stat_smooth()`).
Warning: Removed 2 rows containing missing values or values outside the scale range
(`geom_point()`).

Lessons learned

  • Coloring or shaping the points will automatically also group them, and the grouping is inherited following the same rules as other aesthetics.
  • To avoid aesthetics (e.g., such grouping) being inherited by other layers, specify the shape/color only in the layers where you want to apply it.
  • The order that the geom layers are specified in the expression is also the order in which they will be plotted - use this to your advantage if you would like one layer to appear ‘in front of’ or ‘behind’ another.

Line plots

  • Plots of time courses
library(ggplot2)
ggplot(data = dat) + 
  aes(x = index, 
      y = bill_length_mm) + 
  geom_line() +
  geom_point(size = 3)
Warning: Removed 2 rows containing missing values or values outside the scale range
(`geom_point()`).

library(ggplot2)
ggplot(data = dat) + 
  aes(x = index, 
      y = bill_length_mm) + 
  geom_line() +
  geom_point(data = dat |> 
               filter(species == "Adelie"),
             size = 3) 
Warning: Removed 1 row containing missing values or values outside the scale range
(`geom_point()`).

Box plots and violin plots

  • Plots of cluster-wise expression level distributions in single-cell data
  • QC plots - distribution of the number of reads, detected genes, … in single-cell data
  • Plots of overall intensity distributions
library(ggplot2)
ggplot(data = dat) + 
  aes(x = species, 
      y = bill_length_mm) + 
  geom_boxplot() + 
  theme_minimal() + 
  theme(axis.text = element_text(size = 15),
        axis.title = element_text(size = 18))
Warning: Removed 2 rows containing non-finite outside the scale range
(`stat_boxplot()`).

library(ggplot2)
ggplot(data = dat) + 
  aes(x = species, 
      y = bill_length_mm) + 
  geom_boxplot(outlier.size = -1) + 
  theme_minimal() + 
  theme(axis.text = element_text(size = 15),
        axis.title = element_text(size = 18)) + 
  geom_point(alpha = 0.5, 
             size = 2) 
Warning: Removed 2 rows containing non-finite outside the scale range
(`stat_boxplot()`).
Warning: Removed 2 rows containing missing values or values outside the scale range
(`geom_point()`).

library(ggplot2)
ggplot(data = dat) + 
  aes(x = species, 
      y = bill_length_mm) + 
  geom_boxplot(outlier.size = -1) + 
  theme_minimal() + 
  theme(axis.text = element_text(size = 15),
        axis.title = element_text(size = 18)) + 
  geom_jitter(alpha = 0.5, 
              size = 2,
              height = 0, 
              width = 0.25) 
Warning: Removed 2 rows containing non-finite outside the scale range
(`stat_boxplot()`).
Warning: Removed 2 rows containing missing values or values outside the scale range
(`geom_point()`).

library(ggplot2)
ggplot(data = dat) + 
  aes(x = species, 
      y = bill_length_mm) + 
  geom_violin() + 
  theme_minimal() + 
  theme(axis.text = element_text(size = 15),
        axis.title = element_text(size = 18)) + 
  geom_jitter(alpha = 0.5, 
              size = 2,
              height = 0, 
              width = 0.25) 
Warning: Removed 2 rows containing non-finite outside the scale range
(`stat_ydensity()`).
Warning: Removed 2 rows containing missing values or values outside the scale range
(`geom_point()`).

Exercise 2: Create the following plot

library(ggplot2)
ggplot(data = dat, 
       aes(x = species,
           y = bill_length_mm)) +
  geom_jitter(alpha = 0.5, height = 0,
              width = 0.25, color = "black") + 
  geom_boxplot(aes(fill = species),
               outlier.size = -1,
               alpha = 0.5) + 
  scale_fill_manual(
    values = c(Adelie = "darkorange", 
               Chinstrap = "purple", 
               Gentoo = "cyan4"),
    name = "Species"
  ) +
  labs(x = "Species",
       y = "Bill length (mm)",
       title = "Bill length vs Species") +
  theme_minimal() +
  theme(axis.text = element_text(size = 15),
        axis.title = element_text(size = 18),
        title = element_text(size = 20))
Warning: Removed 2 rows containing non-finite outside the scale range
(`stat_boxplot()`).
Warning: Removed 2 rows containing missing values or values outside the scale range
(`geom_point()`).

Bar plots

  • Plots comparing sizes of categories
  • Plots comparing summary values where there is a clear baseline
  • Distribution plots where only a few values are possible
  • Summary plots (mean +/- sd) - show individual points wherever possible!
library(ggplot2)
ggplot(data = dat |>
         slice(1:10)) + 
  aes(x = factor(index, 
                 levels = index),
      y = bill_length_mm) + 
  geom_bar(stat = "identity") + 
  labs(x = "Penguin", 
       y = "Bill length (mm)") + 
  theme_minimal() + 
  theme(axis.title = element_text(size = 18),
        axis.text = element_text(size = 15))
Warning: Removed 1 row containing missing values or values outside the scale range
(`geom_bar()`).

library(ggplot2)
ggplot(data = dat |>
         slice(1:10)) + 
  aes(x = factor(index, 
                 levels = index),
      y = bill_length_mm) + 
  geom_col() + 
  labs(x = "Penguin", 
       y = "Bill length (mm)") + 
  theme_minimal() + 
  theme(axis.title = element_text(size = 18),
        axis.text = element_text(size = 15)) 
Warning: Removed 1 row containing missing values or values outside the scale range
(`geom_col()`).

Lessons learned

  • By default, geom_bar() uses stat = "count", which counts the number of appearances of each category on the x-axis and displays this count on the y-axis. To display pre-calculated counts/values in a bar plot, either set stat = "identity" in geom_bar(), or use geom_col() (and in both cases, define which column to take the values from via the y aesthetic).
library(ggplot2)
ggplot(data = dat) + 
  aes(x = island) + 
  theme_minimal() + 
  theme(axis.title = element_text(size = 18),
        axis.text = element_text(size = 15)) + 
  geom_bar(stat = "count")

library(ggplot2)
ggplot(data = dat) + 
  aes(x = island, 
      fill = sex) + 
  theme_minimal() + 
  theme(axis.title = element_text(size = 18),
        axis.text = element_text(size = 15)) + 
  geom_bar(stat = "count") 

library(ggplot2)
ggplot(data = dat) + 
  aes(x = island, 
      fill = sex) + 
  theme_minimal() + 
  theme(axis.title = element_text(size = 18),
        axis.text = element_text(size = 15)) + 
  geom_bar(stat = "count", 
           position = "dodge") + 
  scale_fill_manual(
    values = c(male = "orangered",
               female = "cornflowerblue")
  ) 

Density plots

library(ggplot2)
ggplot(data = dat) + 
  aes(x = bill_length_mm) + 
  geom_density() + 
  theme_minimal() + 
  theme(axis.title = element_text(size = 18),
        axis.text = element_text(size = 15))
Warning: Removed 2 rows containing non-finite outside the scale range
(`stat_density()`).

library(ggplot2)
ggplot(data = dat) + 
  aes(x = bill_length_mm,
      fill = species) + 
  geom_density(alpha = 0.25) + 
  theme_minimal() + 
  theme(axis.title = element_text(size = 18),
        axis.text = element_text(size = 15))
Warning: Removed 2 rows containing non-finite outside the scale range
(`stat_density()`).

Facetting

library(ggplot2)
ggplot(data = dat) + 
  geom_point(aes(x = flipper_length_mm,
                 y = bill_length_mm, 
                 color = species),
             size = 5) + 
  scale_color_manual(
    values = c(Adelie = "darkorange", 
               Chinstrap = "purple", Gentoo = "cyan4"),
    name = "Species"
  ) + 
  labs(x = "Flipper length (mm)",
       y = "Bill length (mm)",
       title = "Bill length vs flipper length") +
  theme_bw() +
  theme(axis.text = element_text(size = 15),
        axis.title = element_text(size = 18),
        strip.text = element_text(size = 18),
        title = element_text(size = 20),
        legend.text = element_text(size = 18)) + 
  facet_wrap(~ sex, ncol = 1) 
Warning: Removed 2 rows containing missing values or values outside the scale range
(`geom_point()`).

library(ggplot2)
ggplot(data = dat) + 
  geom_point(aes(x = flipper_length_mm,
                 y = bill_length_mm, 
                 color = species),
             size = 5) + 
  scale_color_manual(
    values = c(Adelie = "darkorange", 
               Chinstrap = "purple", Gentoo = "cyan4"),
    name = "Species"
  ) + 
  labs(x = "Flipper length (mm)",
       y = "Bill length (mm)",
       title = "Bill length vs flipper length") +
  theme_bw() +
  theme(axis.text = element_text(size = 15),
        axis.title = element_text(size = 18),
        strip.text = element_text(size = 18),
        title = element_text(size = 20),
        legend.text = element_text(size = 18)) + 
  facet_grid(island ~ sex) 
Warning: Removed 2 rows containing missing values or values outside the scale range
(`geom_point()`).

Exercise 3: Create the following plot

library(ggplot2)
ggplot(data = dat |>
         filter(!is.na(sex)), 
       aes(x = bill_length_mm,
           fill = sex)) + 
  geom_density(alpha = 0.5) + 
  facet_wrap(~ species) +
  scale_fill_manual(
    values = c(male = "yellow",
               female = "forestgreen")) + 
  labs(x = "Bill length (mm)") + 
  theme_bw() + 
  theme(axis.text = element_text(size = 15),
        axis.title = element_text(size = 18),
        strip.text = element_text(size = 15))

Assigning plots to variables, and combining plots

library(ggplot2)
g1 <- ggplot(data = dat) + 
  geom_point(aes(x = flipper_length_mm, 
                 y = bill_length_mm,
                 color = species),
             size = 5) 
g1 
Warning: Removed 2 rows containing missing values or values outside the scale range
(`geom_point()`).

g1 + theme_minimal() 
Warning: Removed 2 rows containing missing values or values outside the scale range
(`geom_point()`).

g1 + theme_dark() + 
  scale_color_manual(
    values = c(Adelie = "darkorange", 
               Chinstrap = "purple", 
               Gentoo = "cyan4")) 
Warning: Removed 2 rows containing missing values or values outside the scale range
(`geom_point()`).

library(ggplot2)
g1 <- ggplot(data = dat) + 
  geom_point(aes(x = flipper_length_mm, 
                 y = bill_length_mm,
                 color = species),
             size = 3) 
g2 <- ggplot(data = dat) + 
  geom_point(aes(x = flipper_length_mm, 
                 y = bill_length_mm,
                 color = sex),
             size = 3) 
g3 <- cowplot::plot_grid(
  g1, g2, labels = c("A", "B"), 
  nrow = 1)
Warning: Removed 2 rows containing missing values or values outside the scale range
(`geom_point()`).
Removed 2 rows containing missing values or values outside the scale range
(`geom_point()`).
g3 

library(ggplot2)
g1 <- ggplot(data = dat) + 
  geom_point(aes(x = flipper_length_mm, 
                 y = bill_length_mm,
                 color = species),
             size = 3)
g2 <- ggplot(data = dat) + 
  geom_point(aes(x = flipper_length_mm, 
                 y = bill_length_mm,
                 color = sex),
             size = 3)
g3 <- cowplot::plot_grid(
  g1, g2, labels = c("A", "B"), 
  nrow = 1)
Warning: Removed 2 rows containing missing values or values outside the scale range
(`geom_point()`).
Removed 2 rows containing missing values or values outside the scale range
(`geom_point()`).
g4 <- ggplot(data = dat) + 
  geom_line(aes(x = index,
                y = bill_length_mm)) 
cowplot::plot_grid(
  g3, g4, labels = c("", "C"),
  ncol = 1) 

More resources

Session info

R version 4.4.1 (2024-06-14)
Platform: aarch64-apple-darwin20
Running under: macOS 15.0.1

Matrix products: default
BLAS:   /Library/Frameworks/R.framework/Versions/4.4-arm64/Resources/lib/libRblas.0.dylib 
LAPACK: /Library/Frameworks/R.framework/Versions/4.4-arm64/Resources/lib/libRlapack.dylib;  LAPACK version 3.12.0

locale:
[1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8

time zone: Europe/Zurich
tzcode source: internal

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

other attached packages:
 [1] lubridate_1.9.3  forcats_1.0.0    stringr_1.5.1    dplyr_1.1.4     
 [5] purrr_1.0.2      readr_2.1.5      tidyr_1.3.1      tibble_3.2.1    
 [9] tidyverse_2.0.0  ggplot2_3.5.1    BiocStyle_2.32.1 png_0.1-8       
[13] knitr_1.48      

loaded via a namespace (and not attached):
 [1] utf8_1.2.4          generics_0.1.3      lattice_0.22-6     
 [4] stringi_1.8.4       hms_1.1.3           digest_0.6.37      
 [7] magrittr_2.0.3      evaluate_1.0.1      timechange_0.3.0   
[10] fastmap_1.2.0       Matrix_1.7-0        jsonlite_1.8.9     
[13] BiocManager_1.30.25 mgcv_1.9-1          fansi_1.0.6        
[16] scales_1.3.0        cli_3.6.3           rlang_1.1.4        
[19] crayon_1.5.3        cowplot_1.1.3       splines_4.4.1      
[22] bit64_4.5.2         munsell_0.5.1       withr_3.0.1        
[25] yaml_2.3.10         tools_4.4.1         parallel_4.4.1     
[28] tzdb_0.4.0          colorspace_2.1-1    vctrs_0.6.5        
[31] R6_2.5.1            lifecycle_1.0.4     htmlwidgets_1.6.4  
[34] bit_4.5.0           vroom_1.6.5         pkgconfig_2.0.3    
[37] archive_1.1.9       pillar_1.9.0        gtable_0.3.5       
[40] glue_1.8.0          xfun_0.48           tidyselect_1.2.1   
[43] rstudioapi_0.16.0   farver_2.1.2        nlme_3.1-164       
[46] htmltools_0.5.8.1   rmarkdown_2.28      labeling_0.4.3     
[49] compiler_4.4.1