Examples

Data

For the exercises, we’ll be using data on health metrics in urban centres via the TidyTuesday project. You can read in the data directly from GitHub.

health <- readr::read_csv("https://raw.githubusercontent.com/rfordatascience/tidytuesday/main/data/2026/2026-09-29/health.csv")

This dataset has the following variables:

health.csv

variable class description
ID_UC_G0 double Unique ID.
GC_UCN_MAI_2025 character Urban Centre Main Name.
GC_CNT_GAD_2025 character Country name. GADM.
GC_UCA_KM2_2025 double Urban centre area in 2025.
GC_POP_TOT_2025 double Urban centre total population.
GC_DEV_WIG_2025 character World Bank Income Group.
GC_DEV_USR_2025 character UN SDG Region, geographic and statistical groupings of countries used by the UN.
HL_FCL_HOS_2024 double Number of hospitals in 2024.
HL_FCL_PHA_2024 double Number of pharmacies in 2024.
HL_FDE_HOS_2024 double Number of hospitals per urban centre area (km2) in 2024.
HL_FDE_PHA_2024 double Number of pharmacies per urban centre area (km2) in 2024.
HL_FPC_HOS_2025 double Number of hospitals per capita in 2025.
HL_FPC_PHA_2025 double Number of pharmacies per capita in 2025.
HL_POP_HOS_2025 double Population living within 1 km buffer from a hospital in 2025.
HL_POP_PHA_2025 double Population living within 1 km buffer from a pharmacy in 2025.
HL_SHP_HOS_2025 double Share of the urban centre population living within 1 km buffer from a hospital in 2025.
HL_SHP_PHA_2025 double Share of the urban centre population living within 1 km buffer from a pharmacy in 2025.

Examples

Since we’re focusing on making visualisations, the following code will clean up the raw data and find the continent for each country:

library(tidyverse)
plot_data <- health |>
  select(
    region = GC_DEV_USR_2025,
    hospitals = HL_FPC_HOS_2025
  ) |>
  drop_na()

Example 1

Initial plot:

ggplot() +
  geom_density(
    data = plot_data,
    mapping = aes(x = hospitals),
    fill = "red4",
    colour = "red4",
    alpha = 0.4
  ) +
  facet_wrap(~region)

Add ggview():

ggplot() +
  geom_density(
    data = plot_data,
    mapping = aes(x = hospitals),
    fill = "red4",
    colour = "red4",
    alpha = 0.4
  ) +
  facet_wrap(~region) +
  canvas(
    width = 6, height = 4,
    units = "in",
    dpi = 300
  )

Saving with ggview():

ggplot() +
  geom_density(
    data = plot_data,
    mapping = aes(x = hospitals),
    fill = "red4",
    colour = "red4",
    alpha = 0.4
  ) +
  facet_wrap(~region) +
  canvas(
    width = 6, height = 4,
    units = "in",
    dpi = 300
  ) # -> p

save_ggplot(
  plot = p,
  file = "plot.png"
)

Increase the base size:

ggplot() +
  geom_density(
    data = plot_data,
    mapping = aes(x = hospitals),
    fill = "red4",
    colour = "red4",
    alpha = 0.4
  ) +
  facet_wrap(~region) +
  theme_minimal(base_size = 13)

Increase specific element sizes relative to the base size:

ggplot() +
  geom_density(
    data = plot_data,
    mapping = aes(x = hospitals),
    fill = "red4",
    colour = "red4",
    alpha = 0.4
  ) +
  facet_wrap(~region) +
  labs(title = "Hospitals per capita") +
  theme_minimal(base_size = 13) +
  theme(
    plot.title = element_text(size = rel(1.6))
  )

Format y-axis labels:

ggplot() +
  geom_density(
    data = plot_data,
    mapping = aes(x = hospitals),
    fill = "red4",
    colour = "red4",
    alpha = 0.4
  ) +
  facet_wrap(~region) +
  scale_x_continuous(
    labels = scales::label_comma()
  )

Prevent overlapping axis labels:

