Putting It All Together: Advanced Plotting

ASI: Introduction to R

Dr Stevie Pederson

Black Ochre Data Labs
The Kids Research Institute Australia

September 3, 2025

Customising Figures
With ggplot2

Recap

  • Already learnt how to pipe data into ggplot
    • Can modify data ‘on the fly’
    • Don’t need multiple similar objects
  • Explored geom_point(), geom_col(), geom_boxplot()
    • Used scales to modify colour, shape etc
  • Tweaking plots for publication
    • Digging deeper with scales
    • Using theme() & ggsave()

Setup

  • Start a new script: AdvancedPlotting.R
library(palmerpenguins)
library(tidyverse)
theme_set(theme_bw())
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>

Custom Scales

  • Our previous plot (now with NA values)
penguins |>
  ggplot(
    aes(body_mass_g, flipper_length_mm)
  ) +
  geom_point(
    aes(colour = species, shape = sex), 
    size = 2
  ) +
  scale_colour_brewer(palette = "Set1") +
  scale_shape_manual(values = c(19, 1))

Custom Scales

  • We’ve set size as a fixed value outside of aes()
    • Will override any values set inside aes()
  • Notice that NA values are now in the legend but blank
    • We can set these by adding na.value inside scale_shape_manual()
    • Or apply dplyr::filter(!is.na(sex)) before passing to ggplot()
  • To ensure intended mappings we can name values

Custom Scales

penguins |>
  ggplot(
    aes(body_mass_g, flipper_length_mm)
  ) +
  geom_point(
    aes(colour = species, shape = sex), 
    size = 2
  ) +
  scale_colour_brewer(palette = "Set1") +
  scale_shape_manual(
    values = c(female = 19, male = 1), 
    na.value = 4
  )

Choosing Colours

  • Manual selection can also be applied to colours
  • The complete set of 657 named colours is in the vector colours()
colours() |> head(20)
 [1] "white"         "aliceblue"     "antiquewhite"  "antiquewhite1"
 [5] "antiquewhite2" "antiquewhite3" "antiquewhite4" "aquamarine"   
 [9] "aquamarine1"   "aquamarine2"   "aquamarine3"   "aquamarine4"  
[13] "azure"         "azure1"        "azure2"        "azure3"       
[17] "azure4"        "beige"         "bisque"        "bisque1"      

Choosing Colours

  • We can use str_subset() to find some options
    • Note the all named colours are previewed in the RStudio Script Window
colours() |> str_subset("blue")
 [1] "aliceblue"       "blue"            "blue1"           "blue2"          
 [5] "blue3"           "blue4"           "blueviolet"      "cadetblue"      
 [9] "cadetblue1"      "cadetblue2"      "cadetblue3"      "cadetblue4"     
[13] "cornflowerblue"  "darkblue"        "darkslateblue"   "deepskyblue"    
[17] "deepskyblue1"    "deepskyblue2"    "deepskyblue3"    "deepskyblue4"   
[21] "dodgerblue"      "dodgerblue1"     "dodgerblue2"     "dodgerblue3"    
[25] "dodgerblue4"     "lightblue"       "lightblue1"      "lightblue2"     
[29] "lightblue3"      "lightblue4"      "lightskyblue"    "lightskyblue1"  
[33] "lightskyblue2"   "lightskyblue3"   "lightskyblue4"   "lightslateblue" 
[37] "lightsteelblue"  "lightsteelblue1" "lightsteelblue2" "lightsteelblue3"
[41] "lightsteelblue4" "mediumblue"      "mediumslateblue" "midnightblue"   
[45] "navyblue"        "powderblue"      "royalblue"       "royalblue1"     
[49] "royalblue2"      "royalblue3"      "royalblue4"      "skyblue"        
[53] "skyblue1"        "skyblue2"        "skyblue3"        "skyblue4"       
[57] "slateblue"       "slateblue1"      "slateblue2"      "slateblue3"     
[61] "slateblue4"      "steelblue"       "steelblue1"      "steelblue2"     
[65] "steelblue3"      "steelblue4"     

Choosing Colours

penguins |>
  ggplot(
    aes(body_mass_g, flipper_length_mm)
  ) +
  geom_point(
    aes(colour = species, shape = sex), 
    size = 2
  ) +
  scale_colour_manual(
    values = c(
      Adelie = "indianred", 
      Chinstrap = "skyblue4",
      Gentoo = "forestgreen"
    )
  ) +
  scale_shape_manual(
    values = c(female = 19, male = 1), 
    na.value = 4
  )

Choosing Colours

  • These can be defined as vectors and passed to every figure
my_colours <- c(
  Adelie = "indianred", 
  Chinstrap = "skyblue4",
  Gentoo = "forestgreen"
)
penguins |>
  ggplot(
    aes(body_mass_g, flipper_length_mm)
  ) +
  geom_point(
    aes(colour = species, shape = sex), 
    size = 2
  ) +
  scale_colour_manual(values = my_colours) +
  scale_shape_manual(
    values = c(female = 19, male = 1), 
    na.value = 4
  )

RGB Colours

  • Colours can be defined using RGB format
  • Start with a # then two values for red (R), green (G) and blue (B)
    • Values are hexadecimal: 0, 1, 2,…, 9, A, B, C, D, E, F
    • Gives 256 increments for each channel
    • #000000 is all channels at zero \(\implies\) black
  • Also previewed in RStudio:
## Full value in the Red & Green Channels without any blue: Yellow
"#FFFF00"
## Dim green a little & add some blue
"#FFAA70"
  • Try changing a few values to get the hang of it

Tweaking Labels

  • female and male can be set to title-case using labels = str_to_title
    • labels takes a function and applies it to all labels
penguins |>
  ggplot(
    aes(body_mass_g, flipper_length_mm)
  ) +
  geom_point(
    aes(colour = species, shape = sex), size = 2
  ) +
  scale_colour_manual(values = my_colours) +
  scale_shape_manual(
    values = c(female = 19, male = 1), na.value = 4,
    labels = str_to_title
  )

Tweaking Labels

  • labels can take functions using \(x) some code modifying x
    • Known as inline functions
penguins |>
  ggplot(
    aes(body_mass_g, flipper_length_mm)
  ) +
  geom_point(
    aes(colour = species, shape = sex), size = 2
  ) +
  scale_colour_manual(values = my_colours) +
  scale_shape_manual(
    values = c(female = 19, male = 1), na.value = 4,
    labels = \(x) x |> str_to_title() |> str_extract("^[MF]")
  )

Axis and Scale Titles

  • Axis and Scale titles can be set using labs() or scale_*(name = )
  • Both break the association between the source data and title
penguins |>
  ggplot(
    aes(body_mass_g, flipper_length_mm)
  ) +
  geom_point(
    aes(colour = species, shape = sex), size = 2
  ) +
  scale_colour_manual(values = my_colours) +
  scale_shape_manual(
    values = c(female = 19, male = 1), na.value = 4,
    labels = \(x) x |> str_to_title() |> str_extract("^[MF]")
  ) +
  labs(
    x = "Body Mass (g)", y = "Flipper Length (mm)",
    colour = "Species", shape = "Sex"
  )

Themes

Themes

  • The theme() layer provides control over general attributes
    • Font sizes, colours, types etc
    • Axis lines, tick marks, labels
    • Plot borders, fills, gridlines
    • Legend position & types
  • Many attributes are set using elements:
    • element_text(), element_line(), element_rect()
    • element_blank()

Themes

  • Check the help page: ?theme 😱
  • Let’s save our plot as the object p \(\implies\) then modify themes
p <- penguins |>
  ggplot(aes(body_mass_g, flipper_length_mm)) +
  geom_point(
    aes(colour = species, shape = sex), size = 2
  ) +
  scale_colour_manual(values = my_colours) +
  scale_shape_manual(
    values = c(female = 19, male = 1), na.value = 4,
    labels = \(x) x |> str_to_title() |> str_extract("^[MF]")
  ) +
  labs(
    x = "Body Mass (g)", y = "Flipper Length (mm)",
    colour = "Species", shape = "Sex"
  ) +
  ggtitle("Palmer Penguins")

Controlling Text Appearance

  • Set font size using element_text() \(\implies\) titles, legends and axis text
## Set the global font size for better 
## readability in presentations and
## publications. Also try the arguments
## family = "serif"
## face = "italic"
p +
  theme(text = element_text(size = 16))

Controlling Text Appearance

  • Plot titles are left-aligned by default \(\implies\) use hjust
## `element_text()` can also be applied to 
## individual aspects, e.g. plot.title
## hjust controls horizontal adjustment
p +
  theme(
    text = element_text(size = 16),
    plot.title = element_text(hjust = 0.5)
  )

Controlling Text Appearance

  • Axis text can be rotated using angle (not great here)
## The defaults for text don't look great
## when rotating. Modifying placement using
## hjust = 1 (right-aligned) and
## vjust = 0.5 (centre aligned) helps.
## vjust/hjust are always with respect to 
## text direction
p +
  theme(
    text = element_text(size = 16),
    plot.title = element_text(hjust = 0.5),
    axis.text.x = element_text(
      angle = 90, hjust = 1, vjust = 0.5
    )
  )

Controlling Line Appearance

## `element_line()` applies to grid-lines
## using panel.grid.
## Setting linetype = 2 will produce
## dashed lines.
## Also try experimenting with colour if
## you like
p +
  theme(
    text = element_text(size = 16),
    plot.title = element_text(hjust = 0.5),
    panel.grid = element_line(linetype = 2)
  )

Removing Elements

## Grid lines can be removed using 
## `element_blank()`
p +
  theme(
    text = element_text(size = 16),
    plot.title = element_text(hjust = 0.5),
    panel.grid = element_blank()
  )

Modifying Legends

## Legend position can be set as
## "top", "right", "bottom", 
## "left", "none" or "inside"
p +
  theme(
    legend.position = "bottom"
  )

Modifying Legends

## If placing the legend "inside", the
## position is set using a vector with
## values c(x, y)
## The default is to place the centre of
## the legend at that position.
## Changing the justification from 
## c(0.5 ,0.5) to c(1, 0) 
## (i.e. right, bottom) tells theme which
## part of the legend is placed at the 
## co-ordinates
p +
  theme(
    legend.position = "inside",
    legend.position.inside = c(0.99, 0.01),
    legend.justification.inside = c(1, 0)
  )

Saving To Disk

  • The most common strategy is to call ggsave()
  • Will automatically save the most recent plot
?ggsave
  • File types are auto-detected from the suffix, e.g. "my_plot.png"
    • Writes to PDF, PNG, JPEG, TIFF, SVG, EPS etc
  • Width & Height are set using inches by default
    • Can be changed using units = "cm" or similar

Saving To Disk

  • May require multiple iterations getting font-sizes correct
  • Try save near the size you expect:
    • Screen presentation and journals my be different
    • Err on the side of large font sizes
## Save our last plot as a 7x7 inch PNG file
## Resolution can be set using dpi (default 300)
ggsave("my_plot.png", width = 7, height = 7)