Skip to content

Instantly share code, notes, and snippets.

@z3tt
Last active November 5, 2024 23:10
Show Gist options
  • Save z3tt/8b2a06d05e8fae308abbf027ce357f01 to your computer and use it in GitHub Desktop.
Save z3tt/8b2a06d05e8fae308abbf027ce357f01 to your computer and use it in GitHub Desktop.
Polished raincloud plot using the Palmer penguins data
library(dplyr)
library(forcats)
library(ggplot2)
library(palmerpenguins)
library(ggtext)
library(colorspace)
library(ragg)
url <- "https://raw.githubusercontent.com/allisonhorst/palmerpenguins/master/man/figures/lter_penguins.png"
img <- magick::image_read((url))
pic <- grid::rasterGrob(img, interpolate = TRUE)
pal <- c("#FF8C00", "#A034F0", "#159090")
add_sample <- function(x) {
return(c(y = max(x) + .025,
label = length(x)))
}
penguins |>
group_by(species) |>
mutate(bill_ratio = bill_length_mm / bill_depth_mm) |>
filter(!is.na(bill_ratio)) |>
ggplot(aes(x = fct_rev(species), y = bill_ratio)) +
ggdist::stat_halfeye(
aes(color = species,
fill = after_scale(lighten(color, .5))),
adjust = .5,
width = .75,
.width = 0,
justification = -.4,
point_color = NA
) +
geom_boxplot(
aes(color = stage(species, after_scale = darken(color, .1, space = "HLS")),
fill = after_scale(desaturate(lighten(color, .8), .4))),
width = .42,
outlier.shape = NA
) +
geom_point(
aes(color = stage(species, after_scale = darken(color, .1, space = "HLS"))),
fill = "white",
shape = 21,
stroke = .4,
size = 2,
position = position_jitter(seed = 1, width = .12)
) +
geom_point(
aes(fill = species),
color = "transparent",
shape = 21,
stroke = .4,
size = 2,
alpha = .3,
position = position_jitter(seed = 1, width = .12)
) +
stat_summary(
geom = "text",
fun = "median",
aes(label = round(after_stat(y), 2),
color = stage(species, after_scale = darken(color, .1, space = "HLS"))),
family = "Roboto Mono",
fontface = "bold",
size = 4.5,
vjust = -3.5
) +
stat_summary(
geom = "text",
fun.data = add_sample,
aes(label = paste("n =", after_stat(label)),
color = stage(species, after_scale = darken(color, .1, space = "HLS"))),
family = "Roboto Condensed",
size = 4,
hjust = 0
) +
coord_flip(xlim = c(1.2, NA), clip = "off") +
annotation_custom(pic, ymin = 2.9, ymax = 3.85, xmin = 2.7, xmax = 4.7) +
scale_y_continuous(
limits = c(1.57, 3.8),
breaks = seq(1.6, 3.8, by = .2),
expand = c(.001, .001)
) +
scale_color_manual(values = pal, guide = "none") +
scale_fill_manual(values = pal, guide = "none") +
labs(
x = NULL,
y = "Bill ratio",
title = "Bill Ratios of Brush–Tailed Penguins (*Pygoscelis* spec.)",
subtitle = "Distribution of bill ratios, estimated as bill length divided by bill depth.",
caption = "Gorman, Williams & Fraser (2014) *PLoS ONE* DOI: 10.1371/journal.pone.0090081<br>Visualization: Cédric Scherer &bull; Illustration: Allison Horst"
) +
theme_minimal(base_family = "Zilla Slab", base_size = 15) +
theme(
panel.grid.minor = element_blank(),
panel.grid.major.y = element_blank(),
axis.ticks = element_blank(),
axis.text.x = element_text(family = "Roboto Mono"),
axis.text.y = element_text(
color = rev(darken(pal, .1, space = "HLS")),
size = 18
),
axis.title.x = element_text(margin = margin(t = 10),
size = 16),
plot.title = element_markdown(face = "bold", size = 21),
plot.subtitle = element_text(
color = "grey40", hjust = 0,
margin = margin(0, 0, 20, 0)
),
plot.title.position = "plot",
plot.caption = element_markdown(
color = "grey40", lineheight = 1.2,
margin = margin(20, 0, 0, 0)),
plot.margin = margin(15, 15, 10, 15)
)
@VimiVaron
Copy link

