Plotting data, part 2

with ggplot2

Aurélien Ginolhac

University of Luxembourg

Friday, the 2th of May, 2025

Scales catalogue

Scales for all

Functions are based on this scheme:

scale_{aes}_{type}()

Arguments to change default

  • breaks, choose where are labels
  • labels, usually with package scales
  • trans, to change to i.e log

tidyr crossing + glue glue_data trick

To generate all combinations

tidyr::expand_grid(aes = c("fill", "colour", "alpha", "x", "y"),
         type = c("continuous", "discrete", "date")) |>
  glue::glue_data("scale_{aes}_{type}()")
scale_fill_continuous()
scale_fill_discrete()
scale_fill_date()
scale_colour_continuous()
scale_colour_discrete()
scale_colour_date()
scale_alpha_continuous()
scale_alpha_discrete()
scale_alpha_date()
scale_x_continuous()
scale_x_discrete()
scale_x_date()
scale_y_continuous()
scale_y_discrete()
scale_y_date()
scales::label_percent()(c(0.4, 0.6, .95))
[1] "40%" "60%" "95%"
scales::label_comma()(20^(1:4))
[1] "20"      "400"     "8,000"   "160,000"
scales::breaks_log()(20^(1:3))
[1]    10    30   100   300  1000  3000 10000

Scales transformation

Starwars: who is the massive guy?

ggplot(starwars, aes(x = height, y = mass)) +
  geom_point(size = 2) # dataset from dplyr

ggplot(starwars, aes(x = height, y = mass)) +
  geom_point(alpha = 0.6, size = 2) +
  geom_text(data = \(x) filter(x, mass > 1000),
            aes(label = name), nudge_y = -0.1) +
  annotation_logticks(sides = "l") +
  scale_y_continuous(trans = "log10", guide =  guide_axis_logticks())

# worth testing pseudo_log

All geometries geom_ can take either a data.frame or an anonymous function, here geom_text().

Custom colors

Better: see Emil Hvitfeldt repo

Changing colours for categories

Default is scale_fill_hue()

filter(starwars, !is.na(gender)) |>
ggplot(aes(y = gender,
          fill = fct_lump_n(species, 8) |>
                 fct_infreq())) +
  geom_bar(position = "fill") +
  scale_fill_hue() +
  labs(fill = "top 8 Species\n(ranked)")

Rbrewer is a safe alternative

filter(starwars, !is.na(gender)) |>
ggplot(aes(y = gender,
          fill = fct_lump_n(species, 8) |>
                 fct_infreq())) +
  geom_bar(position = "fill") +
  scale_fill_brewer(palette = "Set1") +
  labs(fill = "top 8 Species\n(ranked)")

Predefined colour palettes

library(RColorBrewer)
par(mar = c(0, 4, 
            0, 0))
display.brewer.all()

Colour gradient, for continuous variables

The default gradient generated by ggplot2 is not very good… Better off using viridis

penguins |>
  ggplot(aes(x = bill_length_mm, 
             y = bill_depth_mm, 
             colour = body_mass_g)) +
  geom_point(alpha = 0.6, size = 5)

penguins |>
  ggplot(aes(x = bill_length_mm, y = bill_depth_mm, 
             colour = body_mass_g)) +
  geom_point(alpha = 0.6, size = 5) +
  scale_color_viridis_c()

(scale_color_viridis_c() c for continuous)

Viridis palettes

  • 8 different scales
  • Also for discrete variables
  • viridis is colour blind friendly and nice in b&w
  • In ggplot2 since v3.0 but no the default
penguins |>
  ggplot(aes(x = bill_length_mm, y = bill_depth_mm, 
             colour = species)) +
  geom_point(alpha = 0.6, size = 5) +
  scale_color_viridis_d()

Binning instead of gradient

Binning help grouping observations

  • Default blue gradient, viridis option
  • Number of bins, limits can be changed
penguins |>
  ggplot(aes(x = bill_length_mm, y = bill_depth_mm, 
             colour = body_mass_g)) +
  geom_point(alpha = 0.6, size = 5) +
  scale_color_binned(type = "viridis")

Merge similar guides

Size & colour on same variable ➡️ 2 guides

penguins |>
  ggplot(aes(x = bill_length_mm, y = bill_depth_mm, 
             colour = body_mass_g,
             size = body_mass_g)) +
  geom_point(alpha = 0.6) +
  scale_color_viridis_c()

