Skip to contents

Overview

This article will demonstrate some fantastic ggplot2 extension packages.

Note, an alternative is to use ggplot2 code directly instead. This may be easier depending on your familiarity with ggblanket/ggplot2 and the relevant extension package. The set_blanket() function used to get a lot of the ggblanket style also works with ggplot2.

library(dplyr)
library(stringr)
library(ggplot2)
library(scales)
library(ggblanket)
library(patchwork)
library(palmerpenguins)

set_blanket()

penguins2 <- penguins |> 
  labelled::set_variable_labels(
    bill_length_mm = "Bill length (mm)",
    bill_depth_mm = "Bill depth (mm)",
    flipper_length_mm = "Flipper length (mm)",
    body_mass_g = "Body mass (g)",
  ) |> 
  mutate(sex = factor(sex, labels = c("Female", "Male")))

patchwork

The patchwork package enables plots to be patched together.

A convenient hack to add a panel centred title can be to add a facet_wrap layer with a character title string for the facets argument.

Otherwise you will need to modify margins

p1 <- mtcars |> 
  gg_point(
    x = mpg, 
    y = disp,
  ) +
  facet_wrap(~"Plot 1")

p2 <- mtcars |> 
  gg_boxplot(
    x = gear, 
    y = disp, 
    group = gear, 
    width = 0.5,
  ) +
  facet_wrap(~"Plot 2")

p3 <- mtcars |> 
  gg_smooth(
    x = disp, 
    y = qsec, 
  ) +
  facet_wrap(~"Plot 3")

(p1 + p2) / p3 +
  plot_annotation(
    title = "A collection of plots patched together",
    subtitle = "Works so nice!",
    theme = light_mode_r() +
      theme(
        plot.title = element_text(margin = margin(t = -11)),
        plot.subtitle = element_text(margin = margin(t = 5.5)),
      )
  )

marquee

The marquee package supports using markdown in text.

