Raincloud plots
Description
Raincloud plots are a combination of density graph, a box plot, and a beeswarm (or jitter) plot, and are used to compare distributions of quantitative/numerical variables across the levels of a categorical (or discrete) grouping variable.
We can use the raincloudplots
package to create raincloud plots, or they can be built using the ggdist
package and geoms from ggplot2
.
Getting set up
PACKAGES:
Install packages.
Code
::install_github('jorvlan/raincloudplots')
remotes::install_github('mjskay/ggdist')
remoteslibrary(raincloudplots)
library(ggdist)
library(palmerpenguins)
library(ggplot2)
DATA:
Remove the missing values from year
and flipper_length_mm
the penguins
data. The raincloudplots
package has a data_1x1()
function we can use to build the dataset for a 1x1 repeated measure graph (peng_1x1
).
This function takes two array arguments (array_1
and array_2
), which we create with the flipper length (flipper_length_mm
) for two levels of year
in the peng_raincloud
data.
The jit_distance
and jit_seed
refer to the points in the plot.
Code
# remove missing
<- palmerpenguins::penguins |>
peng_raincloud filter(!is.na(year) & !is.na(body_mass_g))
# filter flipper length by years 2008 & 2009
<- raincloudplots::data_1x1(
peng_1x1 array_1 = dplyr::filter(peng_raincloud, year == 2008)$flipper_length_mm,
array_2 = dplyr::filter(peng_raincloud, year == 2009)$flipper_length_mm,
jit_distance = 0.2, # distance between points
jit_seed = 2736) # used in set.seed()
glimpse(peng_1x1)
Rows: 233
Columns: 4
$ y_axis <int> 186, 188, 190, 200, 187, 191, 186, 193, 181, 194, 185, 195, 185…
$ x_axis <dbl> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, …
$ id <fct> 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, …
$ jit <dbl> 1.0200375, 0.9498208, 0.8820888, 0.9516411, 0.9689052, 1.053578…
The grammar (raincloudplots
)
CODE:
Create labels with labs()
Use the raincloudplots::raincloud_1x1()
to build the plot, assigning peng_1x1
to data_1x1
- assign colors
and fills
- set the size (of the points) and alpha (for opacity)
Code
<- raincloudplots::raincloud_1x1(
ggp2_raincloud data_1x1 = peng_1x1,
colors = (c('#0bd3d3', '#fa7b3c')),
fills = (c('#0bd3d3', '#fa7b3c')),
size = 0.8,
alpha = 3/4,
ort = 'h')
<- ggp2_raincloud +
ggp2_raincloud_x ::scale_x_continuous(
ggplot2breaks = c(1, 2),
labels = c("2008", "2009"),
limits = c(0, 3))
+
ggp2_raincloud_x ::labs(
ggplot2title = "Flipper length of Palmer penguins",
subtitle = "Years 2008 & 2009",
x = "Year",
y = "Flipper length (mm)")
GRAPH:
More info
DATA:
We’ll use the peng_raincloud
data (with the missing values removed from species
and body_mass_g
).
Code
# remove missing
<- palmerpenguins::penguins |>
peng_raincloud filter(!is.na(species) &
!is.na(body_mass_g))
glimpse(peng_raincloud)
Rows: 342
Columns: 8
$ species <fct> Adelie, Adelie, Adelie, Adelie, Adelie, Adelie, Adel…
$ island <fct> Torgersen, Torgersen, Torgersen, Torgersen, Torgerse…
$ bill_length_mm <dbl> 39.1, 39.5, 40.3, 36.7, 39.3, 38.9, 39.2, 34.1, 42.0…
$ bill_depth_mm <dbl> 18.7, 17.4, 18.0, 19.3, 20.6, 17.8, 19.6, 18.1, 20.2…
$ flipper_length_mm <int> 181, 186, 195, 193, 190, 181, 195, 193, 190, 186, 18…
$ body_mass_g <int> 3750, 3800, 3250, 3450, 3650, 3625, 4675, 3475, 4250…
$ sex <fct> male, female, female, female, male, female, male, NA…
$ year <int> 2007, 2007, 2007, 2007, 2007, 2007, 2007, 2007, 2007…
ggplot2::geom_boxplot()
Create labels with labs()
Initialize the graph with ggplot()
and provide data
For the first layer, we create a box plot with geom_boxplot()
, but include notches and remove the outliers.
Code
<- ggplot2::labs(
labs_raincloud_2 title = "Flipper length of Palmer penguins",
x = "Flipper length (mm)",
y = "Species")
<- ggplot(peng_raincloud,
ggp2_box aes( x = flipper_length_mm, y = species)) +
geom_boxplot(aes(fill = species),
notch = TRUE,
notchwidth = 0.9,
width = 0.15,
outlier.shape = NA,
show.legend = FALSE)
+
ggp2_box labs_raincloud_2
ggdist::stat_halfeye()
We then add a horizontal density curve with ggdist::stat_halfeye()
, mapping species
to fill
, and adjusting the size and shape of the density curve and shifting it slightly above the box plot.
Code
<- ggp2_box +
ggp2_stat_halfeye ::stat_halfeye(aes(fill = species),
ggdistadjust = 0.6, # shape = adjust * density estimator
.width = 0, # can use probabilities or 0
point_colour = NA, # removes the point in center
orientation = "horizontal", # like the box plot
height = 0.5, # height of curve
justification = -0.3, # shift vertically above box
show.legend = FALSE # don't need this
) +
ggp2_stat_halfeye labs_raincloud_2
ggplot2::geom_point()
The final layer is a geom_point()
, mapping fill
to species
and setting position
to position_jitter()
. Additional adjustments to the points include:
Using
shape = 21
, we cancolor
the outside of the point (white makes it appear to glow).Manually set the
height
, which refers to the vertical area for the points
Code
<- ggp2_stat_halfeye +
ggp2_jitter geom_point(aes(fill = species),
position = position_jitter(
seed = 321,
height = .07),
shape = 21,
color = "#ffffff",
alpha = 1/3,
size = 1.8,
show.legend = FALSE)
+
ggp2_jitter labs_raincloud_2
More examples & resources
Point shape
Cédric Scherer covered raincloud plots in this great write-up for #TidyTuesday.
Cédric also covers some alternative methods for plotting the points (I particularly like using bands instead of points when displaying the rainclouds vertically).
We can switch to this layout by applying ggplot2::coord_flip()
to the ggp2_stat_halfeye
layer, then adding geom_point()
with shape
set to 95
Code
+
ggp2_stat_halfeye ::coord_flip() +
ggplot2::geom_point(
ggplot2shape = 95,
size = 8,
alpha = 0.2) +
theme(legend.position = "none") +
labs_raincloud_2
Polished Graph
The code to re-create the #TidyTuesday graph is contained in this gist.