Risky Viz!

Cara R Thompson, PhD | R!SK Conference 2026

Hello there 👋

  • 👩🏻‍💻 Cara Thompson, Data Visualisation Consultant
  • 🗺️ Edinburgh, Scotland, near the zoo 🐧🐧🐧
  • 💬 5 risks and 10 solutions

What’s the story?

  • Three new species of penguins are
    moving into Edinburgh zoo 🧳
  • The keepers are scared of penguins with long beaks 😱
  • Penguins with long flippers take longer to settle in ❤️‍🩹

They are arriving in batches. Help us visualise the risks in each batch so we can prepare well for their arrival.

#1 Everything is super important all the time!

*Everything is super important all the time

Fix 1: What’s the focus?

What do the users need to know?

Fix 1: What’s the focus?

What do the users need to know?

penguins |>
  ggplot() +
  ggbeeswarm::geom_quasirandom(
    aes(
      x = bill_len,
      y = species,
      shape = species
    ),
    size = 4
  ) +
  labs(x = "Beak length") +
  scale_x_continuous(label = function(x) paste0(x, "mm")) +
  scale_shape(guide = "none") +
  theme(axis.title.y = element_blank())

Fix 1: What’s the focus?

What do the users need to know?

penguins |>
  ggplot() +
  annotate(
    "rect",
    xmin = 50,
    xmax = Inf,
    ymin = -Inf,
    ymax = Inf,
    fill = alpha("red", 0.3)
  ) +
  ggtext::geom_textbox(
    data = data.frame(),
    aes(x = 50, y = Inf, label = "*Beaks > 50 mm*"),
    fill = NA,
    box.colour = NA,
    hjust = 0,
    vjust = 1
  ) +
  ggbeeswarm::geom_quasirandom(
    aes(
      x = bill_len,
      y = species,
      shape = species
    ),
    size = 4
  ) +
  labs(x = "Beak length") +
  scale_x_continuous(label = function(x) paste0(x, "mm")) +
  scale_shape(guide = "none") +
  theme(axis.title.y = element_blank())

Fix 1: What’s the focus?

What do the users need to know?

penguins |>
  ggplot() +
  annotate(
    "rect",
    xmin = 50,
    xmax = Inf,
    ymin = -Inf,
    ymax = Inf,
    fill = alpha("red", 0.3)
  ) +
  ggtext::geom_textbox(
    data = data.frame(),
    aes(x = 50, y = Inf, label = "*Beaks > 50 mm*"),
    fill = NA,
    box.colour = NA,
    hjust = 0,
    vjust = 1
  ) +
  ggbeeswarm::geom_quasirandom(
    aes(
      x = bill_len,
      y = species,
      shape = species,
      colour = flipper_len
    ),
    size = 4
  ) +
  labs(x = "Beak length") +
  scale_x_continuous(label = function(x) paste0(x, "mm")) +
  scale_colour_gradient2(
    low = "#008000",
    mid = "orange",
    high = "red"
  ) +
  scale_shape(guide = "none") +
  theme(axis.title.y = element_blank())

Fix 1: What’s the focus?

What do the users need to know?

penguins |>
  ggplot() +
  annotate(
    "rect",
    xmin = 50,
    xmax = Inf,
    ymin = -Inf,
    ymax = Inf,
    fill = alpha("red", 0.3)
  ) +
  ggtext::geom_textbox(
    data = data.frame(),
    aes(x = 50, y = Inf, label = "*Beaks > 50 mm*"),
    fill = NA,
    box.colour = NA,
    hjust = 0,
    vjust = 1
  ) +
  ggbeeswarm::geom_quasirandom(
    aes(
      x = bill_len,
      y = species,
      shape = species,
      colour = flipper_len
    ),
    size = 4
  ) +
  labs(x = "Beak length") +
  scale_x_continuous(label = function(x) paste0(x, "mm")) +
  scale_colour_gradient2(
    low = "#008000",
    mid = "orange",
    high = "red",
    midpoint = mean(penguins$flipper_len, na.rm = TRUE)
  ) +
  scale_shape(guide = "none") +
  theme(axis.title.y = element_blank())

Fix 2: Colour accessibility

It looked nice but…

Fix 2: Colour accessibility

👉 vis4.net/palettes

Fix 2: Colour accessibility

Mute your colours!

Fix 2: Colour accessibility

And check what it looks like to others

Fix 2: Colour accessibility

And check what it looks like to others

Fix 2: Colour accessibility

And check what it looks like to others

Fix 2: Colour accessibility

Let’s take a look!

penguins |>
  ggplot() +
  annotate(
    "rect",
    xmin = 50,
    xmax = Inf,
    ymin = -Inf,
    ymax = Inf,
    fill = alpha("red", 0.3)
  ) +
  ggtext::geom_textbox(
    data = data.frame(),
    aes(x = 50, y = Inf, label = "*Beaks > 50 mm*"),
    fill = NA,
    box.colour = NA,
    hjust = 0,
    vjust = 1
  ) +
  ggbeeswarm::geom_quasirandom(
    aes(
      x = bill_len,
      y = species,
      shape = species,
      colour = flipper_len
    ),
    size = 4
  ) +
  labs(x = "Beak length") +
  scale_x_continuous(label = function(x) paste0(x, "mm")) +
  scale_shape(guide = "none") +
  scale_colour_gradient2(
    low = "#a5dc94",
    mid = "#edac59",
    high = "#e0361d",
    midpoint = mean(penguins$flipper_len, na.rm = TRUE)
  ) +
  theme(axis.title.y = element_blank())

Fix 2: Colour accessibility

Let’s take a look!

penguins |>
  ggplot() +
  annotate(
    "rect",
    xmin = 50,
    xmax = Inf,
    ymin = -Inf,
    ymax = Inf,
    fill = alpha("red", 0.3)
  ) +
  ggtext::geom_textbox(
    data = data.frame(),
    aes(x = 50, y = Inf, label = "*Beaks > 50 mm*"),
    fill = NA,
    box.colour = NA,
    hjust = 0,
    vjust = 1
  ) +
  ggbeeswarm::geom_quasirandom(
    aes(
      x = bill_len,
      y = species,
      shape = species,
      colour = flipper_len
    ),
    size = 4
  ) +
  labs(x = "Beak length") +
  scale_x_continuous(label = function(x) paste0(x, "mm")) +
  scale_shape(guide = "none") +
  scale_colour_gradient2(
    low = "#a5dc94",
    mid = "#a7a7a7",
    high = "#e0361d",
    midpoint = mean(penguins$flipper_len, na.rm = TRUE)
  ) +
  theme(axis.title.y = element_blank())

wall of text wall of text wall of text wall of text wall of text wall of text wall of text wall of text wall of text wall of text wall of text wall of text

#2 No one reads the text

wall of text wall of text wall of text wall of text wall of text wall of text wall of text wall of text oh look a squirrel wall of text wall of text wall of text wall of text wall of text wall of text wall of text wall of text wall of text wall of text wall of text wall of text wall of text wall of text wall of text wall of text wall of text wall of text wall of text wall of text

Fix 3: Text hierarchy

Our starting point

penguins |>
  ggplot() +
  annotate(
    "rect",
    xmin = 50,
    xmax = Inf,
    ymin = -Inf,
    ymax = Inf,
    fill = alpha("red", 0.3)
  ) +
  ggtext::geom_textbox(
    data = data.frame(),
    aes(x = 50, y = Inf, label = "*Beaks > 50 mm*"),
    fill = NA,
    box.colour = NA,
    hjust = 0,
    vjust = 1
  ) +
  ggbeeswarm::geom_quasirandom(
    aes(
      x = bill_len,
      y = species,
      shape = species,
      colour = flipper_len
    ),
    size = 4
  ) +
  labs(
    x = "Beak length",
    title = paste0("We are about to welcome ", nrow(penguins), " new penguins"),
    subtitle = paste0(
      "There are ",
      verbaliseR::pluralise(
        sum(penguins$bill_len > 50, na.rm = TRUE),
        word = "penguin"
      ),
      " with scary beaks. Please look out for the ones on the red (darker) end of the spectrum; they have longer flippers so will need more support settling in."
    ),
    colour = "Predicted need for support"
  ) +
  scale_x_continuous(label = function(x) paste0(x, "mm")) +
  scale_shape(guide = "none") +
  scale_colour_gradient2(
    low = "#a5dc94",
    mid = "#a7a7a7",
    high = "#e0361d",
    midpoint = mean(penguins$flipper_len, na.rm = TRUE),
    limits = c(
      min(penguins$flipper_len, na.rm = TRUE),
      max(penguins$flipper_len, na.rm = TRUE)
    ),
    breaks = c(
      min(penguins$flipper_len, na.rm = TRUE),
      max(penguins$flipper_len, na.rm = TRUE)
    ),
    labels = c("low", "high")
  ) +
  theme(
    plot.subtitle = marquee::element_marquee(width = 1),
    axis.title.y = element_blank(),
    legend.position = "top",
    legend.title.position = "top",
    legend.justification = 0,
    legend.key.width = unit(3.3, "lines")
  )

Fix 3: Text hierarchy

The magic of typography ✨

penguins |>
  ggplot() +
  annotate(
    "rect",
    xmin = 50,
    xmax = Inf,
    ymin = -Inf,
    ymax = Inf,
    fill = alpha("red", 0.3)
  ) +
  ggtext::geom_textbox(
    data = data.frame(),
    aes(x = 50, y = Inf, label = "Beaks > 50 mm"),
    fill = NA,
    box.colour = NA,
    hjust = 0,
    vjust = 1,
    box.padding = unit(10, "pt"),
    family = "Noah",
    colour = "#3b4046",
    size = 5
  ) +
  ggbeeswarm::geom_quasirandom(
    aes(
      x = bill_len,
      y = species,
      shape = species,
      colour = flipper_len
    ),
    size = 4
  ) +
  labs(
    x = "Beak length",
    title = paste0(
      "We are about to welcome ",
      nrow(penguins),
      " penguins"
    ),
    subtitle = paste0(
      "There are **",
      verbaliseR::pluralise(
        sum(penguins$bill_len > 50, na.rm = TRUE),
        word = "penguin"
      ),
      " with scary beaks**. Please look out for the ones on the red (darker) end of the spectrum; they have longer flippers so will need more support settling in."
    ),
    colour = "Predicted need for support"
  ) +
  scale_x_continuous(label = function(x) paste0(x, "mm")) +
  scale_shape(guide = "none") +
  scale_colour_gradient2(
    low = "#a5dc94",
    mid = "#a7a7a7",
    high = "#e0361d",
    midpoint = mean(penguins$flipper_len, na.rm = TRUE),
    limits = c(
      min(penguins$flipper_len, na.rm = TRUE),
      max(penguins$flipper_len, na.rm = TRUE)
    ),
    breaks = c(
      min(penguins$flipper_len, na.rm = TRUE),
      max(penguins$flipper_len, na.rm = TRUE)
    ),
    labels = c("low", "high")
  ) +
  theme(
    text = element_text(family = "Noah", colour = "#3b4046"),
    plot.subtitle = marquee::element_marquee(
      width = 1,
      family = "Noah",
      size = rel(1.2)
    ),
    plot.title = element_text(
      face = "bold",
      size = rel(1.6),
      colour = "#1A242F"
    ),
    legend.title = element_text(hjust = 0.5),
    axis.title.y = element_blank(),
    panel.grid = element_line(colour = "white"),
    plot.background = element_rect(fill = "#fafafa", colour = "#fafafa"),
    plot.margin = margin_auto(40),
    axis.title.x = element_text(hjust = 1, margin = margin(10, 0, 0, 0)),
    legend.position = "top",
    plot.title.position = "plot",
    legend.title.position = "top",
    legend.justification = 0.5,
    legend.key.width = unit(3.6, "lines")
  )

Fix 4: Check your colour contrasts

Fix 4: Check your colour contrasts

Fix 4: Check your colour contrasts

Fix 4: Check your colour contrasts

Starting point

penguins |>
  ggplot() +
  annotate(
    "rect",
    xmin = 50,
    xmax = Inf,
    ymin = -Inf,
    ymax = Inf,
    fill = alpha("red", 0.3)
  ) +
  ggtext::geom_textbox(
    data = data.frame(),
    aes(x = 50, y = Inf, label = "Beaks > 50 mm"),
    fill = NA,
    box.colour = NA,
    hjust = 0,
    vjust = 1,
    box.padding = unit(10, "pt"),
    family = "Noah",
    colour = "#3b4046",
    size = 5
  ) +
  ggbeeswarm::geom_quasirandom(
    aes(
      x = bill_len,
      y = species,
      shape = species,
      colour = flipper_len
    ),
    size = 4
  ) +
  labs(
    x = "Beak length",
    title = paste0(
      "We are about to welcome ",
      nrow(penguins),
      " penguins"
    ),
    subtitle = paste0(
      "There are **",
      verbaliseR::pluralise(
        sum(penguins$bill_len > 50, na.rm = TRUE),
        word = "penguin"
      ),
      " with scary beaks**. Please look out for the ones on the red (darker) end of the spectrum; they have longer flippers so will need more support settling in."
    ),
    colour = "Predicted need for support"
  ) +
  scale_x_continuous(label = function(x) paste0(x, "mm")) +
  scale_shape(guide = "none") +
  scale_colour_gradient2(
    low = "#a5dc94",
    mid = "#a7a7a7",
    high = "#e0361d",
    midpoint = mean(penguins$flipper_len, na.rm = TRUE),
    limits = c(
      min(penguins$flipper_len, na.rm = TRUE),
      max(penguins$flipper_len, na.rm = TRUE)
    ),
    breaks = c(
      min(penguins$flipper_len, na.rm = TRUE),
      max(penguins$flipper_len, na.rm = TRUE)
    ),
    labels = c("low", "high")
  ) +
  theme(
    text = element_text(family = "Noah", colour = "#3b4046"),
    plot.subtitle = marquee::element_marquee(
      width = 1,
      family = "Noah",
      size = rel(1.2)
    ),
    plot.title = element_text(
      face = "bold",
      size = rel(1.6),
      colour = "#1A242F"
    ),
    legend.title = element_text(hjust = 0.5),
    axis.title.y = element_blank(),
    panel.grid = element_line(colour = "white"),
    plot.background = element_rect(fill = "#fafafa", colour = "#fafafa"),
    plot.margin = margin_auto(40),
    axis.title.x = element_text(hjust = 1, margin = margin(10, 0, 0, 0)),
    legend.position = "top",
    plot.title.position = "plot",
    legend.title.position = "top",
    legend.justification = 0.5,
    legend.key.width = unit(3.6, "lines")
  )

