IDHTG how to annotate plots

A new post in my I Don’t Have to Google (IDHTG) series. I’ve been wanting to work out how to add annotations to plots for a while and this plot from the @WeAreRladies account this week got me inspired!


Giorgia, who is curating WeAreRLadies this week, suggested checking out this blog post by Cedric Scherer. It is a great illustration of how you might start with a basic box plot and turn it into something that looks REALLY fancy, step by step.

Lets plot some penguin data to practice.

load packages

library(tidyverse)
library(palmerpenguins)

read data

penguins <- penguins

plot body mass by flipper length

Add text to a ggplot using annotate(), just define x and y coordinates and what you want the label to say.

g_text <- penguins %>%
  ggplot(aes(x = flipper_length_mm, y = body_mass_g, colour = species)) +
  geom_point() +
  annotate("text", x = 180, y = 6000, label = "Add text here") +
  annotate("text", x = 220, y = 3000, label = "More text here")
  
  
g_text

To add arrows, first make a tibble that has x1, x2, y1, and y2 coordinates for where you want the curved line to start and stop. On my penguin plot I want two arrows, one in the top left and another in the bottom right.

arrows <- 
  tibble(
    x1 = c(187, 212),
    x2 = c(205, 204),
    y1 = c(6000, 3000), 
    y2 = c(5500, 3500)
  )

arrows
## # A tibble: 2 x 4
##      x1    x2    y1    y2
##   <dbl> <dbl> <dbl> <dbl>
## 1   187   205  6000  5500
## 2   212   204  3000  3500

Then use geom_curve() to add arrows, adjusting and adjusting and adjusting until they fall where you want them.

g_text +
  geom_curve(
    data = arrows, aes(x = x1, y = y1, xend = x2, yend = y2),
    arrow = arrow(length = unit(0.08, "inch")), size = 0.5,
    color = "gray20", curvature = -0.3)

We should probably make the annotations meaningful.

penguins %>%
  ggplot(aes(x = flipper_length_mm, y = body_mass_g, colour = species)) +
  geom_point() +
  annotate("text", x = 180, y = 6000, label = "Gentoo penguins \n are much larger") +
  annotate("text", x = 223, y = 3000, label = "It is hard to differentiate \n Chinstrap and Adelie") +
  geom_curve(
    data = arrows, aes(x = x1, y = y1, xend = x2, yend = y2),
    arrow = arrow(length = unit(0.08, "inch")), size = 0.5,
    color = "gray20", curvature = -0.3)

And Miss 9 thought the colour pallete could do with some work. The Luna Lovegood palette from the harrypotter package is her favourite.

library(harrypotter)

penguins %>%
  ggplot(aes(x = flipper_length_mm, y = body_mass_g, colour = species)) +
  geom_point() +
  annotate("text", x = 180, y = 6000, label = "Gentoo penguins \n are much larger") +
  annotate("text", x = 223, y = 3000, label = "It is hard to differentiate \n Chinstrap and Adelie") +
  geom_curve(
    data = arrows, aes(x = x1, y = y1, xend = x2, yend = y2),
    arrow = arrow(length = unit(0.08, "inch")), size = 0.5,
    color = "gray20", curvature = -0.3) +
  scale_colour_hp_d(option = "LunaLovegood")

I love twitter… intro to ggannotate

One of the best things about blogging about what you learn is that you can give it away and then people let you know about new/different/easier ways to do what you just learned to do.

I shared this post on Twitter and within a few minutes Matt Cowgill let me know that he has been working on a package called ggannotate which lets you add notes and arrows to your ggplot, using a point and click interface via a Shiny app.

How cool is that??

You can install the package from github using this line of code

remotes::install_github("mattcowgill/ggannotate")

library(ggannotate)

Start with an annotation free plot…

penguins %>%
  ggplot(aes(x = flipper_length_mm, y = body_mass_g, colour = species)) +
  geom_point() +
  scale_colour_hp_d(option = "LunaLovegood")

To use ggannotate you can…

  1. select the ggplot code and use the Addins pulldown to open the ggannotate tool OR
  2. assign your ggplot to an object (maybe called plot) and then call ggannotate::ggannotate(plot) OR
plot <- penguins %>%
  ggplot(aes(x = flipper_length_mm, y = body_mass_g, colour = species)) +
  geom_point() +
  scale_colour_hp_d(option = "LunaLovegood")

ggannotate(plot)
  1. you can wrap your ggplot code in a ggannotate call like this
ggannotate(penguins %>%
  ggplot(aes(x = flipper_length_mm, y = body_mass_g, colour = species)) +
  geom_point() +
  scale_colour_hp_d(option = "LunaLovegood"))

Each option will open a shiny app that you can use to point and click where you want your annotations/curves and then copy the code back into your markdown. The downside is that at this stage you need to add each annotation and curve separately and the code that the tool generates is longer than if you had done it manually. But you don’t have to write it so I guess that doesn’t matter!

Matt said that adding multiple annotations in single call is in the works, so watch this space.

penguins %>%
  ggplot(aes(x = flipper_length_mm, y = body_mass_g, colour = species)) +
  geom_point() +
  scale_colour_hp_d(option = "LunaLovegood") +
  geom_text(data = data.frame(x = 188.437797399636, y = 5990.9008070379, label = "Gentoo penguins are much larger"),
mapping = aes(x = x, y = y, label = label),
size = 3.86605783866058, alpha = 1, inherit.aes = FALSE) +
  geom_text(data = data.frame(x = 219.749643585604, y = 3366.61344597189, label = "It is hard to differentiate \n Chinstrap and Gentoo"),
mapping = aes(x = x, y = y, label = label),
size = 3.86605783866058, alpha = 1, inherit.aes = FALSE) +
  geom_curve(data = data.frame(x = 199.308325132123, y = 6014.12458899424, xend = 208.642800032846, yend = 5781.88676943088),
mapping = aes(x = x, y = y, xend = xend, yend = yend),
curvature = -0.515, arrow = arrow(30L, unit(0.1, "inches"),
"last", "closed"),
alpha = 1, inherit.aes = FALSE) +
  geom_curve(data = data.frame(x = 211.833063606511, y = 3320.16588205921, xend = 203.443851986874, yend = 3680.13450238243),
mapping = aes(x = x, y = y, xend = xend, yend = yend),
curvature = -0.59, arrow = arrow(30L, unit(0.1, "inches"),
"last", "closed"),
alpha = 1, inherit.aes = FALSE)