How to center stacked percent barchart labels - r

I am trying to plot nice stacked percent barchart using ggplot2. I've read some material and almost manage to plot, what I want. Also, I enclose the material, it might be useful in one place:
How do I label a stacked bar chart in ggplot2 without creating a summary data frame?
Create stacked barplot where each stack is scaled to sum to 100%
R stacked percentage bar plot with percentage of binary factor and labels (with ggplot)
My problem is that I can't place labels where I want - in the middle of the bars.
You can see the problem in the picture above - labels looks awfull and also overlap each other.
What I am looking for right now is:
How to place labels in the midde of the bars (areas)
How to plot not all the labels, but for example which are greather than 10%?
How to solve overlaping problem?
For the Q 1. #MikeWise suggested possible solution. However, I still can't deal with this problem.
Also, I enclose reproducible example, how I've plotted this grahp.
library('plyr')
library('ggplot2')
library('scales')
set.seed(1992)
n=68
Category <- sample(c("Black", "Red", "Blue", "Cyna", "Purple"), n, replace = TRUE, prob = NULL)
Brand <- sample("Brand", n, replace = TRUE, prob = NULL)
Brand <- paste0(Brand, sample(1:5, n, replace = TRUE, prob = NULL))
USD <- abs(rnorm(n))*100
df <- data.frame(Category, Brand, USD)
# Calculate the percentages
df = ddply(df, .(Brand), transform, percent = USD/sum(USD) * 100)
# Format the labels and calculate their positions
df = ddply(df, .(Brand), transform, pos = (cumsum(USD) - 0.5 * USD))
#create nice labes
df$label = paste0(sprintf("%.0f", df$percent), "%")
ggplot(df, aes(x=reorder(Brand,USD,
function(x)+sum(x)), y=percent, fill=Category))+
geom_bar(position = "fill", stat='identity', width = .7)+
geom_text(aes(label=label, ymax=100, ymin=0), vjust=0, hjust=0,color = "white", position=position_fill())+
coord_flip()+
scale_y_continuous(labels = percent_format())+
ylab("")+
xlab("")

Here's how to center the labels and avoid plotting labels for small percentages. An additional issue in your data is that you have multiple bar sections for each colour. Instead, it seems to me all the bar sections of a given colour should be combined. The code below uses dplyr instead of plyr to set up the data for plotting:
library(dplyr)
# Initial data frame
df <- data.frame(Category, Brand, USD)
# Calculate percentages
df.summary = df %>% group_by(Brand, Category) %>%
summarise(USD = sum(USD)) %>% # Within each Brand, sum all values in each Category
mutate(percent = USD/sum(USD))
With ggplot2 version 2, it is no longer necessary to calculate the coordinates of the text labels to get them centered. Instead, you can use position=position_stack(vjust=0.5). For example:
ggplot(df.summary, aes(x=reorder(Brand, USD, sum), y=percent, fill=Category)) +
geom_bar(stat="identity", width = .7, colour="black", lwd=0.1) +
geom_text(aes(label=ifelse(percent >= 0.07, paste0(sprintf("%.0f", percent*100),"%"),"")),
position=position_stack(vjust=0.5), colour="white") +
coord_flip() +
scale_y_continuous(labels = percent_format()) +
labs(y="", x="")
With older versions, we need to calculate the position. (Same as above, but with an extra line defining pos):
# Calculate percentages and label positions
df.summary = df %>% group_by(Brand, Category) %>%
summarise(USD = sum(USD)) %>% # Within each Brand, sum all values in each Category
mutate(percent = USD/sum(USD),
pos = cumsum(percent) - 0.5*percent)
Then plot the data using an ifelse statement to determine whether a label is plotted or not. In this case, I've avoided plotting a label for percentages less than 7%.
ggplot(df.summary, aes(x=reorder(Brand,USD,function(x)+sum(x)), y=percent, fill=Category)) +
geom_bar(stat='identity', width = .7, colour="black", lwd=0.1) +
geom_text(aes(label=ifelse(percent >= 0.07, paste0(sprintf("%.0f", percent*100),"%"),""),
y=pos), colour="white") +
coord_flip() +
scale_y_continuous(labels = percent_format()) +
labs(y="", x="")

I followed the example and found the way how to put nice labels for simple stacked barchart. I think it might be usefull too.
df <- data.frame(Category, Brand, USD)
# Calculate percentages and label positions
df.summary = df %>% group_by(Brand, Category) %>%
summarise(USD = sum(USD)) %>% # Within each Brand, sum all values in each Category
mutate( pos = cumsum(USD)-0.5*USD)
ggplot(df.summary, aes(x=reorder(Brand,USD,function(x)+sum(x)), y=USD, fill=Category)) +
geom_bar(stat='identity', width = .7, colour="black", lwd=0.1) +
geom_text(aes(label=ifelse(USD>100,round(USD,0),""),
y=pos), colour="white") +
coord_flip()+
labs(y="", x="")

Related

ggplot2 barplot - adding percentage labels inside the stacked bars but retaining counts on the y-axis

I have created an stacked barplot with the counts of a variables. I want to keep these as counts, so that the different bar sizes represent different group sizes. However, inside the bar plot i would like to add labels that show the proportion of each stack - in terms of percentage.
I managed to create the stacked plot of count for every group. Also I have created the labels and they are are placed correctly. What i struggle with is how to calculate the percentage there?
I have tried this, but i get an error:
dataex <- iris %>%
dplyr::group_by(group, Species) %>%
dplyr::summarise(N = n())
names(dataex)
dataex <- as.data.frame(dataex)
str(dataex)
ggplot(dataex, aes(x = group, y = N, fill = factor(Species))) +
geom_bar(position="stack", stat="identity") +
geom_text(aes(label = ifelse((..count..)==0,"",scales::percent((..count..)/sum(..count..)))), position = position_stack(vjust = 0.5), size = 3) +
theme_pubclean()
Error in (count) == 0 : comparison (1) is possible only for atomic
and list types
desired result:
well, just found answer ... or workaround. Maybe this will help someone in the future: calculate the percentage before the ggplot and then just just use that vector as labels.
dataex <- iris %>%
dplyr::group_by(group, Species) %>%
dplyr::summarise(N = n()) %>%
dplyr::mutate(pct = paste0((round(N/sum(N)*100, 2))," %"))
names(dataex)
dataex <- as.data.frame(dataex)
str(dataex)
ggplot(dataex, aes(x = group, y = N, fill = factor(Species))) +
geom_bar(position="stack", stat="identity") +
geom_text(aes(label = dataex$pct), position = position_stack(vjust = 0.5), size = 3) +
theme_pubclean()

How to change the order of fill aesthetic in faceted ggplot?

I have a faceted ggplot that is all but done. I cannot seem to get the fill aesthetic to be descending for each group in the dodged plot and across facets. The idea is to look at the plot and quickly recognise the top three categories within each group on the y-axis - and that the colors will be order different for each group. Here is some code to get a representative graph.
library(tidyverse)
set.seed(123)
#using crossing from purrr
df <- crossing(
mean = 1:8,
cats = sample(letters[1:3], 8, T),
gender = c('Male', 'Female')) %>%
mutate(vary_x = sample(seq(1,3,.1),nrow(.), T))
df %>%
ggplot(aes(mean, vary_x, fill = cats))+
geom_bar(stat = 'identity',
position = 'dodge') +
facet_grid(.~gender) +
coord_flip()
Something like this maybe:
df %>%
ggplot(aes(mean, reorder(vary_x,mean), fill = cats))+
geom_bar(stat = 'identity',
position = 'dodge') +
facet_grid(.~gender) +
coord_flip()

Plotting a bar chart with years grouped together

I am using the fivethirtyeight bechdel dataset, located here https://github.com/rudeboybert/fivethirtyeight, and am attempting to recreate the first plot shown in the article here https://fivethirtyeight.com/features/the-dollar-and-cents-case-against-hollywoods-exclusion-of-women/. I am having trouble getting the years to group together similarly to how they did in the article.
This is the current code I have:
ggplot(data = bechdel, aes(year)) +
geom_histogram(aes(fill = clean_test), binwidth = 5, position = "fill") +
scale_fill_manual(breaks = c("ok", "dubious", "men", "notalk", "nowomen"),
values=c("red", "salmon", "lightpink", "dodgerblue",
"blue")) +
theme_fivethirtyeight()
I see where you were going with using the histogram geom but this really looks more like a categorical bar chart. Once you take that approach it's easier, after a bit of ugly code to get the correct labels on the year columns.
The bars are stacked in the wrong order on this one, and there needs to be some formatting applied to look like the 538 chart, but I'll leave that for you.
library(fivethirtyeight)
library(tidyverse)
library(ggthemes)
library(scales)
# Create date range column
bechdel_summary <- bechdel %>%
mutate(date.range = ((year %/% 10)* 10) + ((year %% 10) %/% 5 * 5)) %>%
mutate(date.range = paste0(date.range," - '",substr(date.range + 5,3,5)))
ggplot(data = bechdel_summary, aes(x = date.range, fill = clean_test)) +
geom_bar(position = "fill", width = 0.95) +
scale_y_continuous(labels = percent) +
theme_fivethirtyeight()
ggplot

dodge columns in ggplot2

I am trying to create a picture that summarises my data. Data is about prevalence of drug use obtained from different practices form different countries. Each practice has contributed with a different amount of data and I want to show all of this in my picture.
Here is a subset of the data to work on:
gr<-data.frame(matrix(0,36))
gr$drug<-c("a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","b","b","b","b","b","b","b","b","b","b","b","b","b","b","b","b","b","b")
gr$practice<-c("a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r")
gr$country<-c("c1","c1","c1","c1","c1","c1","c1","c1","c1","c1","c2","c2","c2","c2","c2","c2","c3","c3","c1","c1","c1","c1","c1","c1","c1","c1","c1","c1","c2","c2","c2","c2","c2","c2","c3","c3")
gr$prevalence<-c(9.14,5.53,16.74,1.93,8.51,14.96,18.90,11.18,15.00,20.10,24.56,22.29,19.41,20.25,25.01,25.87,29.33,20.76,18.94,24.60,26.51,13.37,23.84,21.82,23.69,20.56,30.53,16.66,28.71,23.83,21.16,24.66,26.42,27.38,32.46,25.34)
gr$prop<-c(0.027,0.023,0.002,0.500,0.011,0.185,0.097,0.067,0.066,0.023,0.433,0.117,0.053,0.199,0.098,0.100,0.594,0.406,0.027,0.023,0.002,0.500,0.011,0.185,0.097,0.067,0.066,0.023,0.433,0.117,0.053,0.199,0.098,0.100,0.594,0.406)
gr$low.CI<-c(8.27,4.80,12.35,1.83,7.22,14.53,18.25,10.56,14.28,18.76,24.25,21.72,18.62,19.83,24.36,25.22,28.80,20.20,17.73,23.15,21.06,13.12,21.79,21.32,22.99,19.76,29.60,15.41,28.39,23.25,20.34,24.20,25.76,26.72,31.92,24.73)
gr$high.CI<-c(10.10,6.37,22.31,2.04,10.00,15.40,19.56,11.83,15.74,21.52,24.87,22.86,20.23,20.68,25.67,26.53,29.86,21.34,20.21,26.10,32.79,13.63,26.02,22.33,24.41,21.39,31.48,17.98,29.04,24.43,22.01,25.12,27.09,28.05,33.01,25.95)
The code I wrote is this
p<-ggplot(data=gr, aes(x=factor(drug), y=as.numeric(gr$prevalence), ymax=max(high.CI),position="dodge",fill=practice,width=prop))
colour<-c(rep("gray79",10),rep("gray60",6),rep("gray39",2))
p + theme_bw()+
geom_bar(stat="identity",position = position_dodge(0.9)) +
labs(x="Drug",y="Prevalence") +
geom_errorbar(ymax=gr$high.CI,ymin=gr$low.CI,position=position_dodge(0.9),width=0.25,size=0.25,colour="black",aes(x=factor(drug), y=as.numeric(gr$prevalence), fill=practice)) +
ggtitle("Drug usage by country and practice") +
scale_fill_manual(values = colour)+ guides(fill=F)
The figure I obtain is this one where bars are all on top of each other while I want them "dodge".
I also obtain the following warning:
ymax not defined: adjusting position using y instead
Warning message:
position_dodge requires non-overlapping x intervals
Ideally I would get each bar near one another, with their error bars in the middle of its bar, all organised by country.
Also should I be concerned about the warning (which I clearly do not fully understand)?
I hope this makes sense. I hope I am close enough, but I don't seem to be going anywhere, some help would be greatly appreciated.
Thank you
ggplot's geom_bar() accepts the width parameter, but doesn't line them up neatly against one another in dodged position by default. The following workaround references the solution here:
library(dplyr)
# calculate x-axis position for bars of varying width
gr <- gr %>%
group_by(drug) %>%
arrange(practice) %>%
mutate(pos = 0.5 * (cumsum(prop) + cumsum(c(0, prop[-length(prop)])))) %>%
ungroup()
x.labels <- gr$practice[gr$drug == "a"]
x.pos <- gr$pos[gr$drug == "a"]
ggplot(gr,
aes(x = pos, y = prevalence,
fill = country, width = prop,
ymin = low.CI, ymax = high.CI)) +
geom_col(col = "black") +
geom_errorbar(size = 0.25, colour = "black") +
facet_wrap(~drug) +
scale_fill_manual(values = c("c1" = "gray79",
"c2" = "gray60",
"c3" = "gray39"),
guide = F) +
scale_x_continuous(name = "Drug",
labels = x.labels,
breaks = x.pos) +
labs(title = "Drug usage by country and practice", y = "Prevalence") +
theme_classic()
There is a lot of information you are trying to convey here - to contrast drug A and drug B across countries using the barplots and accounting for proportions, you might use the facet_grid function. Try this:
colour<-c(rep("gray79",10),rep("gray60",6),rep("gray39",2))
gr$drug <- paste("Drug", gr$drug)
p<-ggplot(data=gr, aes(x=factor(practice), y=as.numeric(prevalence),
ymax=high.CI,ymin = low.CI,
position="dodge",fill=practice, width=prop))
p + theme_bw()+ facet_grid(drug~country, scales="free") +
geom_bar(stat="identity") +
labs(x="Practice",y="Prevalence") +
geom_errorbar(position=position_dodge(0.9), width=0.25,size=0.25,colour="black") +
ggtitle("Drug usage by country and practice") +
scale_fill_manual(values = colour)+ guides(fill=F)
The width is too small in the C1 country and as you indicated the one clinic is quite influential.
Also, you can specify your aesthetics with the ggplot(aes(...)) and not have to reset it and it is not needed to include the dataframe objects name in the aes function within the ggplot call.

put total observation number (n) on top of stacked percentage barplot in ggplot

I have a stacked percentage barplot in ggplot, I'd like to put the total observation number on top of the stacked bars (while keeping the stacked bars in percentages). Yet I keep running into problems.
Below is my code to produce the percentage barplot:
# sample dataset
set.seed(123)
cat1<-sample(letters[1:3], 500, replace=T, prob=c(0.1, 0.2, 0.65))
cat2<-sample(letters[4:8], 500, replace=T, prob=c(0.3, 0.4, 0.75, 0.5, 0.1))
df <- data.frame(cat1, cat2)
# the barplot
ggplot(df, aes(x=cat1))+
geom_bar(aes(fill = cat2),
position = 'fill',color = "black")+
scale_y_continuous(labels = scales::percent)+
labs ( y = "Percentage")+
# this final line is me trying to add the label
geom_text(aes(label=cat1))
# this is the observation number I want display
table(df$cat1)
# but I get this error:
Error: geom_text requires the following missing aesthetics: y
so I have 2 questions:
how do I put the total observation number for each of cat1 "N=" label on top of each stacked bar)?
What exactly is the y for the barplot in my code(aes(x=...))? I have x, but no y, but the plot seems to work..
thanks!
If you don't want to hardcode your summary labels, here's a slightly different approach (but still a bit of a hack) using dplyr to calculate your percentages and format your labels.
I've also reversed your legend to match the order on the chart :)
library(dplyr)
df2 <- df %>%
group_by(cat1, cat2) %>%
summarise(n=n())%>%
mutate(percent = (n / sum(n)), cumsum = cumsum(percent), label=ifelse(cat2=="h", paste0("N=", sum(n)),""))
ggplot(df2,aes(x=cat1, y=percent, fill=cat2)) +
scale_y_continuous(labels = scales::percent) +
labs ( y = "Percentage") +
geom_bar(position = 'fill',color = "black", stat="identity") +
geom_text(aes(y=cumsum, label=label), vjust=-1) +
guides(fill=guide_legend(reverse=T))
you could try
temp <- data.frame(x=c("a", "b", "c"), y=c(1.02, 1.02, 1.02), z=c(51, 101, 348))
ggplot(df, aes(x=cat1))+
geom_bar(aes(fill = cat2),
position = 'fill',color = "black")+
scale_y_continuous(labels = scales::percent)+
labs ( y = "Percentage")+
# this final line is me trying to add the label
geom_text(data=temp, aes(x=x, y=y, label=as.factor(z)))

Resources