2

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!

monkeytennis
  • 848
  • 7
  • 18

4 Answers4

1

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

Duck
  • 39,058
  • 13
  • 42
  • 84
  • I still need to obtain the correct values for n, but this at least puts the labels in the correct position, thank you! – monkeytennis Dec 08 '20 at 19:58
  • @monkeytennis Always a pleasure helping. You answered yourself to your issue. If in any sense the answer helped, consider potentially accpeting it. Many thanks :) – Duck Dec 08 '20 at 20:38
1

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

  • Did you by any chance find a way to this when the barplots are not all adding to the same (eg some add to 75%, others to 50%, etc) https://stackoverflow.com/questions/72894374/add-different-labels-to-show-totals-in-stacked-bar-plot-in-ggplot-r – lu-202 Jul 07 '22 at 08:20
  • in the like you specified the solution is present there. The idea is to prepare different dataframes for mapping or use calculation method inside aes argument – Ibrahim Hassan Oct 05 '22 at 13:40
0

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

monkeytennis
  • 848
  • 7
  • 18
0

Just as a little addition to @monkeytennis solution, if you don't want the labels to be printed multiple times on top of each other (i.e. the number of times you have groups/stacks, per category), just filter your data in the geom_text() like so:

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(data = . %>% slice_head(n=1), 
  aes(x = 1.05, label = paste0("n = ", column_total))) +
  facet_wrap(~am, ncol = 1, scales = 'free')+ 
  theme_minimal() + 
  scale_x_continuous(labels = scales::percent)