guides() merge both aesthetics

penguins |>
  ggplot(aes(x = bill_length_mm, y = bill_depth_mm, 
             colour = body_mass_g, size = body_mass_g)) +
  geom_point(alpha = 0.6) +
  scale_color_viridis_c() +
  guides(colour = "legend")

Facets

Facets: facet_wrap()

Creating facets

  • Easiest way: facet_wrap()
  • Use vars(col_name)
  • facet_wrap() is for one var
ggplot(penguins, 
       aes(bill_depth_mm, 
           bill_length_mm)) +
  geom_point() +
  facet_wrap(vars(species))

Facets layout

Specify the number of rows/columns:

  • ncol = integer
  • nrow = integer
fc <- ggplot(penguins, 
       aes(bill_depth_mm, 
           bill_length_mm)) +
  geom_point()
fc + facet_wrap(vars(species), ncol = 2)

Facets, free scales

  • x or y (free_x / free_y)
fc + facet_wrap(vars(species), scales = "free_y")

Warning

Make comparison harder

  • Both axis
fc + facet_wrap(vars(species), scales = "free")

facet_grid() to lay out panels in a grid

Specify a 2 variables

ggplot(penguins, 
       aes(bill_depth_mm, 
           bill_length_mm)) +
  geom_point() +
  facet_grid(rows = vars(island), cols = vars(species))

Barplots in facets

Waste of space

ggplot(starwars) +
  geom_bar(aes(y = gender, fill = sex)) +
  facet_grid(rows = vars(fct_lump_min(species, 4))) +
  # alternative :
  #facet_wrap(dir = "v", ncol = 1L, strip.position = "right", vars(fct_lump_min(species, 4))) +
  labs(y = NULL)

Optimize by removing empty slots

ggplot(starwars) +
  geom_bar(aes(y = gender, fill = sex)) +
  facet_grid(rows = vars(fct_lump_min(species, 4)),
             space = "free", scales = "free_y") +
  labs(y = NULL)

Extensions

ggplot2 introduced the possibility for the community to create extensions, they are referenced on a dedicated site

Cosmetic 💅

Black theme

Using Bob Rudis hrbrthemes package

library(hrbrthemes)
ggplot(penguins,
       aes(x = bill_length_mm,
           y = bill_depth_mm,
           colour = species)) +
  geom_point() +
  geom_smooth(method = "lm", formula = 'y ~ x',
      # no standard error ribbon
              se = FALSE) +
  facet_grid(rows = vars(island)) +
  labs(x = "length (mm)", y = "depth (mm)",
       title = "Palmer penguins",
       subtitle = "bill dimensions over location and species",
       caption = "source: Horst AM, Hill AP, Gorman KB (2020)") +
  # hrbrthemes specifications
  scale_fill_ipsum() +
  theme_ft_rc(14) +
  # tweak the theme
  theme(panel.grid.major.y = element_blank(),
   panel.grid.major.x = element_line(size = 0.5),
   plot.caption = element_text(face = "italic"),
   strip.text = element_text(face = "bold"),
   plot.caption.position = "plot")

Compose plots with patchwork

Patchwork is developed by Thomas Lin Pedersen, main maintainer of ggplot2.

Define 3 plots and assign them names

p1 <- ggplot(penguins, 
             aes(x = species)) +
  geom_bar() +
  labs(title = "Species distribution")
p2 <- ggplot(penguins, 
             aes(y = island)) +
  geom_bar() +
  labs(title = "Island but flipped")
p3 <- ggplot(penguins, 
             aes(x = body_mass_g,
                 y = bill_depth_mm,
                 colour = sex)) +
  geom_point()
p1

p2

p3

Now, compose them!

Compose plots with patchwork

patchwork provides an API using the classic arithmetic operators

library(patchwork)
(( p1 | p2 ) / p3) +
  # add tags and main title
  plot_annotation(tag_levels = 'A',
                  title = 'Plots about penguins') &
  # modify all plots recursively
  theme_minimal() +
  theme(text = element_text('Roboto'))

Polished example by Cédric Scherer

Using patchwork, ggforce (T. Pedersen) and ggtext (Claus O. Wilke)

Polished penguins

culmen <- png::readPNG("img/plot_culmen_depth.png", native = TRUE)

Polished penguins

culmen <- png::readPNG("img/plot_culmen_depth.png", native = TRUE)
penguins
# A tibble: 344 × 8
   species island    bill_length_mm bill_depth_mm flipper_length_mm body_mass_g
   <fct>   <fct>              <dbl>         <dbl>             <int>       <int>
 1 Adelie  Torgersen           39.1          18.7               181        3750
 2 Adelie  Torgersen           39.5          17.4               186        3800
 3 Adelie  Torgersen           40.3          18                 195        3250
 4 Adelie  Torgersen           NA            NA                  NA          NA
 5 Adelie  Torgersen           36.7          19.3               193        3450
 6 Adelie  Torgersen           39.3          20.6               190        3650
 7 Adelie  Torgersen           38.9          17.8               181        3625
 8 Adelie  Torgersen           39.2          19.6               195        4675
 9 Adelie  Torgersen           34.1          18.1               193        3475
10 Adelie  Torgersen           42            20.2               190        4250
# ℹ 334 more rows
# ℹ 2 more variables: sex <fct>, year <int>

Polished penguins

culmen <- png::readPNG("img/plot_culmen_depth.png", native = TRUE)
penguins |>
  filter(across(ends_with("mm"), \(x) !is.na(x)))
# A tibble: 342 × 8
   species island    bill_length_mm bill_depth_mm flipper_length_mm body_mass_g
   <fct>   <fct>              <dbl>         <dbl>             <int>       <int>
 1 Adelie  Torgersen           39.1          18.7               181        3750
 2 Adelie  Torgersen           39.5          17.4               186        3800
 3 Adelie  Torgersen           40.3          18                 195        3250
 4 Adelie  Torgersen           36.7          19.3               193        3450
 5 Adelie  Torgersen           39.3          20.6               190        3650
 6 Adelie  Torgersen           38.9          17.8               181        3625
 7 Adelie  Torgersen           39.2          19.6               195        4675
 8 Adelie  Torgersen           34.1          18.1               193        3475
 9 Adelie  Torgersen           42            20.2               190        4250
10 Adelie  Torgersen           37.8          17.1               186        3300
# ℹ 332 more rows
# ℹ 2 more variables: sex <fct>, year <int>

Polished penguins

culmen <- png::readPNG("img/plot_culmen_depth.png", native = TRUE)
penguins |>
  filter(across(ends_with("mm"), \(x) !is.na(x))) |>
  ggplot(aes(x = bill_length_mm, y = bill_depth_mm))

Polished penguins

culmen <- png::readPNG("img/plot_culmen_depth.png", native = TRUE)
penguins |>
  filter(across(ends_with("mm"), \(x) !is.na(x))) |>
  ggplot(aes(x = bill_length_mm, y = bill_depth_mm)) +
  ggforce::geom_mark_ellipse(
    aes(fill = species, label = species), alpha = 0,
    show.legend = FALSE
  )

Polished penguins

culmen <- png::readPNG("img/plot_culmen_depth.png", native = TRUE)
penguins |>
  filter(across(ends_with("mm"), \(x) !is.na(x))) |>
  ggplot(aes(x = bill_length_mm, y = bill_depth_mm)) +
  ggforce::geom_mark_ellipse(
    aes(fill = species, label = species), alpha = 0,
    show.legend = FALSE
  ) +
  geom_point(aes(color = body_mass_g), alpha = .6, size = 3.5)

Polished penguins

culmen <- png::readPNG("img/plot_culmen_depth.png", native = TRUE)
penguins |>
  filter(across(ends_with("mm"), \(x) !is.na(x))) |>
  ggplot(aes(x = bill_length_mm, y = bill_depth_mm)) +
  ggforce::geom_mark_ellipse(
    aes(fill = species, label = species), alpha = 0,
    show.legend = FALSE
  ) +
  geom_point(aes(color = body_mass_g), alpha = .6, size = 3.5) +
  scale_x_continuous(breaks = seq(25, 65, by = 5), limits = c(25, 65))

Polished penguins

culmen <- png::readPNG("img/plot_culmen_depth.png", native = TRUE)
penguins |>
  filter(across(ends_with("mm"), \(x) !is.na(x))) |>
  ggplot(aes(x = bill_length_mm, y = bill_depth_mm)) +
  ggforce::geom_mark_ellipse(
    aes(fill = species, label = species), alpha = 0,
    show.legend = FALSE
  ) +
  geom_point(aes(color = body_mass_g), alpha = .6, size = 3.5) +
  scale_x_continuous(breaks = seq(25, 65, by = 5), limits = c(25, 65)) +
  scale_y_continuous(breaks = seq(12, 24, by = 2), limits = c(12, 24))