Fix 4: Check your colour contrasts

Better in several ways!

penguins |>
  ggplot() +
  annotate(
    "rect",
    xmin = 50,
    xmax = Inf,
    ymin = -Inf,
    ymax = Inf,
    fill = alpha("skyblue", 0.3)
  ) +
  geom_vline(xintercept = 50, colour = "#2c3d4f", linewidth = 0.2) +
  ggtext::geom_textbox(
    data = data.frame(),
    aes(x = 50, y = Inf, label = "Beaks > 50 mm"),
    fill = NA,
    box.colour = NA,
    hjust = 0,
    vjust = 1,
    box.padding = unit(10, "pt"),
    family = "Noah",
    colour = "#3b4046",
    size = 5
  ) +
  ggbeeswarm::geom_quasirandom(
    aes(
      x = bill_len,
      y = species,
      shape = species,
      colour = flipper_len
    ),
    size = 4
  ) +
  labs(
    x = "Beak length",
    title = paste0(
      "We are about to welcome ",
      nrow(penguins),
      " penguins"
    ),
    subtitle = paste0(
      "There are **",
      verbaliseR::pluralise(
        sum(penguins$bill_len > 50, na.rm = TRUE),
        word = "penguin"
      ),
      " with scary beaks**. Please look out for the ones on the red (darker) end of the spectrum; they have longer flippers so will need more support settling in."
    ),
    colour = "Predicted need for support"
  ) +
  scale_x_continuous(label = function(x) paste0(x, "mm")) +
  scale_shape(guide = "none") +
  scale_colour_gradient2(
    low = "#a5dc94",
    mid = "#a7a7a7",
    high = "#e0361d",
    midpoint = mean(penguins$flipper_len, na.rm = TRUE),
    limits = c(
      min(penguins$flipper_len, na.rm = TRUE),
      max(penguins$flipper_len, na.rm = TRUE)
    ),
    breaks = c(
      min(penguins$flipper_len, na.rm = TRUE),
      max(penguins$flipper_len, na.rm = TRUE)
    ),
    labels = c("low", "high")
  ) +
  theme(
    text = element_text(family = "Noah", colour = "#3b4046"),
    plot.subtitle = marquee::element_marquee(
      width = 1,
      family = "Noah",
      size = rel(1.2)
    ),
    plot.title = element_text(
      face = "bold",
      size = rel(1.6),
      colour = "#1A242F"
    ),
    legend.title = element_text(hjust = 0.5),
    axis.title.y = element_blank(),
    panel.grid = element_line(colour = "white"),
    plot.background = element_rect(fill = "#fafafa", colour = "#fafafa"),
    plot.margin = margin_auto(40),
    axis.title.x = element_text(hjust = 1, margin = margin(10, 0, 0, 0)),
    legend.position = "top",
    plot.title.position = "plot",
    legend.title.position = "top",
    legend.justification = 0.5,
    legend.key.width = unit(3.6, "lines")
  )

Now we can turn it into a parameterised function!

🥳

make_welcome_plot <- function(df = penguins) {
  df |>
    ggplot() +
    annotate(
      "rect",
      xmin = 50,
      xmax = Inf,
      ymin = -Inf,
      ymax = Inf,
      fill = alpha("skyblue", 0.3)
    ) +
    geom_vline(xintercept = 50, colour = "#2c3d4f", linewidth = 0.2) +
    ggtext::geom_textbox(
      data = data.frame(),
      aes(x = 50, y = Inf, label = "Beaks > 50 mm"),
      fill = NA,
      box.colour = NA,
      hjust = 0,
      vjust = 1,
      box.padding = unit(10, "pt"),
      family = "Noah",
      colour = "#3b4046",
      size = 5
    ) +
    ggbeeswarm::geom_quasirandom(
      aes(
        x = bill_len,
        y = species,
        shape = species,
        colour = flipper_len
      ),
      size = 4
    ) +
    labs(
      x = "Beak length",
      title = paste0(
        "We are about to welcome ",
        nrow(df),
        " penguins"
      ),
      subtitle = paste0(
        "There are **",
        verbaliseR::pluralise(
          sum(df$bill_len > 50, na.rm = TRUE),
          word = "penguin"
        ),
        " with scary beaks**. Please look out for the ones on the red (darker) end of the spectrum; they have longer flippers so will need more support settling in."
      ),
      colour = "Predicted need for support"
    ) +
    scale_x_continuous(label = function(x) paste0(x, "mm")) +
    scale_shape(guide = "none") +
    scale_colour_gradient2(
      low = "#a5dc94",
      mid = "#a7a7a7",
      high = "#e0361d",
      midpoint = mean(df$flipper_len, na.rm = TRUE),
      limits = c(
        min(df$flipper_len, na.rm = TRUE),
        max(df$flipper_len, na.rm = TRUE)
      ),
      breaks = c(
        min(df$flipper_len, na.rm = TRUE),
        max(df$flipper_len, na.rm = TRUE)
      ),
      labels = c("low", "high")
    ) +
    theme(
      text = element_text(family = "Noah", colour = "#3b4046"),
      plot.subtitle = marquee::element_marquee(
        width = 1,
        family = "Noah",
        size = rel(1.2)
      ),
      plot.title = element_text(
        face = "bold",
        size = rel(1.6),
        colour = "#1A242F"
      ),
      legend.title = element_text(hjust = 0.5),
      axis.title.y = element_blank(),
      panel.grid = element_line(colour = "white"),
      plot.background = element_rect(fill = "#fafafa", colour = "#fafafa"),
      plot.margin = margin_auto(40),
      axis.title.x = element_text(hjust = 1, margin = margin(10, 0, 0, 0)),
      legend.position = "top",
      plot.title.position = "plot",
      legend.title.position = "top",
      legend.justification = 0.5,
      legend.key.width = unit(3.6, "lines")
    )
}

