'Ordering bars AND stacks in a horizontal ggplot bar chart
Similar questions have been asked before but this question relates to the case when I wish to order the bars and the stacks in a ggplot bar chart simultaneously.
Here is my data:
data = data.frame(gender = c("Female", "Female", "Female", "Male", "Male", "Male"),
BMI = c("Healthy", "Obese", "Overweight", "Healthy", "Obese", "Overweight"),
Value = c(10,20,30,25,35,45),
Colour = c("#009698", "#ed1651", "#f26522", "#009698", "#ed1651", "#f26522"))
The colour column corresponds to the BMI column.
I would like to plot a horizontal bar chart with the "Female" bar at the top and the "Male" bar underneath. Further I would like the stacks of the bar to be in the following order: Healthy, Overweight, Obese, with healthy to the left then overweight, then obese. And further still I would like each stack to have a specific colour as defined in the Colour column of the data frame. Ideally I would like to have the legend in the following order: Healthy, Overweight, Obese.
So far I have managed to order the gender as I want it and order the stacks within each bar but I do not like the approach. I'm in danger of mis-labelling the graphic. I am ordering using factors. Is there a better way?
x = c("Male", "Female")
data$gender = factor(data$gender,levels=x)
# Order the bmi categories so we have them in a specific order
x = c("Obese", "Overweight", "Healthy")
data$BMI = factor(data$BMI, levels=x)
p=ggplot(data=data, aes(x=gender, y=proportion, fill=data$BMI)) +
geom_bar(position="stack", stat="identity") +
scale_y_continuous(labels = scales::percent, limits=c(0,1)) +
geom_text(aes(label = count), size=2, angle=0, position=position_stack(vjust=0.1)) +
coord_flip() +
xlab("") + ylab("") +
theme(legend.title = element_blank(), legend.position="bottom")
This code gives this except the labels are different to those I supplied in the data frame above.

I am finding it very easy to mis-plot (numbers not corresponding to what is plotted) this graph and I need to get it right.
Could anyone help? Thank you Phil,
Solution 1:[1]
TL;DR
Use position_fill() in place of position_stack(), remove the limits= argument from scale_y_continuous(), and use reverse=TRUE to reverse the ordering of stacking in the bars so that it matches the order you see in the legend.
The Details
First of all, the approach OP uses to change the order of the bars and stacking is correct. Re-leveling a factor or defining the levels prior to plotting is really the best way to ensure the order when plotted will be as you wish them to be.
It's not altogether clear from the OP's question what is meant by "in danger of mis-labelling the graphic", but there is one graphical difficulty with the plot that can cause some confusion, with is the the order of stacking does not match the order we see in the legend. In other words, in the legend it is ordered as we would want it to appear: "Obese", "Overweight", "Healthy" (left-to-right). However, in the bar geom in the plot, the order is reversed: "Healthy", "Overweight", and "Obese" (left-to-right). I'll fix that.
Here's the plot code again. Note that I've changed a few things (syntax), but also I'm using position_fill() in place of position_stack(). Both are similar, but position_fill() will stretch the bars to be the same length based on proportion. This is what we want here, so I'm using that both in geom_bar() and geom_text() places. I'm also using geom_col() in place of geom_bar(), since it's more succint: geom_col() is essentially the wrapper for geom_bar(stat="identity"). One final note is that we need to remove the limits= argument from scale_y_continuous() or we won't see anything.
p=ggplot(data=data, aes(x=gender, y=Value, fill=BMI)) +
geom_col(position=position_fill()) +
scale_y_continuous(labels = scales::percent) +
geom_text(aes(label = Value), size=2, angle=0, position=position_fill(vjust=0.1)) +
coord_flip() +
xlab("") + ylab("") +
theme(legend.title = element_blank(), legend.position="bottom")
p
To switch the order we see in the plot, you can use reverse=TRUE within position_fill(). We just need to be sure to do this in both places (or the text will not align with the bars).
p=ggplot(data=data, aes(x=gender, y=Value, fill=BMI)) +
geom_col(position=position_fill(reverse=TRUE)) +
scale_y_continuous(labels = scales::percent) +
geom_text(aes(label = Value), size=2, angle=0, position=position_fill(vjust=0.1, reverse=TRUE)) +
coord_flip() +
xlab("") + ylab("") +
theme(legend.title = element_blank(), legend.position="bottom")
p
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | chemdork123 |