Polished penguins

culmen <- png::readPNG("img/plot_culmen_depth.png", native = TRUE)
penguins |>
  filter(across(ends_with("mm"), \(x) !is.na(x))) |>
  ggplot(aes(x = bill_length_mm, y = bill_depth_mm)) +
  ggforce::geom_mark_ellipse(
    aes(fill = species, label = species), alpha = 0,
    show.legend = FALSE
  ) +
  geom_point(aes(color = body_mass_g), alpha = .6, size = 3.5) +
  scale_x_continuous(breaks = seq(25, 65, by = 5), limits = c(25, 65)) +
  scale_y_continuous(breaks = seq(12, 24, by = 2), limits = c(12, 24)) +
  scale_color_viridis_c(option = "turbo", labels = scales::comma)

Polished penguins

culmen <- png::readPNG("img/plot_culmen_depth.png", native = TRUE)
penguins |>
  filter(across(ends_with("mm"), \(x) !is.na(x))) |>
  ggplot(aes(x = bill_length_mm, y = bill_depth_mm)) +
  ggforce::geom_mark_ellipse(
    aes(fill = species, label = species), alpha = 0,
    show.legend = FALSE
  ) +
  geom_point(aes(color = body_mass_g), alpha = .6, size = 3.5) +
  scale_x_continuous(breaks = seq(25, 65, by = 5), limits = c(25, 65)) +
  scale_y_continuous(breaks = seq(12, 24, by = 2), limits = c(12, 24)) +
  scale_color_viridis_c(option = "turbo", labels = scales::comma) +
  labs(
    title = "Bill Dimensions of Brush-Tailed Penguins (<i style='color:#28A87D;'>*Pygoscelis*</i>)",
    subtitle = 'A scatter plot of bill depth *versus* bill length.',
    caption = "Data: Gorman, Williams & Fraser (2014) *PLoS ONE*",
    x = "Bill Length (mm)",
    y = "Bill Depth (mm)",
    color = "Body mass (g)")

Polished penguins

culmen <- png::readPNG("img/plot_culmen_depth.png", native = TRUE)
penguins |>
  filter(across(ends_with("mm"), \(x) !is.na(x))) |>
  ggplot(aes(x = bill_length_mm, y = bill_depth_mm)) +
  ggforce::geom_mark_ellipse(
    aes(fill = species, label = species), alpha = 0,
    show.legend = FALSE
  ) +
  geom_point(aes(color = body_mass_g), alpha = .6, size = 3.5) +
  scale_x_continuous(breaks = seq(25, 65, by = 5), limits = c(25, 65)) +
  scale_y_continuous(breaks = seq(12, 24, by = 2), limits = c(12, 24)) +
  scale_color_viridis_c(option = "turbo", labels = scales::comma) +
  labs(
    title = "Bill Dimensions of Brush-Tailed Penguins (<i style='color:#28A87D;'>*Pygoscelis*</i>)",
    subtitle = 'A scatter plot of bill depth *versus* bill length.',
    caption = "Data: Gorman, Williams & Fraser (2014) *PLoS ONE*",
    x = "Bill Length (mm)",
    y = "Bill Depth (mm)",
    color = "Body mass (g)") +
  theme_minimal(base_family = "RobotoCondensed", base_size = 12)

Polished penguins

culmen <- png::readPNG("img/plot_culmen_depth.png", native = TRUE)
penguins |>
  filter(across(ends_with("mm"), \(x) !is.na(x))) |>
  ggplot(aes(x = bill_length_mm, y = bill_depth_mm)) +
  ggforce::geom_mark_ellipse(
    aes(fill = species, label = species), alpha = 0,
    show.legend = FALSE
  ) +
  geom_point(aes(color = body_mass_g), alpha = .6, size = 3.5) +
  scale_x_continuous(breaks = seq(25, 65, by = 5), limits = c(25, 65)) +
  scale_y_continuous(breaks = seq(12, 24, by = 2), limits = c(12, 24)) +
  scale_color_viridis_c(option = "turbo", labels = scales::comma) +
  labs(
    title = "Bill Dimensions of Brush-Tailed Penguins (<i style='color:#28A87D;'>*Pygoscelis*</i>)",
    subtitle = 'A scatter plot of bill depth *versus* bill length.',
    caption = "Data: Gorman, Williams & Fraser (2014) *PLoS ONE*",
    x = "Bill Length (mm)",
    y = "Bill Depth (mm)",
    color = "Body mass (g)") +
  theme_minimal(base_family = "RobotoCondensed", base_size = 12) +
  theme(plot.title = element_markdown(face = "bold"),
        plot.subtitle = element_markdown(),
        plot.caption = element_markdown(margin = margin(t = 15)),
        axis.title.x = element_markdown(),
        axis.title.y = element_markdown())