Now we can turn it into a parameterised function!

make_welcome_plot()

make_welcome_plot(df = head(penguins, 250))

#3 The colours and shapes jump around, jump around

Fix 5: Named vectors

scale_shape_manual()

make_welcome_plot <- function(df = penguins) {
  df |>
    ggplot() +
    annotate(
      "rect",
      xmin = 50,
      xmax = Inf,
      ymin = -Inf,
      ymax = Inf,
      fill = alpha("skyblue", 0.3)
    ) +
    geom_vline(xintercept = 50, colour = "#2c3d4f", linewidth = 0.2) +
    ggtext::geom_textbox(
      data = data.frame(),
      aes(x = 50, y = Inf, label = "Beaks > 50 mm"),
      fill = NA,
      box.colour = NA,
      hjust = 0,
      vjust = 1,
      box.padding = unit(10, "pt"),
      family = "Noah",
      colour = "#3b4046",
      size = 5
    ) +
    ggbeeswarm::geom_quasirandom(
      aes(
        x = bill_len,
        y = species,
        shape = species,
        colour = flipper_len
      ),
      size = 4
    ) +
    labs(
      x = "Beak length",
      title = paste0(
        "We are about to welcome ",
        nrow(df),
        " penguins"
      ),
      subtitle = paste0(
        "There are **",
        verbaliseR::pluralise(
          sum(df$bill_len > 50, na.rm = TRUE),
          word = "penguin"
        ),
        " with scary beaks**. Please look out for the ones on the red (darker) end of the spectrum; they have longer flippers so will need more support settling in."
      ),
      colour = "Predicted need for support"
    ) +
    scale_x_continuous(label = function(x) paste0(x, "mm")) +
    scale_shape_manual(
      guide = "none",
      values = c("Adelie" = 16, "Chinstrap" = 17, "Gentoo" = 15)
    ) +
    scale_colour_gradient2(
      low = "#a5dc94",
      mid = "#a7a7a7",
      high = "#e0361d",
      midpoint = mean(df$flipper_len, na.rm = TRUE),
      limits = c(
        min(df$flipper_len, na.rm = TRUE),
        max(df$flipper_len, na.rm = TRUE)
      ),
      breaks = c(
        min(df$flipper_len, na.rm = TRUE),
        max(df$flipper_len, na.rm = TRUE)
      ),
      labels = c("low", "high")
    ) +
    theme(
      text = element_text(family = "Noah", colour = "#3b4046"),
      plot.subtitle = marquee::element_marquee(
        width = 1,
        family = "Noah",
        size = rel(1.2)
      ),
      plot.title = element_text(
        face = "bold",
        size = rel(1.6),
        colour = "#1A242F"
      ),
      legend.title = element_text(hjust = 0.5),
      axis.title.y = element_blank(),
      panel.grid = element_line(colour = "white"),
      plot.background = element_rect(fill = "#fafafa", colour = "#fafafa"),
      plot.margin = margin_auto(40),
      axis.title.x = element_text(hjust = 1, margin = margin(10, 0, 0, 0)),
      legend.position = "top",
      plot.title.position = "plot",
      legend.title.position = "top",
      legend.justification = 0.5,
      legend.key.width = unit(3.6, "lines")
    )
}

Fix 5: Named vectors

make_welcome_plot()

make_welcome_plot(df = head(penguins, 250))

Fix 5: Named vectors

Actually…

make_welcome_plot <- function(df = penguins) {
  df |>
    ggplot() +
    annotate(
      "rect",
      xmin = 50,
      xmax = Inf,
      ymin = -Inf,
      ymax = Inf,
      fill = alpha("skyblue", 0.3)
    ) +
    geom_vline(xintercept = 50, colour = "#2c3d4f", linewidth = 0.2) +
    ggtext::geom_textbox(
      data = data.frame(),
      aes(x = 50, y = Inf, label = "Beaks > 50 mm"),
      fill = NA,
      box.colour = NA,
      hjust = 0,
      vjust = 1,
      box.padding = unit(10, "pt"),
      family = "Noah",
      colour = "#3b4046",
      size = 5
    ) +
    ggbeeswarm::geom_quasirandom(
      aes(
        x = bill_len,
        y = species,
        shape = species,
        fill = flipper_len
      ),
      size = 4,
      colour = "#2c3d4f",
      stroke = 0.2
    ) +
    labs(
      x = "Beak length",
      title = paste0(
        "We are about to welcome ",
        nrow(df),
        " penguins"
      ),
      subtitle = paste0(
        "There are **",
        verbaliseR::pluralise(
          sum(df$bill_len > 50, na.rm = TRUE),
          word = "penguin"
        ),
        " with scary beaks**. Please look out for the ones on the red (darker) end of the spectrum; they have longer flippers so will need more support settling in."
      ),
      fill = "Predicted need for support"
    ) +
    scale_x_continuous(label = function(x) paste0(x, "mm")) +
    scale_shape_manual(
      guide = "none",
      values = c("Adelie" = 21, "Chinstrap" = 24, "Gentoo" = 22)
    ) +
    scale_fill_gradient2(
      low = "#a5dc94",
      mid = "#a7a7a7",
      high = "#e0361d",
      midpoint = mean(df$flipper_len, na.rm = TRUE),
      limits = c(
        min(df$flipper_len, na.rm = TRUE),
        max(df$flipper_len, na.rm = TRUE)
      ),
      breaks = c(
        min(df$flipper_len, na.rm = TRUE),
        max(df$flipper_len, na.rm = TRUE)
      ),
      labels = c("low", "high")
    ) +
    theme(
      text = element_text(family = "Noah", colour = "#3b4046"),
      plot.subtitle = marquee::element_marquee(
        width = 1,
        family = "Noah",
        size = rel(1.2)
      ),
      plot.title = element_text(
        face = "bold",
        size = rel(1.6),
        colour = "#1A242F"
      ),
      legend.title = element_text(hjust = 0.5),
      axis.title.y = element_blank(),
      panel.grid = element_line(colour = "white"),
      plot.background = element_rect(fill = "#fafafa", colour = "#fafafa"),
      plot.margin = margin_auto(40),
      axis.title.x = element_text(hjust = 1, margin = margin(10, 0, 0, 0)),
      legend.position = "top",
      plot.title.position = "plot",
      legend.title.position = "top",
      legend.justification = 0.5,
      legend.key.width = unit(3.6, "lines")
    )
}