ggplot() +
  geom_density(
    data = plot_data,
    mapping = aes(x = hospitals),
    fill = "red4",
    colour = "red4",
    alpha = 0.4
  ) +
  facet_wrap(~region) +
  scale_x_continuous(
    labels = scales::label_comma(),
    guide = guide_axis(check.overlap = TRUE)
  )

Example 2

Add a long subtitle:

long_st <- "Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae pellentesque sem placerat."

Wrap with ggtext:

library(ggtext)
ggplot() +
  geom_density(
    data = plot_data,
    mapping = aes(x = hospitals),
    fill = "red4",
    colour = "red4",
    alpha = 0.4
  ) +
  facet_wrap(~region) +
  labs(subtitle = long_st) +
  theme(
    plot.subtitle = element_textbox_simple()
  )

Or marquee:

library(marquee)
ggplot() +
  geom_density(
    data = plot_data,
    mapping = aes(x = hospitals),
    fill = "red4",
    colour = "red4",
    alpha = 0.4
  ) +
  facet_wrap(~region) +
  labs(subtitle = long_st) +
  theme(
    plot.subtitle = element_marquee(width = 1)
  )

Add Markdown formatting:

long_st <- "**Note**: Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae pellentesque sem placerat."

Add the region name to the top right corner:

ggplot() +
  geom_density(
    data = plot_data,
    mapping = aes(x = hospitals),
    fill = "red4",
    colour = "red4",
    alpha = 0.4
  ) +
  geom_text(
    data = plot_data |> slice_head(by = region),
    mapping = aes(x = I(0.95), y = I(0.95), label = region),
    hjust = 1
  ) +
  facet_wrap(~region)

Add an annotation:

ggplot() +
  geom_density(
    data = plot_data,
    mapping = aes(x = hospitals),
    fill = "red4",
    colour = "red4",
    alpha = 0.4
  ) +
  facet_wrap(~region) +
  annotate("text",
    x = 0.0004, y = 15000,
    label = "Hospitals per capita in Europe"
  )

Add an annotation to the European facet:

library(ggh4x)
ggplot() +
  geom_density(
    data = plot_data,
    mapping = aes(x = hospitals),
    fill = "red4",
    colour = "red4",
    alpha = 0.4
  ) +
  facet_wrap(~region) +
  at_panel(
    annotate("text",
      x = 0.0004, y = 15000,
      label = "Hospitals per capita in Europe"
    ),
    region == "Europe"
  )

Example 3

Create variables to store colours:

text_col <- "grey10"
highlight_col <- "#B01D5F"
bg_col <- "white"

Create your own basic theme():

my_theme <- function(base_size = 13) {
  theme_minimal(base_size = base_size)
}
ggplot() +
  geom_density(
    data = plot_data,
    mapping = aes(x = hospitals),
    fill = highlight_col,
    colour = highlight_col,
    alpha = 0.4
  ) +
  facet_wrap(~region) +
  my_theme()

Create your own more complex theme():

my_theme <- function(base_size = 13) {
  theme_minimal(base_size = base_size) +
    theme(
      plot.title = element_textbox_simple(face = "bold", size = rel(1.3)),
      plot.subtitle = element_textbox_simple(),
      plot.caption = element_textbox_simple(),
      strip.text = element_textbox_simple(minheight = unit(0.3, "in"))
    )
}
ggplot() +
  geom_density(
    data = plot_data,
    mapping = aes(x = hospitals),
    fill = highlight_col,
    colour = highlight_col,
    alpha = 0.4
  ) +
  facet_wrap(~region) +
  labs(title = "Hospitals per capita", subtitle = long_st) +
  my_theme() 

Use namespacing (::) to make it easier to reuse:

my_theme <- function(base_size = 13) {
  ggplot2::theme_minimal(base_size = base_size) +
    ggplot2::theme(
      plot.title = ggtext::element_textbox_simple(face = "bold", size = rel(1.3)),
      plot.subtitle = ggtext::element_textbox_simple(),
      plot.caption = ggtext::element_textbox_simple(),
      strip.text = ggtext::element_textbox_simple(minheight = unit(0.3, "in"))
    )
}