Polished penguins

culmen <- png::readPNG("img/plot_culmen_depth.png", native = TRUE)
penguins |>
  filter(across(ends_with("mm"), \(x) !is.na(x))) |>
  ggplot(aes(x = bill_length_mm, y = bill_depth_mm)) +
  ggforce::geom_mark_ellipse(
    aes(fill = species, label = species), alpha = 0,
    show.legend = FALSE
  ) +
  geom_point(aes(color = body_mass_g), alpha = .6, size = 3.5) +
  scale_x_continuous(breaks = seq(25, 65, by = 5), limits = c(25, 65)) +
  scale_y_continuous(breaks = seq(12, 24, by = 2), limits = c(12, 24)) +
  scale_color_viridis_c(option = "turbo", labels = scales::comma) +
  labs(
    title = "Bill Dimensions of Brush-Tailed Penguins (<i style='color:#28A87D;'>*Pygoscelis*</i>)",
    subtitle = 'A scatter plot of bill depth *versus* bill length.',
    caption = "Data: Gorman, Williams & Fraser (2014) *PLoS ONE*",
    x = "Bill Length (mm)",
    y = "Bill Depth (mm)",
    color = "Body mass (g)") +
  theme_minimal(base_family = "RobotoCondensed", base_size = 12) +
  theme(plot.title = element_markdown(face = "bold"),
        plot.subtitle = element_markdown(),
        plot.caption = element_markdown(margin = margin(t = 15)),
        axis.title.x = element_markdown(),
        axis.title.y = element_markdown()) +
  theme(plot.title.position = "plot")

Polished penguins

culmen <- png::readPNG("img/plot_culmen_depth.png", native = TRUE)
penguins |>
  filter(across(ends_with("mm"), \(x) !is.na(x))) |>
  ggplot(aes(x = bill_length_mm, y = bill_depth_mm)) +
  ggforce::geom_mark_ellipse(
    aes(fill = species, label = species), alpha = 0,
    show.legend = FALSE
  ) +
  geom_point(aes(color = body_mass_g), alpha = .6, size = 3.5) +
  scale_x_continuous(breaks = seq(25, 65, by = 5), limits = c(25, 65)) +
  scale_y_continuous(breaks = seq(12, 24, by = 2), limits = c(12, 24)) +
  scale_color_viridis_c(option = "turbo", labels = scales::comma) +
  labs(
    title = "Bill Dimensions of Brush-Tailed Penguins (<i style='color:#28A87D;'>*Pygoscelis*</i>)",
    subtitle = 'A scatter plot of bill depth *versus* bill length.',
    caption = "Data: Gorman, Williams & Fraser (2014) *PLoS ONE*",
    x = "Bill Length (mm)",
    y = "Bill Depth (mm)",
    color = "Body mass (g)") +
  theme_minimal(base_family = "RobotoCondensed", base_size = 12) +
  theme(plot.title = element_markdown(face = "bold"),
        plot.subtitle = element_markdown(),
        plot.caption = element_markdown(margin = margin(t = 15)),
        axis.title.x = element_markdown(),
        axis.title.y = element_markdown()) +
  theme(plot.title.position = "plot") +
  theme(plot.caption.position = "plot")

Polished penguins