Fix 5: Named vectors

make_welcome_plot()

make_welcome_plot(df = head(penguins, 250))

Fix 5: Named vectors

make_welcome_plot(df = head(penguins, 200))

make_welcome_plot(dplyr::filter(penguins, species == "Adelie"))

Fix 6: Specify your limits

The x-axis needs to stay the same for comparability

make_welcome_plot <- function(df = penguins) {
  df |>
    ggplot() +
    annotate(
      "rect",
      xmin = 50,
      xmax = Inf,
      ymin = -Inf,
      ymax = Inf,
      fill = alpha("skyblue", 0.3)
    ) +
    geom_vline(xintercept = 50, colour = "#2c3d4f", linewidth = 0.2) +
    ggtext::geom_textbox(
      data = data.frame(),
      aes(x = 50, y = Inf, label = "Beaks > 50 mm"),
      fill = NA,
      box.colour = NA,
      hjust = 0,
      vjust = 1,
      box.padding = unit(10, "pt"),
      family = "Noah",
      colour = "#3b4046",
      size = 5
    ) +
    ggbeeswarm::geom_quasirandom(
      aes(
        x = bill_len,
        y = species,
        shape = species,
        fill = flipper_len
      ),
      size = 4,
      colour = "#2c3d4f",
      stroke = 0.2
    ) +
    labs(
      x = "Beak length",
      title = paste0(
        "We are about to welcome ",
        nrow(df),
        " penguins"
      ),
      subtitle = paste0(
        "There are **",
        verbaliseR::pluralise(
          sum(df$bill_len > 50, na.rm = TRUE),
          word = "penguin"
        ),
        " with scary beaks**. Please look out for the ones on the red (darker) end of the spectrum; they have longer flippers so will need more support settling in."
      ),
      fill = "Predicted need for support"
    ) +
    scale_x_continuous(
      label = function(x) paste0(x, "mm"),
      limits = c(
        # Using range in the full penguin dataframe
        min(penguins$bill_len, na.rm = TRUE),
        max(penguins$bill_len, na.rm = TRUE)
      )
    ) +
    scale_shape_manual(
      guide = "none",
      values = c("Adelie" = 21, "Chinstrap" = 24, "Gentoo" = 22)
    ) +
    scale_fill_gradient2(
      low = "#a5dc94",
      mid = "#a7a7a7",
      high = "#e0361d",
      midpoint = mean(df$flipper_len, na.rm = TRUE),
      limits = c(
        min(df$flipper_len, na.rm = TRUE),
        max(df$flipper_len, na.rm = TRUE)
      ),
      breaks = c(
        min(df$flipper_len, na.rm = TRUE),
        max(df$flipper_len, na.rm = TRUE)
      ),
      labels = c("low", "high")
    ) +
    theme(
      text = element_text(family = "Noah", colour = "#3b4046"),
      plot.subtitle = marquee::element_marquee(
        width = 1,
        family = "Noah",
        size = rel(1.2)
      ),
      plot.title = element_text(
        face = "bold",
        size = rel(1.6),
        colour = "#1A242F"
      ),
      legend.title = element_text(hjust = 0.5),
      axis.title.y = element_blank(),
      panel.grid = element_line(colour = "white"),
      plot.background = element_rect(fill = "#fafafa", colour = "#fafafa"),
      plot.margin = margin_auto(40),
      axis.title.x = element_text(hjust = 1, margin = margin(10, 0, 0, 0)),
      legend.position = "top",
      plot.title.position = "plot",
      legend.title.position = "top",
      legend.justification = 0.5,
      legend.key.width = unit(3.6, "lines")
    )
}

Fix 6: Specify your limits

make_welcome_plot()

make_welcome_plot(dplyr::filter(penguins, species == "Adelie"))

Fix 6: Specify your limits

Do we want to fix colour?

make_welcome_plot <- function(df = penguins) {
  df |>
    ggplot() +
    annotate(
      "rect",
      xmin = 50,
      xmax = Inf,
      ymin = -Inf,
      ymax = Inf,
      fill = alpha("skyblue", 0.3)
    ) +
    geom_vline(xintercept = 50, colour = "#2c3d4f", linewidth = 0.2) +
    ggtext::geom_textbox(
      data = data.frame(),
      aes(x = 50, y = Inf, label = "Beaks > 50 mm"),
      fill = NA,
      box.colour = NA,
      hjust = 0,
      vjust = 1,
      box.padding = unit(10, "pt"),
      family = "Noah",
      colour = "#3b4046",
      size = 5
    ) +
    ggbeeswarm::geom_quasirandom(
      aes(
        x = bill_len,
        y = species,
        shape = species,
        fill = flipper_len
      ),
      size = 4,
      colour = "#2c3d4f",
      stroke = 0.2
    ) +
    labs(
      x = "Beak length",
      title = paste0(
        "We are about to welcome ",
        nrow(df),
        " penguins"
      ),
      subtitle = paste0(
        "There are **",
        verbaliseR::pluralise(
          sum(df$bill_len > 50, na.rm = TRUE),
          word = "penguin"
        ),
        " with scary beaks**. Please look out for the ones on the red (darker) end of the spectrum; they have longer flippers so will need more support settling in."
      ),
      fill = "Predicted need for support"
    ) +
    scale_x_continuous(
      label = function(x) paste0(x, "mm"),
      limits = c(
        # Using range in the full penguin dataframe
        min(penguins$bill_len, na.rm = TRUE),
        max(penguins$bill_len, na.rm = TRUE)
      )
    ) +
    scale_shape_manual(
      guide = "none",
      values = c("Adelie" = 21, "Chinstrap" = 24, "Gentoo" = 22)
    ) +
    scale_fill_gradient2(
      low = "#a5dc94",
      mid = "#a7a7a7",
      high = "#e0361d",
      midpoint = mean(penguins$flipper_len, na.rm = TRUE),
      limits = c(
        min(penguins$flipper_len, na.rm = TRUE),
        max(penguins$flipper_len, na.rm = TRUE)
      ),
      breaks = c(
        min(penguins$flipper_len, na.rm = TRUE),
        max(penguins$flipper_len, na.rm = TRUE)
      ),
      labels = c("low", "high")
    ) +
    theme(
      text = element_text(family = "Noah", colour = "#3b4046"),
      plot.subtitle = marquee::element_marquee(
        width = 1,
        family = "Noah",
        size = rel(1.2)
      ),
      plot.title = element_text(
        face = "bold",
        size = rel(1.6),
        colour = "#1A242F"
      ),
      legend.title = element_text(hjust = 0.5),
      axis.title.y = element_blank(),
      panel.grid = element_line(colour = "white"),
      plot.background = element_rect(fill = "#fafafa", colour = "#fafafa"),
      plot.margin = margin_auto(40),
      axis.title.x = element_text(hjust = 1, margin = margin(10, 0, 0, 0)),
      legend.position = "top",
      plot.title.position = "plot",
      legend.title.position = "top",
      legend.justification = 0.5,
      legend.key.width = unit(3.6, "lines")
    )
}

