Exploring the Wide World of ggplot2 Extensions

Eric R. Scott
Kristina Riemer
Renata Diaz

2024-06-20

Learning Objectives

  • Understand where to find packages that extend ggplot2
  • Demonstrate some extension packages
    • Assemble multi-panel figures with patchwork
    • Make animated plots with gganimate
    • Visualize distributions with ggdist
    • Plot network data with ggraph

Packages

library(ggplot2)
library(palmerpenguins) #for example dataset
library(patchwork) #for multi-panel figures
library(gridGraphics) #for combining ggplot2 and base R figures
library(gganimate) #for animated plots
library(ggdist) #for showing distributions + uncertainty in data
library(dplyr) #for cleaning data
library(ggraph) #for network data

Extensions to ggplot2

There are many kinds of extensions to ggplot2, ranging from simple to complex, from familiar to transformative.

General-use and modular

library(ggbeeswarm)
library(ggthemes)
ggplot(penguins, aes(x = species, y = body_mass_g)) +
  geom_beeswarm() +
  theme_economist()

“All-in-one” functions

library(ggstatsplot)
ggbetweenstats(penguins, species, body_mass_g) +
  scale_y_continuous("Body Mass (g)", n.breaks = 10)

Transformative & field-specific

ggspectra

ggspectra

gggenes

gggenes

Finding Extensions

Package Demos

patchwork

patchwork allows you to compose multi-panel figures with ease

Example plots

p1 <- 
  ggplot(penguins, aes(x = body_mass_g, 
                       y = bill_length_mm, 
                       color = species)) +
  geom_point()
p2 <-
  ggplot(penguins, aes(x = bill_depth_mm, 
                       y = bill_length_mm, 
                       color = species)) +
  geom_point()
p3 <- 
  ggplot(penguins, aes(x = body_mass_g)) + 
  facet_wrap(vars(island)) +
  geom_histogram()

Combine plots

  • + wraps plots
  • | combines plots horizontally
  • / combines plots vertically
  • () can be used to nest operations

Combine plots

(p1 | p2) / p3

Combine guides

If plots have identical guides, you can combine them with plot_layout(guides = "collect")

p1 + p2 + plot_layout(guides = "collect")

Combining axes

As of the most recent version of patchwork (v1.2.0) you can also combine identical axes.

p1 + p2 + plot_layout(guides = "collect", axes = "collect")

Controlling layout

For more options for controlling layout, see the related vignette on the package website.

  • Adding empty areas
  • Inset plots
  • Adjusting widths and heights

Tags

You can add “tags” to each panel with plot_annotation()

p1 + p2 + plot_annotation(tag_levels = "A", tag_suffix = ")")

Modifying all panels

You can use the & operator instead of + to modify all elements of a multi-panel figure.

p1 + p2 & theme_bw() & scale_color_viridis_d()

Using with base R plots

You can even combine ggplot2 plots with base R plots with a special syntax

p1 + ~hist(penguins$bill_depth_mm, main = "")

gganimate

Display transitions between states of a variable

Useful for showing trends in time series & spatial data

Advice from the experts!

“Graphic elements should only transition between instances of the same underlying phenomenon”

Example

Body size distribution of penguins changing over the years

ggplot(penguins, aes(x = body_mass_g, col = species)) +
  geom_density() +
  facet_wrap(~year)

Show annual transition

library(gganimate)

ggplot(penguins, aes(x = body_mass_g, col = species)) +
  geom_density() +
  transition_time(year) +
  ggtitle('Year: {frame_time}')

Add history

ggplot(penguins, aes(x = body_mass_g, col = species)) +
  geom_density() +
  transition_time(year) +
  ggtitle('Year: {frame_time}') +
  shadow_wake(wake = 0.3, wrap = FALSE)

Modify transition speed

ggplot(penguins, aes(x = body_mass_g, col = species)) +
  geom_density() +
  transition_time(year) +
  ggtitle('Year: {frame_time}') +
  ease_aes("quintic-in-out")

Have axes follow data

ggplot(penguins, aes(x = body_mass_g, col = species)) +
  geom_density() +
  transition_time(year) +
  ggtitle('Year: {frame_time}') +
  view_follow()

Save file

anim_save("~/Desktop/test_anim.gif")