culmen <- png::readPNG("img/plot_culmen_depth.png", native = TRUE)
penguins |>
  filter(across(ends_with("mm"), \(x) !is.na(x))) |>
  ggplot(aes(x = bill_length_mm, y = bill_depth_mm)) +
  ggforce::geom_mark_ellipse(
    aes(fill = species, label = species), alpha = 0,
    show.legend = FALSE
  ) +
  geom_point(aes(color = body_mass_g), alpha = .6, size = 3.5) +
  scale_x_continuous(breaks = seq(25, 65, by = 5), limits = c(25, 65)) +
  scale_y_continuous(breaks = seq(12, 24, by = 2), limits = c(12, 24)) +
  scale_color_viridis_c(option = "turbo", labels = scales::comma) +
  labs(
    title = "Bill Dimensions of Brush-Tailed Penguins (<i style='color:#28A87D;'>*Pygoscelis*</i>)",
    subtitle = 'A scatter plot of bill depth *versus* bill length.',
    caption = "Data: Gorman, Williams & Fraser (2014) *PLoS ONE*",
    x = "Bill Length (mm)",
    y = "Bill Depth (mm)",
    color = "Body mass (g)") +
  theme_minimal(base_family = "RobotoCondensed", base_size = 12) +
  theme(plot.title = element_markdown(face = "bold"),
        plot.subtitle = element_markdown(),
        plot.caption = element_markdown(margin = margin(t = 15)),
        axis.title.x = element_markdown(),
        axis.title.y = element_markdown()) +
  theme(plot.title.position = "plot") +
  theme(plot.caption.position = "plot") +
  theme(legend.position = "top")

Polished penguins

culmen <- png::readPNG("img/plot_culmen_depth.png", native = TRUE)
penguins |>
  filter(across(ends_with("mm"), \(x) !is.na(x))) |>
  ggplot(aes(x = bill_length_mm, y = bill_depth_mm)) +
  ggforce::geom_mark_ellipse(
    aes(fill = species, label = species), alpha = 0,
    show.legend = FALSE
  ) +
  geom_point(aes(color = body_mass_g), alpha = .6, size = 3.5) +
  scale_x_continuous(breaks = seq(25, 65, by = 5), limits = c(25, 65)) +
  scale_y_continuous(breaks = seq(12, 24, by = 2), limits = c(12, 24)) +
  scale_color_viridis_c(option = "turbo", labels = scales::comma) +
  labs(
    title = "Bill Dimensions of Brush-Tailed Penguins (<i style='color:#28A87D;'>*Pygoscelis*</i>)",
    subtitle = 'A scatter plot of bill depth *versus* bill length.',
    caption = "Data: Gorman, Williams & Fraser (2014) *PLoS ONE*",
    x = "Bill Length (mm)",
    y = "Bill Depth (mm)",
    color = "Body mass (g)") +
  theme_minimal(base_family = "RobotoCondensed", base_size = 12) +
  theme(plot.title = element_markdown(face = "bold"),
        plot.subtitle = element_markdown(),
        plot.caption = element_markdown(margin = margin(t = 15)),
        axis.title.x = element_markdown(),
        axis.title.y = element_markdown()) +
  theme(plot.title.position = "plot") +
  theme(plot.caption.position = "plot") +
  theme(legend.position = "top") +
  guides(color = guide_colorbar(title.position = "top",
                                title.hjust = .5,
                                barwidth = unit(20, "lines"),
                                barheight = unit(.5, "lines")))

Polished penguins

culmen <- png::readPNG("img/plot_culmen_depth.png", native = TRUE)
penguins |>
  filter(across(ends_with("mm"), \(x) !is.na(x))) |>
  ggplot(aes(x = bill_length_mm, y = bill_depth_mm)) +
  ggforce::geom_mark_ellipse(
    aes(fill = species, label = species), alpha = 0,
    show.legend = FALSE
  ) +
  geom_point(aes(color = body_mass_g), alpha = .6, size = 3.5) +
  scale_x_continuous(breaks = seq(25, 65, by = 5), limits = c(25, 65)) +
  scale_y_continuous(breaks = seq(12, 24, by = 2), limits = c(12, 24)) +
  scale_color_viridis_c(option = "turbo", labels = scales::comma) +
  labs(
    title = "Bill Dimensions of Brush-Tailed Penguins (<i style='color:#28A87D;'>*Pygoscelis*</i>)",
    subtitle = 'A scatter plot of bill depth *versus* bill length.',
    caption = "Data: Gorman, Williams & Fraser (2014) *PLoS ONE*",
    x = "Bill Length (mm)",
    y = "Bill Depth (mm)",
    color = "Body mass (g)") +
  theme_minimal(base_family = "RobotoCondensed", base_size = 12) +
  theme(plot.title = element_markdown(face = "bold"),
        plot.subtitle = element_markdown(),
        plot.caption = element_markdown(margin = margin(t = 15)),
        axis.title.x = element_markdown(),
        axis.title.y = element_markdown()) +
  theme(plot.title.position = "plot") +
  theme(plot.caption.position = "plot") +
  theme(legend.position = "top") +
  guides(color = guide_colorbar(title.position = "top",
                                title.hjust = .5,
                                barwidth = unit(20, "lines"),
                                barheight = unit(.5, "lines"))) +
  coord_cartesian(expand = FALSE, clip = "off")