Hi Cédric,

Thank you for the script. It was so helpful. Also, thank you for your blog.

@z3tt
Copy link
Author

z3tt commented Jun 14, 2024

Hey Vimi, thanks for the feedback, glad to hear it's helpful!

@Creeki
Copy link

Creeki commented Oct 25, 2024

Thank you so much, Cédric, for your tutorial!

@z3tt
Copy link
Author

z3tt commented Oct 28, 2024

Thanks for the feedback Creeki! I've just updated the code to add some missing libraries, use the base pipe, and make proper use of after_scale() in combination with stage() and after_stat().

@Creeki
Copy link

Creeki commented Oct 28, 2024

Superb Cédric!

@francisvolh
Copy link

francisvolh commented Oct 29, 2024

Hi Cédric, thanks for the awesome work! I have been able to use it for some plots I wanted to make. Thanks!!

I have a couple of quick questions for you though. First of all I get a few warnings (some are font related, which are not supported on my Windows database, so very unimportant) and one is

'1: Vectorized input to element_text() is not officially supported.'
And I think it may come from line 99 color = rev(darken(pal, .1, space = "HLS")), because if I comment out that axis.text.y = warning goes away.

Also, I am trying to avoid loading libraries in all my scripts, and just calling the function from the library like ggplot2::ggplot(). If I do this, the ggplot2::stage in the geom_boxplot (or any other stage) does not recognize the species as an object, on line 35 for example. If I simply define color = species (with no stage, it works.

I guess this is all unimportant! but I wondered if you hade any comments about this! thanks!

ps: I have a forked gist of what I have done if you have a minute to see it here

@z3tt
Copy link
Author

z3tt commented Nov 4, 2024

Thanks for your feedback @francisvolh 🙌

cf. warning fonts:
Exactly, the fonts need to be installed locally. All font files are available via GoogleFonts:
https://fonts.google.com/specimen/Zilla+Slab
https://fonts.google.com/specimen/Roboto+Condensed
https://fonts.google.com/specimen/Roboto+Mono

cf. warning element_text:
Yes, vectorized inputs are not supported for theme elements but work! The warning means the resulting behavior is not guaranteed and the trick might stop working at some point in future. I use it here to color the labels on the y-axis -- it doesn't matter if you use the darken function or not, the warning pops up because we feed a vector to the color argument. Just passing c("red", "green", "blue") would cause the same warning, while setting a single color e.g. "black" does not.

cf. namespace:
Hm, interesting. As I rarely use namespace convention when using ggplot2, I've never run into this issue. You mean when specifying ggplot2::aes(color = ggplot2::stage(species, after_scale = colorspace::darken(color, .1, space = "HLS")))), ggplot can't find the species column? There is a related issue on the ggplot2 repo, maybe this helps: tidyverse/ggplot2#6104.

@francisvolh
Copy link

Thanks for the comments @z3tt! And thanks a lot also for the input on my warnings/bullet points!

I checked the thread cf. namespace and it got a bit complicated for me to understand the idea of stage not being a proper function but I guess I can just avoid using "stage" and go on with my life for life! But you were on point, if no loading the library, and specifying ggplot2::stage and it seems expected from the issue you linked me to.... and I think they dont want or thinkg is something to be fixed (as stage seems not to be a proper function).

Cheers! and thanks again!

@z3tt
Copy link
Author

z3tt commented Nov 5, 2024

Tbh that discussion is also beyond me 🤓 Exactly, you can use the logic without stage() by repeating the aesthetic, e.g.

geom_point(
  aes(color = species, 
      color = after_scale(darken(color, .1, space = "HLS")))
)

It's not the official way to do it and you'll get a warning but it works perfectly fine and I often do it myself. Just trying to avoid spreading the unofficial approach in my workshops and tutorials.

PS: However, you might run into the same issue when using the afterscale() function without loading {ggplot2} because it's not considered a "true function" as well.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment