How to Draw Stacked Bars in Ggplot2 That Show Percentages Based on Group

How to draw stacked bars in ggplot2 that show percentages based on group?

It's not entirely clear if you want percentages or amount, and whether or not to include labels. But you should be able to modify this to suit your needs. It is often easier to calculate summaries outside the ggplot call.

df is your data file.

library(plyr)
library(ggplot2)

# Get the levels for type in the required order
df$type = factor(df$type, levels = c("inter", "VIIT", "HIIT"))
df = arrange(df, year, desc(type))

# Calculate the percentages
df = ddply(df, .(year), transform, percent = amount/sum(amount) * 100)

# Format the labels and calculate their positions
df = ddply(df, .(year), transform, pos = (cumsum(amount) - 0.5 * amount))
df$label = paste0(sprintf("%.0f", df$percent), "%")

# Plot
ggplot(df, aes(x = factor(year), y = amount, fill = type)) +
geom_bar(stat = "identity", width = .7) +
geom_text(aes(y = pos, label = label), size = 2) +
coord_flip()


Edit

Revised plot: from about ggplot 2.1.0, geom_text gets a position_fill / position_stack, and thus there is no longer a need to calculate nor use the y aesthetic pos to position the labels.

ggplot(df, aes(x = factor(year), y = amount, fill = type)) +
geom_bar(position = position_stack(), stat = "identity", width = .7) +
geom_text(aes(label = label), position = position_stack(vjust = 0.5), size = 2) +
coord_flip()

Sample Image

Adding labels to percentage stacked barplot ggplot2

To put the percentages in the middle of the bars, use position_fill(vjust = 0.5) and compute the proportions in the geom_text. These proportions are proportions on the total values, not by bar.

library(ggplot2)

colors <- c("#00405b", "#008dca", "#c0beb8", "#d70000", "#7d0000")
colors <- setNames(colors, levels(newDoto$Q29_1String))

ggplot(newDoto, aes(pid3lean, fill = Q29_1String)) +
geom_bar(position = position_fill()) +
geom_text(aes(label = paste0(..count../sum(..count..)*100, "%")),
stat = "count",
colour = "white",
position = position_fill(vjust = 0.5)) +
scale_fill_manual(values = colors) +
coord_flip()

Sample Image


Package scales has functions to format the percentages automatically.

ggplot(newDoto, aes(pid3lean, fill = Q29_1String)) +
geom_bar(position = position_fill()) +
geom_text(aes(label = scales::percent(..count../sum(..count..))),
stat = "count",
colour = "white",
position = position_fill(vjust = 0.5)) +
scale_fill_manual(values = colors) +
coord_flip()

Sample Image



Edit

Following the comment asking for proportions by bar, below is a solution computing the proportions with base R only first.

tbl <- xtabs(~ pid3lean + Q29_1String, newDoto)
proptbl <- proportions(tbl, margin = "pid3lean")
proptbl <- as.data.frame(proptbl)
proptbl <- proptbl[proptbl$Freq != 0, ]

ggplot(proptbl, aes(pid3lean, Freq, fill = Q29_1String)) +
geom_col(position = position_fill()) +
geom_text(aes(label = scales::percent(Freq)),
colour = "white",
position = position_fill(vjust = 0.5)) +
scale_fill_manual(values = colors) +
coord_flip() +
guides(fill = guide_legend(title = "29")) +
theme_question_70539767()

Sample Image



Theme to be added to plots

This theme is a copy of the theme defined in TarJae's answer, with minor changes.

theme_question_70539767 <- function(){
theme_bw() %+replace%
theme(panel.grid.major = element_blank(),
panel.grid.minor = element_blank(),
panel.border = element_blank(),
text = element_text(size = 19, family = "serif"),
axis.ticks = element_blank(),
axis.title.y = element_blank(),
axis.title.x = element_blank(),
axis.text.x = element_blank(),
axis.text.y = element_text(color = "black"),
legend.position = "top",
legend.text = element_text(size = 10),
legend.key.size = unit(1, "char")
)
}

Add percentage labels to stacked bar chart ggplot2

Your approach did not work because the labels are not in % but the raw values. You have to do the stats on your own:

df <- read.table(text="County  Group   Plan1   Plan2   Plan3   Plan4   Plan5   Total
County1 Group1 2019 597 513 5342 3220 11691
County2 Group1 521 182 130 1771 731 3335
County3 Group1 592 180 126 2448 1044 4390
County4 Group1 630 266 284 2298 937 4415
County5 Group1 708 258 171 2640 1404 5181
County6 Group1 443 159 71 1580 528 2781
County7 Group1 492 187 157 1823 900 3559
County8 Group1 261 101 84 1418 357 2221", header = TRUE)

library(tidyverse)
df %>%
filter(Group == "Group1") %>%
select(-Total) %>%
gather(key = `Plan Type`, value = value, -County, -Group) %>%
group_by(County, Group) %>%
mutate(Percentage = value/sum(value)) %>%
ggplot(aes(x = County, y = Percentage, fill = `Plan Type`, label = paste0(round(Percentage*100), "%"))) +
geom_col(position = position_stack(), color = "black") +
geom_text(position = position_stack(vjust = .5)) +
coord_flip() +
scale_y_continuous(labels = scales::percent_format())

Edit:

The code above works as well for more plans as well as for more groups, but the plot will not account for that. Just add facet_wrap to produce also a flexible plot regarding the groups:

df %>% 
filter(Group == "Group1") %>%
select(-Total) %>%
gather(key = `Plan Type`, value = value, -County, -Group) %>%
group_by(County, Group) %>%
mutate(Percentage = value/sum(value)) %>%
ggplot(aes(x = County, y = Percentage, fill = `Plan Type`, label = paste0(round(Percentage*100), "%"))) +
geom_col(position = position_stack(), color = "black") +
geom_text(position = position_stack(vjust = .5)) +
coord_flip() +
scale_y_continuous(labels = scales::percent_format()) +
facet_wrap(~Group)

How to use percentage as label in stacked bar plot?

I think what OP wanted was labels on the actual sections of the bars. We can do this using data.table to get the count percentages and the formatted percentages and then plot using ggplot:

library(data.table)
library(scales)
dt <- setDT(df)[,list(count = .N), by = .(sample,class)][,list(class = class, count = count,
percent_fmt = paste0(formatC(count*100/sum(count), digits = 2), "%"),
percent_num = count/sum(count)
), by = sample]

ggplot(data=dt, aes(x=sample, y= percent_num, fill=class)) +
geom_bar(position=position_fill(reverse=TRUE), stat = "identity", width=0.7) +
geom_text(aes(label = percent_fmt),position = position_stack(vjust = 0.5)) + coord_flip()

Sample Image

Edit: Another solution where you calculate the y-value of your label in the aggregate. This is so we don't have to rely on position_stack(vjust = 0.5):

dt <- setDT(df)[,list(count = .N), by = .(sample,class)][,list(class = class, count = count,
percent_fmt = paste0(formatC(count*100/sum(count), digits = 2), "%"),
percent_num = count/sum(count),
cum_pct = cumsum(count/sum(count)),
label_y = (cumsum(count/sum(count)) + cumsum(ifelse(is.na(shift(count/sum(count))),0,shift(count/sum(count))))) / 2
), by = sample]

ggplot(data=dt, aes(x=sample, y= percent_num, fill=class)) +
geom_bar(position=position_fill(reverse=TRUE), stat = "identity", width=0.7) +
geom_text(aes(label = percent_fmt, y = label_y)) + coord_flip()

Ggplot stacked bar plot with percentage labels

You need to group_by team to calculate the proportion and use pct in aes :

library(dplyr)
library(ggplot2)

ashes_df %>%
count(team, role) %>%
group_by(team) %>%
mutate(pct= prop.table(n) * 100) %>%
ggplot() + aes(team, pct, fill=role) +
geom_bar(stat="identity") +
ylab("Number of Participants") +
geom_text(aes(label=paste0(sprintf("%1.1f", pct),"%")),
position=position_stack(vjust=0.5)) +
ggtitle("England & Australia Team Make Up") +
theme_bw()

Sample Image

Add percentage labels to a stacked barplot

You could do something like this...

#set positions for labels
example.melt$labelpos <- ifelse(example.melt$variable=="percent.bad",
example.melt$value/2, 1 - example.melt$value/2)
ggplot(example.melt, aes(x=example.Category, y=value, fill = variable)) +
geom_bar(position = "fill", stat = "identity",color='black',width=0.9) +
scale_y_continuous(labels = scales::percent) +
#use positions to plot labels
geom_text(aes(label = paste0(100*value,"%"),y=labelpos),size = 3)

Sample Image

Adding percentages for the whole group in a stacked ggplot2 bar chart

I would suggest creating pre calculated data.frame. I'll do it with dplyr but you can use whatever you comfortable with:

library('dplyr')

df2 <- df %>%
arrange(Var2, desc(Var1)) %>% # Rearranging in stacking order
group_by(Var2) %>% # For each Gr in Var2
mutate(Freq2 = cumsum(Freq), # Calculating position of stacked Freq
prop = 100*Freq/sum(Freq)) # Calculating proportion of Freq

df2

# A tibble: 9 x 5
# Groups: Var2 [3]
Var1 Var2 Freq Freq2 prop
<chr> <chr> <dbl> <dbl> <dbl>
1 C Gr1 2 2 11.76471
2 B Gr1 5 7 29.41176
3 A Gr1 10 17 58.82353
4 C Gr2 10 10 34.48276
5 B Gr2 4 14 13.79310
6 A Gr2 15 29 51.72414
7 C Gr3 15 15 65.21739
8 B Gr3 3 18 13.04348
9 A Gr3 5 23 21.73913

And resulting plot:

ggplot(data = df2,
aes(x = Var2, y = Freq,
fill = Var1)) +
geom_bar(stat = "identity") +
geom_text(aes(y = Freq2 + 1,
label = sprintf('%.2f%%', prop)))

Resulting ploy

Edit:

Okay, I misunderstood you a bit. But I'll use same approach - in my experience it's better to leave most of calculations out of ggplot, it'll be more predictable that way.

df %>% 
mutate(tot = sum(Freq)) %>%
group_by(Var2) %>% # For each Gr in Var2
summarise(Freq = sum(Freq)) %>%
mutate(Prop = 100*Freq/sum(Freq))

ggplot(data = df,
aes(x = Var2, y = Freq)) +
geom_bar(stat = "identity",
aes(fill = Var1)) +
geom_text(data = df2,
aes(y = Freq + 1,
label = sprintf('%.2f%%', Prop)))

New plot:
New plot



Related Topics



Leave a reply



Submit