Polished penguins

culmen <- png::readPNG("img/plot_culmen_depth.png", native = TRUE)
penguins |>
  filter(across(ends_with("mm"), \(x) !is.na(x))) |>
  ggplot(aes(x = bill_length_mm, y = bill_depth_mm)) +
  ggforce::geom_mark_ellipse(
    aes(fill = species, label = species), alpha = 0,
    show.legend = FALSE
  ) +
  geom_point(aes(color = body_mass_g), alpha = .6, size = 3.5) +
  scale_x_continuous(breaks = seq(25, 65, by = 5), limits = c(25, 65)) +
  scale_y_continuous(breaks = seq(12, 24, by = 2), limits = c(12, 24)) +
  scale_color_viridis_c(option = "turbo", labels = scales::comma) +
  labs(
    title = "Bill Dimensions of Brush-Tailed Penguins (<i style='color:#28A87D;'>*Pygoscelis*</i>)",
    subtitle = 'A scatter plot of bill depth *versus* bill length.',
    caption = "Data: Gorman, Williams & Fraser (2014) *PLoS ONE*",
    x = "Bill Length (mm)",
    y = "Bill Depth (mm)",
    color = "Body mass (g)") +
  theme_minimal(base_family = "RobotoCondensed", base_size = 12) +
  theme(plot.title = element_markdown(face = "bold"),
        plot.subtitle = element_markdown(),
        plot.caption = element_markdown(margin = margin(t = 15)),
        axis.title.x = element_markdown(),
        axis.title.y = element_markdown()) +
  theme(plot.title.position = "plot") +
  theme(plot.caption.position = "plot") +
  theme(legend.position = "top") +
  guides(color = guide_colorbar(title.position = "top",
                                title.hjust = .5,
                                barwidth = unit(20, "lines"),
                                barheight = unit(.5, "lines"))) +
  coord_cartesian(expand = FALSE, clip = "off") +
  theme(plot.margin = margin(t = 25, r = 25, b = 10, l = 25))

Polished penguins

culmen <- png::readPNG("img/plot_culmen_depth.png", native = TRUE)
penguins |>
  filter(across(ends_with("mm"), \(x) !is.na(x))) |>
  ggplot(aes(x = bill_length_mm, y = bill_depth_mm)) +
  ggforce::geom_mark_ellipse(
    aes(fill = species, label = species), alpha = 0,
    show.legend = FALSE
  ) +
  geom_point(aes(color = body_mass_g), alpha = .6, size = 3.5) +
  scale_x_continuous(breaks = seq(25, 65, by = 5), limits = c(25, 65)) +
  scale_y_continuous(breaks = seq(12, 24, by = 2), limits = c(12, 24)) +
  scale_color_viridis_c(option = "turbo", labels = scales::comma) +
  labs(
    title = "Bill Dimensions of Brush-Tailed Penguins (<i style='color:#28A87D;'>*Pygoscelis*</i>)",
    subtitle = 'A scatter plot of bill depth *versus* bill length.',
    caption = "Data: Gorman, Williams & Fraser (2014) *PLoS ONE*",
    x = "Bill Length (mm)",
    y = "Bill Depth (mm)",
    color = "Body mass (g)") +
  theme_minimal(base_family = "RobotoCondensed", base_size = 12) +
  theme(plot.title = element_markdown(face = "bold"),
        plot.subtitle = element_markdown(),
        plot.caption = element_markdown(margin = margin(t = 15)),
        axis.title.x = element_markdown(),
        axis.title.y = element_markdown()) +
  theme(plot.title.position = "plot") +
  theme(plot.caption.position = "plot") +
  theme(legend.position = "top") +
  guides(color = guide_colorbar(title.position = "top",
                                title.hjust = .5,
                                barwidth = unit(20, "lines"),
                                barheight = unit(.5, "lines"))) +
  coord_cartesian(expand = FALSE, clip = "off") +
  theme(plot.margin = margin(t = 25, r = 25, b = 10, l = 25)) +
  labs(caption = "Data: Gorman, Williams & Fraser (2014) *PLoS ONE* &bull; Illustration: Allison Horst")