ggdist for distributions

  • Easily add nuance to density/histogram/ribbon plots
  • Particularly useful for:
    • Sample data
    • Fitted values + uncertainty
    • Frequentist or Bayesian parameter distributions

slabinterval

penguins %>%
  ggplot(aes(x = body_mass_g, y = species)) +
  stat_slabinterval()

Example: dotsinterval

penguins %>%
  ggplot(aes(x = body_mass_g, y = species)) +
  stat_dotsinterval()

Example: interval

penguins %>%
  ggplot(aes(x = body_mass_g, y = species)) +
  stat_interval()

Example: interval

penguins %>%
  ggplot(aes(x = body_mass_g, y = species)) +
  stat_interval() +
  scale_color_brewer()

Combining elements: Raincloud plots

penguins %>%
  ggplot(aes(x = body_mass_g, y = species)) +
  stat_slabinterval() +
  stat_dotsinterval(side = "bottom")

Ribbon plots

stat_lineribbon plots quantile intervals around a line automatically:

penguins %>%
  ggplot(aes(x = year, y = body_mass_g)) +
  stat_lineribbon() +
  scale_fill_brewer() +
  facet_wrap(vars(species))

Ribbon plots

You can control the bands using the .width argument:

penguins %>%
  ggplot(aes(x = year, y = body_mass_g)) +
  stat_lineribbon(.width = c(.8, .97, .99)) +
  scale_fill_brewer() +
  facet_wrap(vars(species))

Ribbon plots

.width = ppoints() creates a gradient:

penguins %>%
  ggplot(aes(x = year, y = body_mass_g, fill = after_stat(.width))) +
  stat_lineribbon(.width = ppoints(100)) +
  scale_fill_distiller() +
  facet_wrap(vars(species))

Visualizing frequentist model output

Combined with the broom and distributional packages, ggdist can display frequentist model uncertainty.

Visualizing frequentist model output

library(broom)
library(distributional)

penguin_lm <- lm(body_mass_g ~  species, data = penguins)

broom::tidy(penguin_lm)
# A tibble: 3 × 5
  term             estimate std.error statistic   p.value
  <chr>               <dbl>     <dbl>     <dbl>     <dbl>
1 (Intercept)        3706.       38.1    97.2   6.88e-245
2 speciesChinstrap     26.9      67.7     0.398 6.91e-  1
3 speciesGentoo      1386.       56.9    24.4   1.01e- 75

Visualizing frequentist model output

broom::tidy(penguin_lm) |>
  ggplot(aes(y = term)) +
    stat_halfeye(
      aes(xdist = dist_student_t(df = df.residual(penguin_lm), 
                                 mu = estimate, 
                                 sigma = std.error))
    )

Visualizing frequentist model output

  • You can also use ribbons to show uncertainty around lines of fit.
  • We’ll need a linear model with a continuous predictor.
  • And we’ll use tidyr::expand and broom::augment to get the predicted line of fit from the model.

Visualizing frequentist model output

penguin_lm_cont <- lm(body_mass_g ~  year + species, data = penguins)

penguin_lm_cont_fitted <- penguins %>%
  group_by(species) %>%
  tidyr::expand(year = seq(min(year), max(year), length.out = 3)) %>%
  augment(penguin_lm_cont, newdata = ., se_fit = TRUE) 

head(penguin_lm_cont_fitted) 
# A tibble: 6 × 4
  species    year .fitted .se.fit
  <fct>     <dbl>   <dbl>   <dbl>
1 Adelie     2007   3703.    50.4
2 Adelie     2008   3706.    38.2
3 Adelie     2009   3709.    48.2
4 Chinstrap  2007   3730.    63.6
5 Chinstrap  2008   3733.    56.0
6 Chinstrap  2009   3737.    64.5

Visualizing frequentist model output

  ggplot(penguin_lm_cont_fitted, aes(x = year)) +
  stat_lineribbon(
    aes(ydist = dist_student_t(df = df.residual(penguin_lm_cont), 
                               mu = .fitted, 
                               sigma = .se.fit))) +
  facet_wrap(vars(species)) +
  scale_fill_brewer() 

Visualizing Bayesian model components

  • ggdist plots are great for visualizing Bayesian model components:
    • priors
    • posterior draws
    • posterior predictions
  • We can fit a simple Bayesian linear model and visualize the posterior…

Fitting a model

library(brms)
library(tidybayes)

