(reposted - previously flagged as duplicate but additional MRE illustrates issue more completely)
I'm trying to order one of the factors in a stacked percentage bar plot by the percentage value of that factor but can't manage to work out how to do it.
e.g. in the following I'd like the categories in x
to be ordered by the percentage value of factor q
.
library(tibble)
library(ggplot2)
df <- tibble(x = rep(LETTERS[1:5], 2), y = c(10,20,5,60,30,90,80,95,40,70), z = c(rep("p", 5), rep("q", 5)))
ggplot(df, aes(x = x, y = y, fill = z)) +
geom_bar(stat = "identity", position = "fill") +
scale_y_continuous(labels=scales::percent)
The solution accepted at question Reorder bars in geom_bar ggplot2 uses reorder()
to change the order of x bars, but does not work for my case:
ggplot(df, aes(x = reorder(x, -y), y = y, fill = z)) +
geom_bar(stat = "identity", position = "fill") +
scale_y_continuous(labels=scales::percent)
Produces exactly the same plot as above.
I think the reason is that the data are in tidy format so there are multiple y values for x, but I can't figure out how to map only a subset of y onto x.
How about first defining the levels:
df %>%
filter(z == "q") %>%
mutate(x = factor(x, levels = x[order(y)])) %>%
pull(x) %>%
levels -> x_levels
and then using those levels as input to ggplot:
df %>%
mutate(x = factor(x,
levels = x_levels)) %>%
ggplot(aes(x = x,
y = y,
fill = z)) +
geom_bar(stat = "identity",
position = "fill") +
scale_y_continuous(labels = scales::percent)
another way is to use two accessory functions:
available on github dgrtwo/drlib
reorder_within <- function(x, by, within, fun = mean, sep = "___", ...) {
new_x <- paste(x, within, sep = sep)
stats::reorder(new_x, by, FUN = fun)
}
scale_x_reordered <- function(..., sep = "___") {
reg <- paste0(sep, ".+$")
ggplot2::scale_x_discrete(labels = function(x) gsub(reg, "", x), ...)
}
ggplot(df, aes(x = reorder_within(x, y, 1, fun = max),
y = y,
fill = z)) +
geom_bar(stat = "identity",
position = "fill") +
scale_y_continuous(labels = scales::percent)+
scale_x_reordered()
I used 1 as within group, but these functions allow for a more complicated sort within each facet.
or:
ggplot(df, aes(x = reorder_within(x, y, 1, fun = min),
y = y,
fill = z)) +
geom_bar(stat = "identity",
position = "fill") +
scale_y_continuous(labels = scales::percent)+
scale_x_reordered()