How to add text label to show total n in each bar of stacked proportion bars in ggplot?

Viewed 542

I have a stacked bar chart of proportions, so all bars total 100%. I would like to add a label to the end of each bar (i.e. on the far right-hand side of each bar, not within the bar itself) to show the total number of observations in each bar.

Something like this gets close-ish...

library(dplyr)
library(ggplot2)
data("mtcars")

mtcars %>% 
  # prep data
  mutate(across(where(is.numeric), as.factor)) %>% 
  count(am, cyl, gear) %>% 
  mutate(prop = n / sum(n)) %>% 
  # plot
  ggplot(aes(x = prop, y = cyl)) + 
  geom_col(aes(fill = gear), 
           position = "fill", 
           alpha = 0.8) +
  facet_wrap(~am, ncol = 1) + 
  theme_minimal() + 
  scale_x_continuous(labels = scales::percent) + 
  # add labels to show total n for each bar 
  geom_text(aes(label = paste0("n = ", stat(y)), group = cyl), 
            stat = 'summary', 
            fun = sum)

enter image description here

...but (i) the values for my n labels clearly aren't the sums for each bar that I was expecting, and (ii) I can't figure out how to position the labels at the end of each bar. I thought I could specify a location on the x-axis within the geom_text aes, like this...

 mtcars %>% 
  # prep data
  mutate(across(where(is.numeric), as.factor)) %>% 
  count(am, cyl, gear) %>% 
  mutate(prop = n / sum(n)) %>% 
  # plot
  ggplot(aes(x = prop, y = cyl)) + 
  geom_col(aes(fill = gear), 
           position = "fill", 
           alpha = 0.8) +
  facet_wrap(~am, ncol = 1) + 
  theme_minimal() + 
  scale_x_continuous(labels = scales::percent) + 
  # add labels to show total n for each bar 
  geom_text(aes(label = paste0("n = ", stat(y)), group = cyl, x = 1), 
            stat = 'summary', 
            fun = sum)

enter image description here

...but I can't work out why that throws the x-axis scale out, and doesn't position all the labels at the same location on the scale.

Thanks in advance for any suggestions!

3 Answers

Try this:

library(dplyr)
library(ggplot2)
data("mtcars")
#Code
mtcars %>% 
  # prep data
  mutate(across(where(is.numeric), as.factor)) %>% 
  count(am, cyl, gear) %>% 
  mutate(prop = n / sum(n)) %>% 
  # plot
  ggplot(aes(x = prop, y = cyl)) + 
  geom_col(aes(fill = gear), 
           position = "fill", 
           alpha = 0.8) +
  geom_text(aes(x=1.05,label = paste0("n = ", stat(y)), group = cyl),
            hjust=0.5
            )+
  facet_wrap(~am, ncol = 1,scales = 'free')+ 
  theme_minimal() + 
  scale_x_continuous(labels = scales::percent)

Output:

enter image description here

This is a modified version to add both proportions and numbers

library(dplyr)
library(ggplot2)
library(scales)
data("mtcars")

mtcars %>% 
  # prep data
  mutate(across(where(is.numeric), as.factor)) %>% 
  count(am, cyl, gear) %>% 
  mutate(prop = n / sum(n)) %>% 
  # plot
  ggplot(aes(x = prop, y = cyl)) + 
  geom_col(aes(fill = gear),
            position = "fill",  alpha = 0.8) +
  theme_minimal() + 
  scale_x_continuous(labels = scales::percent) + 
  # add labels to show total n for each bar 
  geom_text(aes(x = 1.1, , group = cyl, 
                label = paste0("n = ", stat(y))),
            hjust = 0.5) +
  geom_text(aes(x = prop, y = cyl, group = gear, 
                label = paste0('p =',round(stat(x),2))), 
            hjust = 0.5, angle = 0, 
            position = position_fill(vjust = .5)) +
  facet_wrap(~am, ncol = 1, scales = 'free')

enter image description here

It's not the most elegant solution, but I got there in the end by expanding on @Duck's answer for the positioning of labels (thanks!), and calculating the totals to be used as labels outside of ggplot.

mtcars %>% 
  # prep data
  mutate(across(where(is.numeric), as.factor)) %>%
  count(am, cyl, gear) %>%
  group_by(cyl, am) %>%
  mutate(prop = n / sum(n)) %>%
  mutate(column_total = sum(n)) %>%
  ungroup() %>% 
  # plot
  ggplot(aes(x = prop, y = cyl)) + 
  geom_col(aes(fill = gear), 
           position = "fill", 
           alpha = 0.8) +
  geom_text(aes(x = 1.05, label = paste0("n = ", column_total))) +
  facet_wrap(~am, ncol = 1, scales = 'free')+ 
  theme_minimal() + 
  scale_x_continuous(labels = scales::percent)

enter image description here

Related