penguins2 |>
  gg_point(
    x = bill_depth_mm,
    y = bill_length_mm,
    col = species,
    title = "**{.#0095a8ff Adelie}**, **{.#ffa600ff Chinstrap}**, *and* 
    **{.#003f5cff Gentoo}** penguin species",
  ) +
  theme(legend.position = "none") +
  theme(plot.title = marquee::element_marquee()) 

showtext

The showtext package enables the use of different fonts in plots. The *_mode_* theme functions provide a base_family argument.

head(sysfonts::font_families_google())
#> [1] "ABeeZee"       "Abel"          "Abhaya Libre"  "Abril Fatface"
#> [5] "Aclonica"      "Acme"

#register google fonts
sysfonts::font_add_google(name = "Covered By Your Grace", family = "grace")
sysfonts::font_add_google(name = 'Roboto Slab', family = 'roboto_slab')
sysfonts::font_add_google(name = 'Syne Mono', family = 'syne')

#or download, extract install, and register one from somewhere else
# sysfonts::font_add(family = "blah", regular = "blah1.otf", bold = "blah2.otf")

showtext::showtext_auto(enable = TRUE)

penguins2 |>
  gg_point(
    x = flipper_length_mm,
    y = body_mass_g,
    col = sex,
    facet = species,
    title = "penguins2 body mass by flipper length",
    subtitle = "Palmer Archipelago, Antarctica",
    caption = "Source: Gorman, 2020",
    mode = light_mode_r(base_family = "grace"),
  ) +
  theme(
    plot.title = element_text(family = "roboto_slab"),
    plot.subtitle = element_text(family = "syne")
  )


showtext::showtext_auto(enable = FALSE)

gghighlight

The gghighlight package enables geoms or parts thereof to be highlighted.

penguins2 |>
  gg_point(
    x = flipper_length_mm,
    y = body_mass_g,
  ) +
  gghighlight::gghighlight(body_mass_g >= 5000)

The gg_* function builds the scale for the data that it thinks will be within the panel. Therefore in some situations, this must be taken into account to ensure the scale builds correctly.

penguins2 |>
  gg_histogram(
    x = flipper_length_mm,
    col = species,
    x_breaks_n = 4,
  ) + #build the scale for all data
  facet_wrap(~species) + #then add facet layer
  gghighlight::gghighlight() 

ggforce

The ggforce package includes numerous extra geoms, stats etc.

library(ggforce)
geom_bspline()
#> geom_path: arrow = NULL, lineend = butt, na.rm = FALSE
#> stat_bspline: na.rm = FALSE, n = 100, type = clamped
#> position_identity

economics |>
  slice_head(n = 35) |> 
  gg_path(
    stat = "bspline", n = 100, type = "clamped",
    x = date, 
    y = unemploy,
    linewidth = 1,
    y_label = "Unemployment",
  ) 

geom_mark_hull()
#> geom_mark_hull: na.rm = FALSE, expand = 5, radius = 2.5, concavity = 2, label.margin = c(2, 2, 2, 2), label.width = NULL, label.minwidth = 50, label.fontsize = 12, label.family = , label.lineheight = 1, label.fontface = c("bold", "plain"), label.hjust = 0, label.fill = white, label.colour = black, label.buffer = 10, con.colour = black, con.size = 0.5, con.type = elbow, con.linetype = 1, con.border = one, con.cap = 3, con.arrow = NULL
#> stat_identity: na.rm = FALSE
#> position_identity

penguins2 |>
  gg_blanket(
    geom = "mark_hull",
    x = flipper_length_mm,
    y = body_mass_g,
    col = species,
    coord = coord_cartesian(),
  ) +
  geom_point()

ggrepel

The ggrepel package can be used to neatly avoid overlapping labels.

library(ggrepel)
geom_text_repel()
#> geom_text_repel: parse = FALSE, na.rm = FALSE, box.padding = 0.25, point.padding = 1e-06, min.segment.length = 0.5, arrow = NULL, force = 1, force_pull = 1, max.time = 0.5, max.iter = 10000, max.overlaps = 10, nudge_x = 0, nudge_y = 0, xlim = c(NA, NA), ylim = c(NA, NA), direction = both, seed = NA, verbose = FALSE
#> stat_identity: na.rm = FALSE
#> position_identity

mtcars |>
  tibble::rownames_to_column("car") |>
  filter(wt > 2.75, wt < 3.45) |>
  gg_point(
    x = wt, 
    y = mpg, 
    label = car, 
  ) +
  geom_text_repel() 

ggbeeswarm

The ggbeeswarm package enables beeswarm plots.

library(ggbeeswarm)
geom_beeswarm()
#> geom_point: na.rm = FALSE
#> stat_identity: na.rm = FALSE
#> position_beeswarm
geom_quasirandom()
#> geom_point: na.rm = FALSE
#> stat_identity: na.rm = FALSE
#> position_quasirandom

p1 <- penguins2 |> 
  gg_point(
    position = position_beeswarm(),
    x = sex, 
    y = flipper_length_mm,
    col = sex,
    x_labels = \(x) str_to_upper(str_sub(x, 1, 1)),
    subtitle = "\nBeeswarm",
  ) +
  theme(legend.position = "none")

p2 <- penguins2 |> 
  gg_point(
    position = ggbeeswarm::position_quasirandom(),
    x = sex, 
    y = flipper_length_mm,
    col = sex,
    x_labels = \(x) str_to_upper(str_sub(x, 1, 1)),
    subtitle = "\nQuasirandom",
  ) +
  theme(legend.position = "none")

p1 + p2

ggridges

The ggridges package enables ridgeline plots.

library(ggridges)
geom_density_ridges()
#> geom_density_ridges: na.rm = FALSE, panel_scaling = TRUE
#> stat_density_ridges: na.rm = FALSE
#> position_points_sina

ggridges::Catalan_elections |>
  rename_with(snakecase::to_snake_case) |>
  mutate(year = factor(year)) |>
  gg_blanket(
    geom = "density_ridges",
    stat = "density_ridges",
    coord = coord_cartesian(xlim = c(0, 100)),
    x = percent,
    y = year,
    col = option,
    colour = "white",
    y_expand = expansion(c(0, 0.125)),
    alpha = 0.9,
  )

ggdist

The ggdist package enables the visualisation of uncertainty.

library(ggdist)
library(distributional)
geom_slabinterval()
#> geom_slabinterval: orientation = NA, normalize = all, fill_type = segments, interval_size_domain = c(1, 6), interval_size_range = c(0.6, 1.4), fatten_point = 1.8, arrow = NULL, show_slab = TRUE, show_point = TRUE, show_interval = TRUE, subguide = none, na.rm = FALSE
#> stat_identity: na.rm = FALSE
#> position_identity

set.seed(123)

data.frame(
  distribution = c("Gamma(2,1)", "Normal(5,1)", "Mixture"),
  values = c(
    dist_gamma(2, 1),
    dist_normal(5, 1),
    dist_mixture(
      dist_gamma(2, 1),
      dist_normal(5, 1),
      weights = c(0.4, 0.6))
  )) |> 
  gg_blanket(
    geom = "slabinterval",
    stat = "slabinterval",
    y = distribution,
    mapping = aes(dist = values),
    colour = lightness[1],
    fill = blue,
    y_expand = c(0.05, 0.05),
    # show_slab = FALSE,
    # show_interval = FALSE,
  ) +
  theme(legend.position = "none")

geom_lineribbon()
#> geom_lineribbon: step = FALSE, orientation = NA, na.rm = FALSE
#> stat_identity: na.rm = FALSE
#> position_identity

set.seed(123)

tibble(x = 1:10) |> 
  group_by(across(everything()))  |> 
  do(tibble(y = rnorm(100, .$x))) |> 
  median_qi(.width = c(0.5, 0.8, 0.95)) |>
  mutate(.width = factor(.width)) |>
  gg_blanket(
    geom = "lineribbon",
    x = x,
    y = y,
    ymin = .lower,
    ymax = .upper,
    col = .width,
    colour = viridis::mako(n = 9)[c(1)],
    col_legend_rev = TRUE,
    col_palette = viridis::mako(n = 9)[c(4,6,9)],
  )

ggdensity

The ggdensity package provides visualizations of density estimates.

library(ggdensity)
geom_hdr()
#> geom_hdr: na.rm = FALSE
#> stat_hdr: na.rm = FALSE
#> position_identity

set.seed(123)

data.frame(
  x = rnorm(1000),
  y = rnorm(1000),
  z = c(rep("A", times = 500), rep("B", times = 500))
) |>
  gg_blanket(
    geom = "hdr", 
    stat = "hdr",
    x = x, 
    y = y, 
    y_symmetric = FALSE,
  ) +
  guides(colour = FALSE, fill = FALSE)

set.seed(123)

data.frame(
  x = rnorm(1000),
  y = rnorm(1000),
  z = c(rep("A", times = 500), rep("B", times = 500))
) |>
  gg_blanket(
    geom = "hdr", 
    stat = "hdr",
    x = x, 
    y = y, 
    col = z,
    facet = z,
    y_symmetric = FALSE,
  ) 

ggpattern

library(ggpattern)

penguins2 |>
  group_by(species, sex) |>
  summarise(across(body_mass_g, \(x) mean(x))) |>
  tidyr::drop_na() |> 
  labelled::copy_labels_from(penguins2) |> 
  gg_blanket(
    geom = "col_pattern",
    position = "dodge",
    y = species,
    x = body_mass_g,
    col = sex,
    mapping = aes(pattern = sex),
    width = 0.75,
  ) 

paletteer

The paletteer package provides access to most of the palettes within R.

library(paletteer)

p1 <- penguins2 |>
  gg_point(
    x = flipper_length_mm,
    y = body_mass_g,
    col = species,
    col_palette = paletteer_d("RColorBrewer::Dark2"),
    x_breaks_n = 4,
  ) +
  theme(legend.position = "none")

p2 <- penguins2 |>
  gg_point(
    x = flipper_length_mm,
    y = body_mass_g,
    col = bill_depth_mm,
    col_palette = paletteer_c("viridis::rocket", 9),
    x_breaks_n = 4,
  ) +
  theme(legend.position = "none")

p1 + p2

ggblend

The ggblend package provides blending of colours. You must use a graphics device that supports blending.

p1 <- penguins2 |>
  gg_density(
    x = flipper_length_mm,
    col = species,
    y_symmetric = FALSE,
  ) +
  facet_wrap(~"Normal") +
  ggeasy::easy_remove_x_axis() +
  ggeasy::easy_remove_y_axis()

p2 <- penguins2 |>
  gg_blanket(
    stat = "density",
    x = flipper_length_mm,
    col = species,
    y_symmetric = FALSE,
  ) +
  facet_wrap(~"Blended") +
  geom_density(alpha = 0.6) |> ggblend::blend(blend = "multiply") +
  ggeasy::easy_remove_x_axis() +
  ggeasy::easy_remove_y_axis()

p1 + p2

ggh4x

The ggh4x package includes enhanced facet functionality.

iris |> 
  rename_with(\(x) snakecase::to_snake_case(x)) |> 
  mutate(across(species, \(x) str_to_sentence(x))) |> 
  mutate(size = if_else(species == "Setosa", "Short Leaves", "Long Leaves")) |> 
  gg_point(
    x = sepal_width, 
    y = sepal_length, 
    x_breaks_n = 4,
  ) +
  ggh4x::facet_nested(
    cols = vars(size, species), 
    nest_line = TRUE, 
    solo_line = TRUE,
  ) +
  theme(strip.text.x = element_text(margin = margin(t = 2.5, b = 5)))

ggeasy

The ggeasy package provides a lot of support for easily modifying themes. Removing axes and gridlines is often useful.

library(ggeasy)

penguins2 |>
  gg_jitter(
    x = species, 
    y = body_mass_g, 
    col = sex, 
  ) +
  light_mode_t() +
  easy_remove_y_gridlines() +
  easy_remove_x_axis(what = c("line", "ticks")) +
  easy_remove_legend_title()