Skip to content

Instantly share code, notes, and snippets.

@Kudusch
Last active July 28, 2025 11:45
Show Gist options
  • Save Kudusch/577b6f07c686a64a3aace685fd9f3bee to your computer and use it in GitHub Desktop.
Save Kudusch/577b6f07c686a64a3aace685fd9f3bee to your computer and use it in GitHub Desktop.
A function using ggplot2 and dplyr to create stacked donut charts
set.seed(133742)
library(ggplot2)
library(dplyr)
# split_donut function to create stacked donut/pie charts
split_donut <- function(data, group, subgroup, n) {
nths <- function(x, i) {
x[(1:length(x)%%i)==0]
}
group <- ensym(group)
subgroup <- ensym(subgroup)
n <- ensym(n)
data |>
rename(
cat_1 = !!group,
cat_2 = !!subgroup,
n = !!n
) |>
select(cat_1, cat_2, n) |>
mutate(across(1:2, as.factor)) |>
arrange(desc(cat_1)) |>
group_by(cat_1) |>
mutate(cat_1_n = sum(n)) |>
ungroup() |>
arrange(desc(cat_1)) |>
mutate(cat_1_y = (((nths((cat_1_n)/2, 2)))+lag(cumsum(nths(cat_1_n, 2)), default = 0)) |> rep(each = 2)) |>
mutate(cat_1_y = ifelse(as.numeric(cat_2) == 2, cat_1_y, NA)) |>
arrange(desc(cat_1)) |>
mutate(cat_2_y = (n/2)+lag(cumsum(n), default = 0)) |>
ggplot(aes(y = n, fill = cat_1)) +
geom_col(
aes(x = 1, color = cat_1),
position = "stack",
width = 1.05
) +
geom_text(
aes(label = sprintf("%s\n%1.0f%%", cat_1, (cat_1_n/sum(cat_1_n))*100), x = 1, y = cat_1_y)
) +
geom_col(
aes(x = 2, group = cat_1, alpha = cat_2),
color = "white",
linewidth = 1.5,
position = "stack"
) +
geom_text(
aes(label = cat_2, x = 2, y = cat_2_y)
) +
scale_y_continuous(labels = scales::percent) +
scale_alpha_manual(values = c(1, .75)) +
coord_polar(theta="y") +
labs(fill = group, color = group, alpha = subgroup) +
theme_void() +
theme(plot.margin = unit(c(1,1,1,1), "lines"))
}
# Examples
p <- tibble(
favorite_flavor = rep(c("chocolate", "vanilla", "strawberry"), 2),
gender = rep(c("male", "female"), 3),
n = sample(1:10, 6)
) |>
split_donut(favorite_flavor, gender, n)
p
p <- tibble(
mode_of_transportation = rep(c("bus", "car", "bike", "walk"), 2),
gender = rep(c("male", "female"), each = 4),
count = sample(5:10, 8, replace = TRUE)
) |>
split_donut(mode_of_transportation, gender, count)
p
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment