61

Using this dummy data.frame

ts <- data.frame(x=1:3, y=c("blue", "white", "white"), z=c("one", "one", "two"))

I try and plot with category "blue" on top.

ggplot(ts, aes(z, x, fill=factor(y, levels=c("blue","white" )))) + geom_bar(stat = "identity")

enter image description here

gives me "white" on top. and

ggplot(ts, aes(z, x, fill=factor(y, levels=c("white", "blue")))) + geom_bar(stat = "identity")

enter image description here

reverses the colors, but still gives me "white" on top. How can I get "blue" on top?

Tom
  • 4,860
  • 7
  • 43
  • 55

6 Answers6

74

For what it is worth, in ggplot2 version 2.2.1 the order of the stack is no longer determined by the row order in the data.frame. Instead, it matches the order of the legend as determined by the order of levels in the factor.

d <- data.frame(
  y=c(0.1, 0.2, 0.7),
  cat = factor(c('No', 'Yes', 'NA'), levels = c('NA', 'Yes', 'No')))

# Original order
p1 <- ggplot(d, aes(x=1, y=y, fill=cat)) +
  geom_bar(stat='identity')

# Change order of rows
p2 <- ggplot(d[c(2, 3, 1), ], aes(x=1, y=y, fill=cat)) +
  geom_bar(stat='identity')

# Change order of levels
d$cat2 <- relevel(d$cat, 'Yes')
p3 <- ggplot(d, aes(x=1, y=y, fill=cat2)) +
  geom_bar(stat='identity') 

grid.arrange(p1, p2, p3, ncol=3)

It results in the below plot: enter image description here

Prradep
  • 5,506
  • 5
  • 43
  • 84
Inhabitant
  • 859
  • 6
  • 6
  • 3
    Thank you for telling us it is due to the new ggplot2 version. Somehow my plots were suddenly all wrong and I spent a lot of time locating the mistake with myself. Only now I know the problem is with 'ggplot2' instead and I can safely change the order manually – Vincent Nov 16 '17 at 12:03
  • 1
    Oh my gosh, I spent like an hour trying to figure out why the behavior of my code had changed... – Ecksters May 04 '18 at 09:57
  • 1
    I was struggling with the ordering of `NA` values and it needs to be added as a proper level. An easy way to do just that is the convenience function `addNA()`. – hannes101 Aug 31 '18 at 12:06
  • Ecksters, watch your words – WJH Feb 15 '22 at 21:03
70

I've struggled with the same issue before. It appears that ggplot stacks the bars based on their appearance in the dataframe. So the solution to your problem is to sort your data by the fill factor in the reverse order you want it to appear in the legend: bottom item on top of the dataframe, and top item on bottom:

ggplot(ts[order(ts$y, decreasing = T),],
       aes(z, x, fill=factor(y, levels=c("blue","white" )))) + 
  geom_bar(stat = "identity")

enter image description here

Edit: More illustration

Using sample data, I created three plots with different orderings of the dataframe, I thought that more fill-variables would make things a bit clearer.

set.seed(123)
library(gridExtra)
df <- data.frame(x=rep(c(1,2),each=5),
                 fill_var=rep(LETTERS[1:5], 2),
                 y=1)
#original order   
p1 <- ggplot(df, aes(x=x,y=y,fill=fill_var))+
  geom_bar(stat="identity") + labs(title="Original dataframe")


#random order
p2 <- ggplot(df[sample(1:10),],aes(x=x,y=y,fill=fill_var))+
  geom_bar(stat="identity") + labs(title="Random order")
#legend checks out, sequence wird

#reverse order
p3 <- ggplot(df[order(df$fill_var,decreasing=T),],
             aes(x=x,y=y,fill=fill_var))+
  geom_bar(stat="identity") + labs(title="Reverse sort by fill")

plots <- list(p1,p2,p3)

do.call(grid.arrange,plots)

enter image description here

Community
  • 1
  • 1
Heroka
  • 12,889
  • 1
  • 28
  • 38
  • Thank you for the thorough example! I thought the order was determined alphabetically, so this is very helpful. – Tom Sep 02 '15 at 08:22
  • 1
    You're welcome! If any of the provided solutions work to your satisfaction, can you accept one? Keeps others from answering the question. – Heroka Sep 02 '15 at 09:09
  • 1
    Nice answer! Perhaps others will find this SO question useful: http://stackoverflow.com/questions/30739602/ggplot-reorder-stacked-bar-plot-based-on-values-in-data-frame – lawyeR Sep 02 '15 at 10:09
  • 9
    Unfortunately this doesn't seem to work with dplyr's `arrange()` function. Switching the order of the stack category has no effect. – jzadra Mar 30 '17 at 21:24
  • 4
    All of the plots in your example have the same order for me (R 3.4.3) – Parsa Feb 28 '18 at 18:26
  • 1
    It's quite an old answer. Maybe have a look at Inhabitant's solution below. – Heroka Mar 04 '18 at 13:37
13

Use the group aethetic in the ggplot() call. This ensures that all layers are stacked in the same way.

series <- data.frame(
  time = c(rep(1, 4),rep(2, 4), rep(3, 4), rep(4, 4)),
  type = rep(c('a', 'b', 'c', 'd'), 4),
  value = rpois(16, 10)
)

ggplot(series, aes(time, value, group = type)) +
  geom_col(aes(fill = type)) +
  geom_text(aes(label = type), position = "stack")
Paul Roub
  • 36,322
  • 27
  • 84
  • 93
Anne
  • 145
  • 1
  • 4
13

Messing with your data in order to make a graph look nice seems like a bad idea. Here's an alternative that works for me when using position_fill():

ggplot(data, aes(x, fill = fill)) + geom_bar(position = position_fill(reverse = TRUE)) 

The reverse = TRUE argument flips the order of the stacked bars. This works in position_stack also.

dash2
  • 2,024
  • 6
  • 15
6

I have the exactly same problem today. You can get blue on top by using order=-as.numeric():

ggplot(ts, 
aes(z, x, fill=factor(y, levels=c("blue","white")), order=-as.numeric(y))) + 
geom_bar(stat = "identity")
zakrapovic
  • 413
  • 3
  • 17
Fukushi
  • 87
  • 3
1

I had a similar issue and got around by changing the level of the factor. thought I'd share the code:

library(reshape2)
library(ggplot2)

group <- c(
  "1",
  "2-4",
  "5-9",
  "10-14",
  "15-19",
  "20-24",
  "25-29",
  "30-34",
  "35-39",
  "40-44",
  "45-49"
)

xx <- factor(group, levels(factor(group))[c(1, 4, 11, 2, 3, 5:10)])

method.1 <- c(36, 14, 8, 8, 18, 1, 46, 30, 62, 34, 34)
method.2 <- c(21, 37, 45, 42, 68, 41, 16, 81, 51, 62, 14)
method.3 <- c(37, 46, 18, 9, 16, 79, 46, 45, 70, 42, 28)  
elisa.neg <- c(12, 17, 18, 6, 19, 14, 13, 13, 7, 4, 1)  
elisa.eq <- c(3, 6, 3, 14, 1, 4, 11, 13, 5, 3, 2)

test  <- data.frame(person = xx, 
                    "Mixture Model" = method.1,
                    "Censoring" = method.3,
                    "ELISA neg" = elisa.neg,
                    "ELISA eqiv" = elisa.eq) 

melted <- melt(test, "person")
melted$cat <- ifelse(melted$variable == "Mixture.Model", "1",
                     ifelse(melted$variable == "Censoring", "2", "3"))

melted$variable = factor(melted$variable, levels = levels(melted$variable)[c(1, 2, 4,3 )]) ## This did the trick of changing the order

ggplot(melted, aes(x = cat, y = value, fill = variable)) + 
  geom_bar(stat = 'identity') + facet_wrap(~ person) +
  theme(axis.ticks.x=element_blank(),
        axis.text.x=element_blank()) + 
  labs(title = "My Title",
       y = "Per cent", x = "Age Group", fill = "")

(Sorry, this is my data, I didn't reproduce using the data from the original post, hope it's ok!)

Kevin Wang
  • 11
  • 2