penguins_brm <- brm(body_mass_g ~ species, data = penguins, iter = 1000)

Extracting draws from the posterior

penguins_brm %>%
tidybayes::gather_draws(
c(b_Intercept,
b_speciesChinstrap,
b_speciesGentoo)
) |>
head() 
# A tibble: 6 × 5
# Groups:   .variable [1]
  .chain .iteration .draw .variable   .value
   <int>      <int> <int> <chr>        <dbl>
1      1          1     1 b_Intercept  3646.
2      1          2     2 b_Intercept  3680.
3      1          3     3 b_Intercept  3694.
4      1          4     4 b_Intercept  3688.
5      1          5     5 b_Intercept  3632.
6      1          6     6 b_Intercept  3779.

Posterior draws rainclouds

penguins_brm |>
tidybayes::gather_draws(
c(b_Intercept,
b_speciesChinstrap,
b_speciesGentoo)) %>%
ggplot(aes(y = .variable, x = .value)) +  
  geom_dotsinterval(side = "bottom", dotsize = .05) + 
  stat_slabinterval() 

Takeaways

  • ggdist contains geom and stat functions for plotting distributions.
  • Slab functions show variations on density plots.
  • Ribbons show variation around a line.
  • ggdist plays nicely with Bayesian and frequentist modeling frameworks.

Relational data with ggraph

ggraph

  • For plotting networks, graphs, and trees
  • Adds layouts and geoms for nodes and edges
  • Integrates with tidygraph to wrap around numerous other graph object types (igraph, dedrogram, hclust, graph, phylo, …)

Example: Mouse data

Begin by loading network data, in this case from the graphml format.

Data from So et al. 2015, in the Animal Social Networks Repository.

library(igraph)
library(tidygraph)

mouse_sniffing <- read_graph(here::here("2024", "03-extensions", "mouse_so_grooming_network.graphml"),
format = "graphml")

Example: as_tbl_graph

tidygraph::as_tbl_graph standardizes different graph storage formats.

mouse_graph <- mouse_sniffing |>
as_tbl_graph() 

Plotting mouse sniffing data

ggraph(mouse_graph, layout = 'kk') + 
    geom_edge_fan() + 
    geom_node_point() +
    theme_minimal() +
    ggtitle("Mouse sniffs")

Alternate layouts

ggraph(mouse_graph, layout = 'eigen') + 
    geom_edge_fan() + 
    geom_node_point() +
    theme_minimal() +
    ggtitle("Mouse sniffs")

Alternate layouts

ggraph(mouse_graph, layout = 'linear', circular = TRUE) + 
    geom_edge_fan() + 
    geom_node_point() +
    theme_minimal() +
    ggtitle("Mouse sniffs")

Example: phylogenies

  • ggraph can plot phylogenetic trees.
  • We’ll get a phylogeny using the R Open Tree of Life (rotl) and plot it.

Getting a phylogeny

Getting a phylogeny

library(rotl)
rodent_id <- tnrs_match_names("Heteromyidae")
rodent_tree <- tol_subtree(ott_id = ott_id(rodent_id))

Progress [----------------------------------] 0/11 (  0) ?s
Progress [================================] 11/11 (100)  0s
                                                            
rodent_tree$node.label <- c(1:64)
class(rodent_tree)
[1] "phylo"

Convert the phylogeny to a graph

rodent_graph <- as_tbl_graph(rodent_tree)

Dendrogram

ggraph(rodent_graph, "dendrogram") +
geom_edge_link()

Unrooted tree

ggraph(rodent_graph, "unrooted") +
geom_edge_link()

Takeaways

  • ggraph can plot many different types of network objects.
  • Use as_tbl_graph to convert graphs to ggraph-compatible objects.
  • Layouts strongly determine the appearance and interpretability of a graph.

Extensions Resources

patchwork

gganimate

Extensions Resources

ggdist

ggraph

Getting Help

Anything we missed?

Got a ggplot2 question we didn’t cover in this workshop series? Let’s figure it out together!

Data sources

Pratha Sah, José David Mendez, Shweta Bansal. A multi-species repository of social networks. Scientific Data, 6:44 (2019)

So, N., Franks, B., Lim, S., and Curley, J.P. 2015. A social network approach reveals associations between mouse social dominance and brain gene expression. PlosOne 10(7):e0134509