
26 Grouped violin plots
theme_aim()
Graphs in this chapter use fmggp2::theme_aim(), modeled on the AOL Instant Messenger sign on window: a gray window, an AOL blue frame and title, a white chat panel, blue, sky, running man yellow, and slate marks, and an Arial Rounded title.
26.1 Description
A ‘violin plot’ is a variation of a density or ridgeline plot, where the distribution is plotted symmetrically, creating a two-sided, smoothed distribution.
26.2 Set up
PACKAGES:
Install packages.
show/hide
library(ggplot2)
library(dplyr) # for tibble() and data manipulationDATA:
In the late 90s, your AOL Instant Messenger (AIM) buddy list was a status symbol. We’ll simulate buddy list sizes for 150 users at each level of school, and each level gets a different kind of distribution. Middle schoolers’ lists are counts from rpois(). High schoolers’ lists come from rlnorm(), so a few popular kids have much longer lists. College students are a mix of two groups (runif() decides which): most have about 60 buddies, and about 30% are heavy users with about 150. Setting the seed with set.seed() means you’ll get the same 450 users every time you run the code below.
show/hide
set.seed(1997) # same 450 users every time the code runs
aim_buddies <- tibble(
user_id = 1:450, # 450 different AIM users, one row each
school = rep(c("Middle school", "High school", "College"), each = 150) # 150 users per level
) |>
mutate(
school = factor(school,
levels = c("Middle school", "High school", "College")), # youngest to oldest
buddies = case_when( # size of each buddy list
school == "Middle school" ~ rpois(n(), lambda = 20), # short, tight lists
school == "High school" ~ round(rlnorm(n(), meanlog = log(45),
sdlog = 0.45)), # longer lists with a right tail
TRUE ~ round(if_else(runif(n()) < 0.7, # college has two kinds of users
rnorm(n(), mean = 60, sd = 10), # most keep about 60 buddies
rnorm(n(), mean = 150, sd = 20))) # some keep about 150
)
)
glimpse(aim_buddies)
#> Rows: 450
#> Columns: 3
#> $ user_id <int> 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1…
#> $ school <fct> Middle school, Middle school, Mi…
#> $ buddies <dbl> 16, 23, 21, 25, 18, 18, 11, 23, …26.3 Distribution
Violin plots are at their best when groups differ in shape, not just in center and spread. Summary numbers (and box plots built from them) can make three very different distributions look alike. Below we’ll move from the users, to a picture, to the formula.
COUNT IT:
Start with the first eight users (all middle schoolers).
show/hide
head(aim_buddies$buddies, 8)
#> [1] 16 23 21 25 18 18 11 23These middle schoolers have between 11 and 25 buddies. The summary below shows each group’s mean, median, and standard deviation. For middle schoolers the mean and median are close, for high schoolers the mean is a little higher, and for college students the mean is almost 20 buddies higher than the median. The numbers hint that something is different about the college group, but they don’t tell us what.
show/hide
aim_buddies |>
group_by(school) |>
summarise(mean = mean(buddies), median = median(buddies),
sd = sd(buddies))DRAW IT:
The graph below draws a narrow box plot inside each violin. The middle school violin is a short, symmetric diamond. The high school violin has a wide base and a tail that stretches up. The college violin has two bulges, one near 60 buddies and one near 150, with almost nobody in between. The college box plot hides that gap completely: it looks like one long, lopsided box.
show/hide
ggplot(aim_buddies, aes(x = school, y = buddies, fill = school)) +
geom_violin(alpha = 1/2, show.legend = FALSE) +
geom_boxplot(width = 0.12, fill = "#FFFFFF", outlier.shape = NA) +
labs(x = NULL, y = "Buddies")
NAME IT:
Each level of school has its own distribution:
\[ \begin{aligned} \text{Middle school} &\sim \text{Poisson}(\lambda = 20) \\ \log(\text{High school}) &\sim \text{Normal}(\log(45), \; 0.45) \\ \text{College} &\sim 0.7 \cdot \text{Normal}(60, 10) + 0.3 \cdot \text{Normal}(150, 20) \end{aligned} \]
The Poisson gives a narrow, nearly symmetric violin because its standard deviation is only \(\sqrt{20} \approx 4.5\). The log normal gives the long upper tail. The mixture gives two peaks, and its mean (\(0.7 \times 60 + 0.3 \times 150 = 87\)) lands in the empty space between them. A box plot only knows about quartiles, so it can’t show a gap like that. Try moving the two college means closer together in rnorm() and the two bulges merge into one.
26.4 Grammar
CODE:
Create labels with
labs()Initialize the graph with
ggplot()and providedataMap
schoolto thex,buddiesto they, andschooltofillSet
alphato2/3Remove the legend with
show.legend = FALSE
show/hide
labs_grp_violin <- labs(
title = "AOL Instant Messenger",
caption = "Simulated data",
subtitle = "Buddy list sizes for 150 users at each level of school",
x = "School", fill = "School",
y = "Buddies")
ggp2_grp_violin <- ggplot(data = aim_buddies,
aes(x = school,
y = buddies,
fill = school)) +
geom_violin(alpha = 2/3,
show.legend = FALSE)
ggp2_grp_violin +
labs_grp_violinGRAPH:
Violin plots can allow us to compare the ‘center’ and ‘spread’ of continuous variables across categorical groups.
show/hide
labs_grp_violin <- labs(
title = "AOL Instant Messenger",
caption = "Simulated data",
subtitle = "Buddy list sizes for 150 users at each level of school",
x = "School", fill = "School",
y = "Buddies")
ggp2_grp_violin <- ggplot(data = aim_buddies,
aes(x = school,
y = buddies,
fill = school)) +
geom_violin(alpha = 2/3,
show.legend = FALSE)
ggp2_grp_violin +
labs_grp_violin
26.5 More info
26.5.1 Quartiles
Change the shape of the line with linetype and linewidth.
We can include lines for the 25th, 50th, and 75th percentiles (the three quartiles) using the quantiles argument, and draw them with quantile.linetype = 1.
show/hide
ggplot(data = aim_buddies,
aes(x = school,
y = buddies,
fill = school)) +
geom_violin(
quantiles = c(0.25, 0.5, 0.75),
quantile.linetype = 1,
alpha = 1/2,
linewidth = 0.5,
show.legend = FALSE)
26.5.2 Kernel
The kernel argument lets us change the “kernel density estimate” used to create the violin shape. The possible kernel density estimates are "gaussian", "epanechnikov", "rectangular", "triangular", "biweight", "cosine", and "optcosine".
show/hide
ggplot(data = aim_buddies,
aes(x = school,
y = buddies,
fill = school)) +
geom_violin(alpha = 1/2,
linewidth = 0.5,
kernel = "rectangular",
show.legend = FALSE) +
labs_grp_violin +
labs(caption = "kernel = rectangular")
26.5.3 Bandwidth
We can directly adjust the shape of the violin with the bw argument, which is the standard deviation of the smoothing kernel. The trim argument trim(s) the tails of the violins to the range of the data.
show/hide
# bw of 2
grp_violin_bw2 <- ggplot(data = aim_buddies,
aes(x = school,
y = buddies,
fill = school)) +
geom_violin(bw = 2,
alpha = 2/3,
trim = TRUE,
show.legend = FALSE)
grp_violin_bw2 +
labs_grp_violin +
labs(caption = "bw = 2")
# bw of 20
grp_violin_bw20 <- ggplot(data = aim_buddies,
aes(x = school,
y = buddies,
fill = school)) +
geom_violin(bw = 20,
alpha = 2/3,
trim = TRUE,
show.legend = FALSE)
grp_violin_bw20 +
labs_grp_violin +
labs(caption = "bw = 20")
