This week’s #TidyTuesday data set is all about lemurs (with data from the Duke Lemur Center), so I figured I would stick with the animal theme and discuss a few functions from Claus Wilke’s cowplot
package, which is my personal favorite package for arranging plots made in R.
(Because everyone always asks: I believe the package name comes from the longhorn mascot of the University of Texas, with which Wilke is affiliated.)
Today’s data
The data set can be read in directly from the Tidy Tuesday Github repo as follows:
<- readr::read_csv('https://raw.githubusercontent.com/rfordatascience/tidytuesday/master/data/2021/2021-08-24/lemur_data.csv') lemurs
cowplot
is available on CRAN, so it can be installed if necessary with install.packages("cowplot")
and then loaded with the other packages needed for today’s plots. I also loaded a tibble of some common species names, available in the Tidy Tuesday readme, to match with the taxonomic codes.
library(tidyverse)
library(cowplot)
library(scales)
library(ggalt)
<- function () {
meg_theme theme_linedraw(base_size=11, base_family="Avenir") %+replace%
theme(
panel.background = element_blank(),
plot.background = element_rect(fill = "transparent", color = NA),
legend.background = element_rect(fill = "transparent", color = NA),
legend.key = element_rect(fill = "transparent", color = NA),
axis.ticks = element_blank(),
panel.grid.major = element_line(color = "grey90", size = 0.3),
panel.grid.minor = element_blank(),
plot.title.position = "plot",
plot.title = element_text(size = 18, hjust = 0, vjust = 0.5,
margin = margin(b = 0.2, unit = "cm")),
plot.subtitle = element_text(size = 10, hjust = 0, vjust = 0.5,
margin = margin(b = 0.4, unit = "cm")),
plot.caption = element_text(size = 7, hjust = 1, face = "italic",
margin = margin(t = 0.1, unit = "cm")),
axis.text.x = element_text(size = 13),
axis.text.y = element_text(size = 13)
)
}
<- tribble(
species ~"taxon", ~"species",
#--|--|----
"VRUB", "Red ruffed lemur",
"PCOQ", "Coquerel’s sifaka",
"VVV", "Black-and-white ruffed lemur",
"EMAC", "Black lemur",
"ECOL", "Collared brown lemur",
"LCAT", "Ring-tailed lemur",
"ERUF", "Red-fronted brown lemur",
"EUL", "Eulemur hybrid",
"ECOR", "Crowned lemur",
"EFLA", "Blue-eyed black lemur",
"EMON", "Mongoose lemur",
"MMUR", "Gray mouse lemur",
"MZAZ", "Northern giant mouse lemur",
"NCOU", "Slow loris",
"OGG", "Northern greater galago",
"CMED", "Fat-tailed dwarf lemur"
)
Inset images
My lemur knowledge is, admittedly, fairly limited. I found in the data that in many lemur species, females are actually heavier than males, and a quick Wikipedia trip informed me that females are often the dominant sex in the lemur social structure. These sample plots will examine the weight difference by sex and species.
To create the base plot above, I did some data manipulation to find the difference between the average weight, by species, for males and females. That new data frame, size
, was the basis of a dumbbell plot, which I assigned to size_plot
. The ggalt
package has a very convenient geom_dumbbell
function for creating dumbbell plots.
<- lemurs %>%
size # filter to non-pregnant adult records only
filter(preg_status == "NP" & sex != "ND" & age_category == "adult") %>%
# group by the individual lemur and filter to only the highest recorded weight
group_by(dlc_id) %>%
filter(max(weight_g) == weight_g) %>%
# group by species and sex to find the average
group_by(taxon, sex) %>%
summarize(avg = mean(weight_g),
total = n_distinct(dlc_id)) %>%
mutate(sex = ifelse(sex == "F", "Female", "Male")) %>%
# add an extra variable n to count the total number per species and filter
add_count(wt = total) %>%
filter(n > 40) %>%
select(-c(total)) %>%
# pivot so that there is one row per species and calculate the M/F difference
pivot_wider(names_from = sex, values_from = avg) %>%
mutate(difference = Female - Male,
perc = (Female - Male) / Male) %>%
# join in the common species name for labeling purposes
left_join(species, by = "taxon")
<- size %>%
size_plot ggplot(aes(x = Male, xend = Female, y = reorder(species, Female),
group = species)) +
geom_dumbbell(size = 2.5, color = "#dddddd",
colour_x = "#e5ded6", colour_xend = "#6a414a",
dot_guide = TRUE, dot_guide_size = 0.25) +
labs(y = NULL,
x = "Weight in grams",
title = "Females are heavier than males in some lemur species",
subtitle = "Average of maximum adult non-pregnant weight recorded, among species with 40+ subjects",
caption = "#TidyTuesday, data & photo from Duke Lemur Center, plot by @MeghanMHall") +
scale_x_continuous(limits = c(0, 5200)) +
annotate("text", x = 5170, y = 15, label = "Females",
family = "Avenir", size = 3, color = "#6a414a") +
meg_theme() +
theme(panel.grid.major.y = element_blank(),
axis.text.y = element_text(size = 8, hjust = 1),
plot.caption = element_text(hjust = 0),
plot.caption.position = "plot")
And now for cowplot
. The draw_image
function allows you to easily add an image to your plots, which can be convenient for sharing a hex image, a university/company logo, or in this case, a cute picture of lemurs. All you need is a url for the image, and then function arguments like x
, y
, and width
allow you to resize the image and place it on the base plot created previously.
The code below starts with ggdraw
and adds functions with +
, just like in ggplot2
code you’re probably used to. draw_plot
adds the base plot, size_plot
, created in the previous section, and draw_image
allows you to add and place the image. draw_plot_label
adds a label to the plot, which can be useful for a mini caption or a notation like “A” or “1” if you want to refer to the inset image in the text of your paper or whatever document (hopefully created with R Markdown!). This label can be placed with the x
and y
arguments and customized with standard family
and size
arguments to control the font and size.
ggdraw() +
draw_plot(size_plot) +
draw_image("https://lemur.duke.edu/wordpress/wp-content/uploads/2018/06/infants-judith-and-mae-1024x578.jpg",
x = 0.98, y = 0.35, hjust = 1, vjust = 1,
halign = 1, valign = 1, width = 0.4) +
draw_plot_label("Red ruffed lemurs", x = 0.7,
y = 0.38, size = 10, family = "Avenir")
Inset plots
Just like you can use cowplot
to add an inset image, you can add an inset plot. In the code below, I’m creating a separate data frame, finding the average weight for all species combined by sex, and then creating a very simple bar chart.
<- lemurs %>%
size_all # filter to non-pregnant adult records only, among species we used previously
filter(preg_status == "NP" & sex != "ND" & age_category == "adult"
& taxon %in% size$taxon) %>%
# group by the individual lemur and filter to only the highest recorded weight
group_by(dlc_id) %>%
filter(max(weight_g) == weight_g) %>%
mutate(sex = ifelse(sex == "F", "Female", "Male"))
<- size_all %>%
avg_plot # group by sex and find average weight
group_by(sex) %>%
summarize(avg = mean(weight_g)) %>%
ggplot(aes(x = sex, y = avg, fill = sex)) +
geom_bar(stat = "identity") +
scale_fill_manual(values = c("#6a414a", "#e5ded6"), guide = FALSE) +
labs(x = NULL,
y = "Average weight in grams") +
scale_y_continuous(limits = c(0,2050)) +
geom_text(aes(label = round(avg, 0)), vjust = -0.5, family = "Avenir") +
meg_theme() +
theme(panel.grid.major.x = element_blank(),
axis.text.x = element_text(size = 10),
axis.text.y = element_text(size = 10))
The code below to create an inset plot is very similar to the code in the previous section to create an inset image. ggdraw
starts with size_plot
, the original plot created in the first section, and then draw_plot
adds this new bar chart, avg_plot
, onto the base plot, just like we added the image previously. The x
, y
, width
, and height
arguments control the size and the placement of the inset plot.
I’ve also added draw_plot_label
to show how you can add multiple labels, one for each plot, which would allow for easy reference in text. Here, thanks to vectorized values within the label
, x
, and y
arguments, the label “1.” is placed to indicate the base plot, while “2.” is placed above the inset plot.
ggdraw(size_plot) +
draw_plot(avg_plot, x = .54, y = .08, width = .45, height = .4) +
draw_plot_label(label = c("1.", "2."), x = c(0, 0.94),
y = c(0.9, 0.51), size = 12, family = "Avenir")
Arranging plots in a grid
Knowing how to use inset images and inset plots is handy, but easily arranging plots side-by-side is likely a more common use case. And the plot_grid
function makes it easy to combine two plots or in more complex cases, add multiple elements and grids together.
The code below creates a bar graph, first_plot
, that plots, per species, the female weight difference over males. The bars are colored by whether females or males are on average heavier in that species. second_plot
is a jitter plot showing the individual weights by sex in each species.
<- size %>%
first_plot # create a new variable with the n to use as a label
mutate(label = paste0(species, "\nn = ", n)) %>%
ggplot(aes(x = perc, y = reorder(label, perc), fill = perc > 0)) +
geom_bar(stat = "identity") +
scale_fill_manual(values = c("#e5ded6", "#6a414a"), guide = FALSE) +
labs(y = NULL,
x = "Average female weight difference over male",
title = "Females are heavier than males in some lemur species",
subtitle = "Maximum adult non-pregnant weights recorded, among species with 40+ subjects",
caption = "#TidyTuesday, data & photo from Duke Lemur Center, plot by @MeghanMHall") +
scale_x_continuous(labels = percent_format(accuracy = 1)) +
annotate("text", x = 0.09, y = 10, label = "Females are\nheavier",
family = "Avenir", size = 4, color = "#6a414a") +
annotate("text", x = -0.1, y = 3, label = "Males are\nheavier",
family = "Avenir", size = 4, color = "#d1c9c0") +
meg_theme() +
theme(panel.grid.major.y = element_blank(),
axis.text.y = element_text(size = 8, hjust = 1),
axis.text.x = element_text(size = 10),
plot.caption = element_text(size = 7, hjust = 0, face = "italic",
margin = margin(t = 0.2, unit = "cm")),
plot.caption.position = "plot",
plot.subtitle = element_text(size = 10, hjust = 0, vjust = 0.5,
margin = margin(b = 0.8, unit = "cm")))
<- size_all %>%
second_plot left_join(select(size, perc, taxon, species), by = "taxon") %>%
ggplot(aes(x = weight_g, y = reorder(species, perc), color = sex)) +
geom_jitter(alpha = 0.5) +
scale_color_manual(values = c("#6a414a", "#e5ded6")) +
guides(color = guide_legend(override.aes = list(size = 5, alpha = 1))) +
labs(y = NULL,
color = NULL,
x = "Weight in grams") +
meg_theme() +
theme(panel.grid.major.y = element_blank(),
axis.text.y = element_blank(),
axis.text.x = element_text(size = 10),
legend.position = c(0.8, 0.5))
To place those plots neatly side-by-side, we can use plot_grid
. You can add a label (or two); in this case, average weights and individual weights, which I made room for by adding a bit of extra margin space below the subtitle in the code for first_plot
.
The rel_widths
argument controls the relative size of each plot. Here the plot on the left (first_plot
, the one listed first) is larger. align = "h"
ensures that the axes are lined up appropriately.
plot_grid(first_plot, second_plot, labels = c("average weights",
"individual weights"),
label_size = 9, label_fontfamily = "Avenir", label_x = c(0.5, 0.2),
label_y = 0.92, align = "h", rel_widths = c(1.5, 1))