layered-donut chart for a better subgroups-differentiation in R

171 Views Asked by At

I'm trying to find a way to plot the following layered-donut chart in R, so that I can differentiate the subgroups better. The graph is realized by Excel, by generating four different layers, and each subgroup has different amount of layers.

enter image description here

I have tried the following approach, but it is not ideal.

ggdonutchart approach

p <- data %>% group_by(Class) %>% summarise(num = sum(Freq))
ggdonutchart(p, x = "num", label = "Class", fill = "Class", 
             palette = c("darkblue","blue","lightblue","gray"), color = "white")

ggdonutchart

The problem with this approach is that trying to add another identical layer outside, i.e. PieDonut(PD, aes(Class, Class, count=n), title = "Titanic: Survival by Class"), will give me the error message: ! Each row of output must be identified by a unique combination of keys.

I would like to use the Titanic dataset to generate a chart similar to the first one, with different colors showing different groups. For example, dark blue for the share of 1st class, blue for the share of 2nd class, light blue for the share of 3rd class, and gray for the share of crew class. In other words, different color has different width.

Any idea how to do this? Thank you.

2

There are 2 best solutions below

2
MicL On BEST ANSWER
data <- data.frame(data.frame(stringsAsFactors=FALSE,
                                  Group = c("A", "B", "C", "D"),
                                  Share = c(0.25, 0.3, 0.2, 0.25)
))
data2 <- transform(data, Share = ifelse(Group == "A", 0, Share))
data3 <- transform(data2, Share = ifelse(Group == "B", 0, Share))
data4 <- transform(data3, Share = ifelse(Group == "C", 0, Share))

plt <- ggplot() + geom_col(aes(x = 2, y = Share, fill = Group), 
                           data = data, color = "white") + 
  geom_col(aes(x = 2.9, y = Share, fill = Group), 
           data = data2, color = "white") +
  geom_col(aes(x = 3.8, y = Share, fill = Group), 
           data = data3, color = "white") +
  geom_col(aes(x = 4.7, y = Share, fill = Group), 
           data = data4, color = "white") +
  scale_fill_manual(values = c('#bfbfbf', '#c4e1f2', '#4ea5d8', '#1b587c')) +
  xlim(0, 5.5) + labs(x = NULL, y = NULL) + 
  theme(axis.ticks=element_blank(),
        axis.text=element_blank(),
        axis.title=element_blank())

plt
plt + coord_polar(theta = "y") 

enter image description here

2
Allan Cameron On

It's certainly easy enough to recreate the plot in R. I'm not sure how we would go from your data to this plot, since you haven't included it in your question, but here's a fully reproducible example of how to get the plot:

library(tidyverse)

data.frame(x = rep(c('A', 'B', 'C', 'D'), times = c(4:1))) %>%
  ggplot(aes(x, y = 1, fill = x)) +
  geom_col(position = 'stack', color = 'white', linewidth = 0.1, width = 1) +
  coord_polar() +
  scale_y_continuous(limits = c(-10, 5)) +
  scale_fill_manual(values = c('#1b587c', '#4ea5d8', '#c4e1f2', '#bfbfbf')) +
  theme_void() +
  theme(plot.background = element_rect(fill = '#f2f2f2', color = NA))

enter image description here


Edit

To replicate the donut pie chart in vanilla ggplot, you probably need to shape your data into rectangles and plot them:

library(tidyverse)

data <- as.data.frame(Titanic)

data %>% 
  group_by(Class, Survived) %>% 
  summarise(n = sum(Freq), .groups = "drop") %>%
  mutate(xmin = c(0, cumsum(head(n, -1))),
         xmax = cumsum(n), ymin = 4, ymax = 5) %>%
  group_by(Class) %>%
  reframe(Survived = Survived, n = n, xmin = xmin, xmax = xmax,
          ymin = ymin, ymax = ymax, perc = scales::percent(n / sum(n))) %>%
  bind_rows(data %>%
              group_by(Class) %>%
              summarise(Survived = "All", n = sum(Freq), .groups = "drop") %>%
              mutate(xmin = c(0, cumsum(head(n, -1))),
                     xmax = cumsum(n), ymin = 2, ymax = 4,
                     perc = scales::percent(n / sum(n)))) %>%
  ggplot() +
  geom_rect(aes(xmin = xmin, xmax = xmax, alpha = Survived,
                ymin = ymin, ymax = ymax, fill = Class),
            color = "white") +
  geom_text(aes(x = (xmin + xmax)/2, y = ifelse(Survived == "All", 3, 4.5), 
                label = paste(ifelse(Survived == "All", 
                                     as.character(Class), 
                                     Survived), perc, sep = "\n"))) +
  annotate("text", 0, 0, label = "Class", size = 8) +
  scale_y_continuous(limits = c(0, 5)) +
  scale_alpha_manual(values = c(1, 0.3, 0.6), guide = "none") +
  guides(fill = "none") +
  coord_polar() +
  theme_void()

enter image description here