Fix 6: Specify your limits

make_welcome_plot()

make_welcome_plot(dplyr::filter(penguins, species == "Adelie"))

#4 One size fits all


👨🏻 🙀 🦸🏾‍♀️

Fix 7: Parameterise the threshold

  • 👨🏻 Reckless Ron
  • 🙀 Scaredy Sam
  • 🦸🏾‍♀️ Measured Meg

What does each user need?

Fix 7: Parameterise the threshold

Allowing user-specific thresholds?

make_welcome_plot <- function(df = penguins, zoo_keeper = "anyone") {
  threshold <- dplyr::case_when(
    zoo_keeper == "Sam" ~ 40,
    zoo_keeper == "Meg" ~ 50,
    zoo_keeper == "Ron" ~ 60,
    .default = 50
  )

  df |>
    ggplot() +
    annotate(
      "rect",
      xmin = threshold,
      xmax = Inf,
      ymin = -Inf,
      ymax = Inf,
      fill = alpha("skyblue", 0.3)
    ) +
    geom_vline(xintercept = threshold, colour = "#2c3d4f", linewidth = 0.2) +
    ggtext::geom_textbox(
      data = data.frame(),
      aes(x = threshold, y = Inf, label = paste0("Beaks > ", threshold, "mm")),
      fill = NA,
      box.colour = NA,
      hjust = 0,
      vjust = 1,
      box.padding = unit(10, "pt"),
      family = "Noah",
      colour = "#3b4046",
      size = 5
    ) +
    ggbeeswarm::geom_quasirandom(
      aes(
        x = bill_len,
        y = species,
        shape = species,
        fill = flipper_len
      ),
      size = 4,
      colour = "#2c3d4f",
      stroke = 0.2
    ) +
    labs(
      x = "Beak length",
      title = paste0(
        "We are about to welcome ",
        nrow(df),
        " penguins"
      ),
      subtitle = paste0(
        "There are **",
        verbaliseR::pluralise(
          sum(df$bill_len > threshold, na.rm = TRUE),
          word = "penguin"
        ),
        " with scary beaks**. Please look out for the ones on the red (darker) end of the spectrum; they have longer flippers so will need more support settling in."
      ),
      fill = "Predicted need for support"
    ) +
    scale_x_continuous(
      label = function(x) paste0(x, "mm"),
      limits = c(
        # Using range in the full penguin dataframe
        min(penguins$bill_len, na.rm = TRUE),
        max(penguins$bill_len, na.rm = TRUE)
      )
    ) +
    scale_shape_manual(
      guide = "none",
      values = c("Adelie" = 21, "Chinstrap" = 24, "Gentoo" = 22)
    ) +
    scale_fill_gradient2(
      low = "#a5dc94",
      mid = "#a7a7a7",
      high = "#e0361d",
      midpoint = mean(penguins$flipper_len, na.rm = TRUE),
      limits = c(
        min(penguins$flipper_len, na.rm = TRUE),
        max(penguins$flipper_len, na.rm = TRUE)
      ),
      breaks = c(
        min(penguins$flipper_len, na.rm = TRUE),
        max(penguins$flipper_len, na.rm = TRUE)
      ),
      labels = c("low", "high")
    ) +
    theme(
      text = element_text(family = "Noah", colour = "#3b4046"),
      plot.subtitle = marquee::element_marquee(
        width = 1,
        family = "Noah",
        size = rel(1.2)
      ),
      plot.title = element_text(
        face = "bold",
        size = rel(1.6),
        colour = "#1A242F"
      ),
      legend.title = element_text(hjust = 0.5),
      axis.title.y = element_blank(),
      panel.grid = element_line(colour = "white"),
      plot.background = element_rect(fill = "#fafafa", colour = "#fafafa"),
      plot.margin = margin_auto(40),
      axis.title.x = element_text(hjust = 1, margin = margin(10, 0, 0, 0)),
      legend.position = "top",
      plot.title.position = "plot",
      legend.title.position = "top",
      legend.justification = 0.5,
      legend.key.width = unit(3.6, "lines")
    )
}

Fix 7: Parameterise the threshold

make_welcome_plot(head(penguins, 150))

make_welcome_plot(head(penguins, 150), zoo_keeper = "Sam")

Fix 7: Parameterise the threshold

make_welcome_plot(head(penguins, 150), zoo_keeper = "Meg")

make_welcome_plot(head(penguins, 150), zoo_keeper = "Ron")

Fix 8: Parameterise the text

What if they pick up someone else’s graph?

make_welcome_plot <- function(df = penguins, zoo_keeper = "anyone") {
  threshold <- dplyr::case_when(
    zoo_keeper == "Sam" ~ 40,
    zoo_keeper == "Meg" ~ 50,
    zoo_keeper == "Ron" ~ 60,
    .default = 50
  )

  keeper_emoji <- dplyr::case_when(
    zoo_keeper == "Sam" ~ "🙀",
    zoo_keeper == "Meg" ~ "🦸🏾‍♀️",
    zoo_keeper == "Ron" ~ "👨🏻",
    .default = "🧑‍🌾"
  )

  df |>
    ggplot() +
    annotate(
      "rect",
      xmin = threshold,
      xmax = Inf,
      ymin = -Inf,
      ymax = Inf,
      fill = alpha("skyblue", 0.3)
    ) +
    geom_vline(xintercept = threshold, colour = "#2c3d4f", linewidth = 0.2) +
    ggtext::geom_textbox(
      data = data.frame(),
      aes(x = threshold, y = Inf, label = paste0("Beaks > ", threshold, "mm")),
      fill = NA,
      box.colour = NA,
      hjust = 0,
      vjust = 1,
      box.padding = unit(10, "pt"),
      family = "Noah",
      colour = "#3b4046",
      size = 5
    ) +
    ggbeeswarm::geom_quasirandom(
      aes(
        x = bill_len,
        y = species,
        shape = species,
        fill = flipper_len
      ),
      size = 4,
      colour = "#2c3d4f",
      stroke = 0.2
    ) +
    labs(
      x = "Beak length",
      title = paste0(
        keeper_emoji,
        " ",
        dplyr::case_when(
          zoo_keeper %in% c("Sam", "Meg", "Ron") ~ zoo_keeper,
          .default = "Keeper"
        ),
        ", you are about to welcome ",
        nrow(df),
        " penguins"
      ),
      subtitle = paste0(
        "There are **",
        verbaliseR::pluralise(
          sum(df$bill_len > threshold, na.rm = TRUE),
          word = "penguin"
        ),
        " with scary beaks**. Please look out for the ones on the red (darker) end of the spectrum; they have longer flippers so will need more support settling in."
      ),
      fill = "Predicted need for support"
    ) +
    scale_x_continuous(
      label = function(x) paste0(x, "mm"),
      limits = c(
        # Using range in the full penguin dataframe
        min(penguins$bill_len, na.rm = TRUE),
        max(penguins$bill_len, na.rm = TRUE)
      )
    ) +
    scale_shape_manual(
      guide = "none",
      values = c("Adelie" = 21, "Chinstrap" = 24, "Gentoo" = 22)
    ) +
    scale_fill_gradient2(
      low = "#a5dc94",
      mid = "#a7a7a7",
      high = "#e0361d",
      midpoint = mean(penguins$flipper_len, na.rm = TRUE),
      limits = c(
        min(penguins$flipper_len, na.rm = TRUE),
        max(penguins$flipper_len, na.rm = TRUE)
      ),
      breaks = c(
        min(penguins$flipper_len, na.rm = TRUE),
        max(penguins$flipper_len, na.rm = TRUE)
      ),
      labels = c("low", "high")
    ) +
    theme(
      text = element_text(family = "Noah", colour = "#3b4046"),
      plot.subtitle = marquee::element_marquee(
        width = 1,
        family = "Noah",
        size = rel(1.2)
      ),
      plot.title = element_text(
        face = "bold",
        size = rel(1.6),
        colour = "#1A242F"
      ),
      legend.title = marquee::element_marquee(hjust = 0.5),
      axis.title.y = element_blank(),
      panel.grid = element_line(colour = "white"),
      plot.background = element_rect(fill = "#fafafa", colour = "#fafafa"),
      plot.margin = margin_auto(40),
      axis.title.x = element_text(hjust = 1, margin = margin(10, 0, 0, 0)),
      legend.position = "top",
      plot.title.position = "plot",
      legend.title.position = "top",
      legend.justification = 0.5,
      legend.key.width = unit(3.6, "lines")
    )
}

Fix 8: Parameterise the text

make_welcome_plot(dplyr::sample_n(penguins, 75))

make_welcome_plot(dplyr::sample_n(penguins, 90), zoo_keeper = "Sam")

Fix 8: Parameterise the text

make_welcome_plot(dplyr::sample_n(penguins, 64), zoo_keeper = "Ron")

make_welcome_plot(dplyr::sample_n(penguins, 110), zoo_keeper = "Meg")

#5 A dependency changes everything

Fix 9: Know thyself

🤯 Visualising the risks in the visualisation tools you’re using
to visualise risk

Fix 9: Know thyself

📦 {pkgdepends}

beakoff_exploration <- pkgdepends::new_pkg_deps(
  "local::~/work/resources/beakoff"
)
beakoff_exploration$solve()
beakoff_exploration$draw()
local::~/work/resources/beakoff 0.1.0 ✨👷🏾
├─dplyr 1.2.0 ✨👷🏾🔧 ⬇ (922.39 kB)
│ ├─cli 3.6.5 ✨👷🏾🔧 ⬇ (640.24 kB)
│ ├─generics 0.1.4 ✨👷🏾 ⬇ (47.22 kB)
│ ├─glue 1.8.0 ✨👷🏾🔧 ⬇ (126.68 kB)
│ ├─lifecycle 1.0.5 ✨👷🏾 ⬇ (107.14 kB)
│ │ ├─cli
│ │ └─rlang 1.1.7 ✨👷🏾🔧 ⬇ (770.34 kB)
│ ├─magrittr 2.0.4 ✨👷🏾🔧 ⬇ (281.79 kB)
│ ├─pillar 1.11.1 ✨👷🏾 ⬇ (409.51 kB)
│ │ ├─cli
│ │ ├─glue
│ │ ├─lifecycle
│ │ ├─rlang
│ │ ├─utf8 1.2.6 ✨👷🏾🔧 ⬇ (243.86 kB)
│ │ └─vctrs 0.7.1 ✨👷🏾🔧 ⬇ (1.08 MB)
│ │   ├─cli
│ │   ├─glue
│ │   ├─lifecycle
│ │   └─rlang
│ ├─R6 2.6.1 ✨👷🏾 ⬇ (64.51 kB)
│ ├─rlang
│ ├─tibble 3.3.1 ✨👷🏾🔧 ⬇ (557.13 kB)
│ │ ├─cli
│ │ ├─lifecycle
│ │ ├─magrittr
│ │ ├─pillar
│ │ ├─pkgconfig 2.0.3 ✨👷🏾 ⬇ (6.08 kB)
│ │ ├─rlang
│ │ └─vctrs
│ ├─tidyselect 1.2.1 ✨👷🏾🔧 ⬇ (103.59 kB)
│ │ ├─cli
│ │ ├─glue
│ │ ├─lifecycle
│ │ ├─rlang
│ │ ├─vctrs
│ │ └─withr 3.0.2 ✨👷🏾 ⬇ (103.24 kB)
│ └─vctrs
├─janitor 2.2.1 ✨👷🏾 ⬇ (232.99 kB)
│ ├─dplyr
│ ├─hms 1.1.4 ✨👷🏾 ⬇ (44.79 kB)
│ │ ├─cli
│ │ ├─lifecycle
│ │ ├─pkgconfig
│ │ ├─rlang
│ │ └─vctrs
│ ├─lifecycle
│ ├─lubridate 1.9.5 ✨👷🏾🔧 ⬇ (429.86 kB)
│ │ ├─generics
│ │ └─timechange 0.4.0 ✨👷🏾🔧 ⬇ (103.97 kB)
│ │   └─cpp11 0.5.3 ✨👷🏾 ⬇ (302.29 kB)
│ ├─magrittr
│ ├─purrr 1.2.1 ✨👷🏾🔧 ⬇ (278.29 kB)
│ │ ├─cli
│ │ ├─lifecycle
│ │ ├─magrittr
│ │ ├─rlang
│ │ └─vctrs
│ ├─rlang
│ ├─snakecase 0.11.1 ✨👷🏾 ⬇ (457.82 kB)
│ │ ├─stringi 1.8.7 ✨👷🏾🔧 ⬇ (11.91 MB)
│ │ └─stringr 1.6.0 ✨👷🏾 ⬇ (195.83 kB)
│ │   ├─cli
│ │   ├─glue
│ │   ├─lifecycle
│ │   ├─magrittr
│ │   ├─rlang
│ │   ├─stringi
│ │   └─vctrs
│ ├─stringi
│ ├─stringr
│ ├─tidyr 1.3.2 ✨👷🏾🔧 ⬇ (809.03 kB)
│ │ ├─cli
│ │ ├─cpp11
│ │ ├─dplyr
│ │ ├─glue
│ │ ├─lifecycle
│ │ ├─magrittr
│ │ ├─purrr
│ │ ├─rlang
│ │ ├─stringr
│ │ ├─tibble
│ │ ├─tidyselect
│ │ └─vctrs
│ └─tidyselect
├─palmerpenguins 0.1.1 ✨👷🏾 ⬇ (3.00 MB)
├─vdiffr 1.0.9 ✨👷🏾🔧 ⬇ (102.43 kB)
│ ├─cpp11
│ ├─diffobj 0.3.6 ✨👷🏾🔧 ⬇ (472.96 kB)
│ │ └─crayon 1.5.3 ✨👷🏾 ⬇ (40.40 kB)
│ ├─glue
│ ├─htmltools 0.5.9 ✨👷🏾🔧 ⬇ (135.21 kB)
│ │ ├─base64enc 0.1-6 ✨👷🏾🔧 ⬇ (9.42 kB)
│ │ ├─digest 0.6.39 ✨👷🏾🔧 ⬇ (236.63 kB)
│ │ ├─fastmap 1.2.0 ✨👷🏾🔧 ⬇ (46.71 kB)
│ │ └─rlang
│ ├─lifecycle
│ ├─rlang
│ ├─testthat 3.3.2 ✨👷🏾🔧 ⬇ (834.04 kB)
│ │ ├─brio 1.1.5 ✨👷🏾🔧 ⬇ (13.17 kB)
│ │ ├─callr 3.7.6 ✨👷🏾 ⬇ (104.36 kB)
│ │ │ ├─processx 3.8.6 ✨👷🏾🔧 ⬇ (165.19 kB)
│ │ │ │ ├─ps 1.9.1 ✨👷🏾🔧 ⬇ (167.92 kB)
│ │ │ │ └─R6
│ │ │ └─R6
│ │ ├─cli
│ │ ├─desc 1.4.3 ✨👷🏾 ⬇ (80.07 kB)
│ │ │ ├─cli
│ │ │ └─R6
│ │ ├─evaluate 1.0.5 ✨👷🏾 ⬇ (39.32 kB)
│ │ ├─jsonlite 2.0.0 ✨👷🏾🔧 ⬇ (1.06 MB)
│ │ ├─lifecycle
│ │ ├─magrittr
│ │ ├─pkgload 1.5.0 ✨👷🏾 ⬇ (87.78 kB)
│ │ │ ├─cli
│ │ │ ├─desc
│ │ │ ├─fs 1.6.6 ✨👷🏾🔧 ⬇ (1.20 MB)
│ │ │ ├─glue
│ │ │ ├─lifecycle
│ │ │ ├─pkgbuild 1.4.8 ✨👷🏾 ⬇ (51.30 kB)
│ │ │ │ ├─callr
│ │ │ │ ├─cli
│ │ │ │ ├─desc
│ │ │ │ ├─processx
│ │ │ │ └─R6
│ │ │ ├─processx
│ │ │ ├─rlang
│ │ │ └─rprojroot 2.1.1 ✨👷🏾 ⬇ (59.90 kB)
│ │ ├─praise 1.0.0 ✨👷🏾 ⬇ (6.10 kB)
│ │ ├─processx
│ │ ├─ps
│ │ ├─R6
│ │ ├─rlang
│ │ ├─waldo 0.6.2 ✨👷🏾 ⬇ (45.91 kB)
│ │ │ ├─cli
│ │ │ ├─diffobj
│ │ │ ├─glue
│ │ │ └─rlang
│ │ └─withr
│ └─xml2 1.5.2 ✨👷🏾🔧 ⬇ (154.70 kB)
│   ├─cli
│   └─rlang
└─ggplot2 4.0.2 ✨👷🏾 ⬇ (6.36 MB)
  ├─cli
  ├─gtable 0.3.6 ✨👷🏾 ⬇ (148.15 kB)
  │ ├─cli
  │ ├─glue
  │ ├─lifecycle
  │ └─rlang
  ├─isoband 0.3.0 ✨👷🏾🔧 ⬇ (1.59 MB)
  │ ├─cli
  │ ├─cpp11
  │ └─rlang
  ├─lifecycle
  ├─rlang
  ├─S7 0.2.1 ✨👷🏾🔧 ⬇ (184.14 kB)
  ├─scales 1.4.0 ✨👷🏾 ⬇ (328.67 kB)
  │ ├─cli
  │ ├─farver 2.1.2 ✨👷🏾🔧 ⬇ (1.28 MB)
  │ ├─glue
  │ ├─labeling 0.4.3 ✨👷🏾 ⬇ (10.17 kB)
  │ ├─lifecycle
  │ ├─R6
  │ ├─RColorBrewer 1.1-3 ✨👷🏾 ⬇ (11.64 kB)
  │ ├─rlang
  │ └─viridisLite 0.4.3 ✨👷🏾 ⬇ (1.27 MB)
  ├─vctrs
  └─withr

Key:  ✨ new |  ⬇ download | 👷🏾 build | 🔧 compile

Fix 10: Set up dataviz tests

📦 {vdiffr} to the rescue! beakoff/testthat/test_welcome_plot.R

set.seed(2026)

describe("welcome plot", {
  it("The plot looks as expected with all penguins for unspecified zoo keeper", {
    vdiffr::expect_doppelganger(
      "all penguins any keeper",
      make_welcome_plot()
    )
  })
})


describe("welcome plot", {
  it("The plot looks as expected with head(penguins, 250) for unspecified zoo keeper", {
    vdiffr::expect_doppelganger(
      "head 250 penguins any keeper",
      make_welcome_plot(df = head(penguins, 250))
    )
  })
})


describe("welcome plot", {
  it("The plot looks as expected with head(penguins, 250) for Meg", {
    vdiffr::expect_doppelganger(
      "head 250 penguins Meg",
      make_welcome_plot(df = head(penguins, 250), zoo_keeper = "Meg")
    )
  })
})

Fix 10: Set up dataviz tests

📦 {vdiffr} to the rescue!

  • Creates reference ‘.svg’ image files in beakoff/testthat/_snaps the first time we run the tests
  • Compares newly created plots to those reference files next time we run the tests
  • Celebrates if they are all the same
  • Super handy comparison in viewer ❤️ if they are not
  • Super handy interface to accept or reject the change
    • (i.e. was it deliberate?)

Fix 10: Set up dataviz tests

Spot the (unanticipated) difference.

5 risks, 10 solutions

Plenty more to chat about!

  • cararthompson.com/talks
  • hello@cararthompson.com
  • cararthompson.com/newsletter