Polished penguins

culmen <- png::readPNG("img/plot_culmen_depth.png", native = TRUE)
penguins |>
  filter(across(ends_with("mm"), \(x) !is.na(x))) |>
  ggplot(aes(x = bill_length_mm, y = bill_depth_mm)) +
  ggforce::geom_mark_ellipse(
    aes(fill = species, label = species), alpha = 0,
    show.legend = FALSE
  ) +
  geom_point(aes(color = body_mass_g), alpha = .6, size = 3.5) +
  scale_x_continuous(breaks = seq(25, 65, by = 5), limits = c(25, 65)) +
  scale_y_continuous(breaks = seq(12, 24, by = 2), limits = c(12, 24)) +
  scale_color_viridis_c(option = "turbo", labels = scales::comma) +
  labs(
    title = "Bill Dimensions of Brush-Tailed Penguins (<i style='color:#28A87D;'>*Pygoscelis*</i>)",
    subtitle = 'A scatter plot of bill depth *versus* bill length.',
    caption = "Data: Gorman, Williams & Fraser (2014) *PLoS ONE*",
    x = "Bill Length (mm)",
    y = "Bill Depth (mm)",
    color = "Body mass (g)") +
  theme_minimal(base_family = "RobotoCondensed", base_size = 12) +
  theme(plot.title = element_markdown(face = "bold"),
        plot.subtitle = element_markdown(),
        plot.caption = element_markdown(margin = margin(t = 15)),
        axis.title.x = element_markdown(),
        axis.title.y = element_markdown()) +
  theme(plot.title.position = "plot") +
  theme(plot.caption.position = "plot") +
  theme(legend.position = "top") +
  guides(color = guide_colorbar(title.position = "top",
                                title.hjust = .5,
                                barwidth = unit(20, "lines"),
                                barheight = unit(.5, "lines"))) +
  coord_cartesian(expand = FALSE, clip = "off") +
  theme(plot.margin = margin(t = 25, r = 25, b = 10, l = 25)) +
  labs(caption = "Data: Gorman, Williams & Fraser (2014) *PLoS ONE* &bull; Illustration: Allison Horst") +
  patchwork::inset_element(culmen, left = 0.82, bottom = 0.83, right = 1, top = 1, align_to = 'full')

Text paths, curved labels in data

library(geomtextpath)
ggplot(penguins,
       aes(x = bill_length_mm, y = bill_depth_mm, colour = species)) +
  geom_point(alpha = 0.3) +
  stat_ellipse(aes(label = species),
    geom = "textpath", hjust = c(0.55)) +
  theme_bw(14) + theme(legend.position = "none")

ggplot(penguins,
       aes(x = bill_length_mm / bill_depth_mm, 
           colour = species)) +
  stat_density(aes(label = species),
    geom = "textpath", hjust = c(0.25)) +
  theme_bw(14) + theme(legend.position = "none")

Exporting, interactive or passive mode

Right panel

  • Using the Export button in the Plots panel

ggsave

  • Save the ggplot object, 2nd argument, here p or last_plot()
  • Guesses the output type by the extension (jpg, png, pdf etc.)
ggsave("my_name.png", p, width = 60, height = 30, units = "mm")
ggsave("my_name.pdf", last_plot(), width = 50, height = 50, units = "mm")

Quarto docs

  • If needed, adjust the chunk options:
    • Size: fig-height, fig-width

Plot your data!

Never trust summary statistics alone; always visualize your data

Alberto Cairo

Missing features

Geoms list here

  • geom_tile() heatmap
  • geom_bind2d() 2D binning
  • geom_abline() slope

Stats list here

  • stat_ellipse() (ggforge seen)
  • stat_summary() easy mean, 95CI etc.

Plot on multi-pages

  • ggforce::facet_grid_paginate() facets
  • gridExtra::marrangeGrob() plots

Coordinate / transform

  • coord_cartesian() for zooming in

Customise theme elements

  • Legend & guide tweaks
  • Major/minor grids
  • Font, faces
  • Margins
  • Labels & ticks
  • Strip positions
  • See live examples of pre-built themes

Build your plot with point and click

Using esquisse Rstudio addin by dreamRs

Before we stop

You learned to:

  • Apprehend facets
  • Color schemes and gradients
  • Discover extensions
  • The endless potential of cosmetic improvements

Further reading 📚

Thank you for your attention!