stu
stu

Reputation: 183

Stacked bar graphs in plotly: how to control the order of bars in each stack

I'm trying to order a stacked bar chart in plotly, but it is not respecting the order I pass it in the data frame.

It is best shown using some mock data:

library(dplyr)
library(plotly)
cars <- sapply(strsplit(rownames(mtcars), split = " "), "[", i = 1)
dat <- mtcars
dat <- cbind(dat, cars, stringsAsFactors = FALSE)
dat <- dat %>% 
         mutate(carb = factor(carb)) %>%
         distinct(cars, carb) %>% 
         select(cars, carb, mpg) %>% 
         arrange(carb, desc(mpg))
plot_ly(dat) %>% 
  add_trace(data = dat, type = "bar", x = carb, y = mpg, color = cars) %>%  
  layout(barmode = "stack") 

The resulting plot doesn't respect the ordering, I want the cars with the largest mpg stacked at the bottom of each cylinder group. Any ideas?

enter image description here

Upvotes: 5

Views: 11019

Answers (2)

dww
dww

Reputation: 31452

As already pointed out here, the issue is caused by having duplicate values in the column used for color grouping (in this example, cars). As indicated already, the ordering of the bars can be remedied by grouping your colors by a column of unique names. However, doing so will have a couple of undesired side-effects:

  1. different model cars from the same manufacturer would be shown with different colors (not what you are after - you want to color by manufacturer)
  2. the legend will have more entries in it than you want i.e. one per model of car rather than one per manufacturer.

We can hack our way around this by a) creating the legend from a dummy trace that never gets displayed (add_trace(type = "bar", x = 0, y = 0... in the code below), and b) setting the colors for each category manually using the colors= argument. I use a rainbow pallette below to show the principle. You may like to select sme more attractive colours yourself.

dat$unique.car <- make.unique(as.character(dat$cars))
dat2 <- data.frame(cars=levels(as.factor(dat$cars)),color=rainbow(nlevels(as.factor(dat$cars))))
dat2[] <- lapply(dat2, as.character)
dat$color <- dat2$color[match(dat$cars,dat2$cars)]

plot_ly() %>% 
  add_trace(data=dat2, type = "bar", x = 0, y = 0, color = cars, colors=color, showlegend=T) %>%  
  add_trace(data=dat, type = "bar", x = carb, y = mpg, color = unique.car, colors=color, showlegend=F, marker=list(line=list(color="black", width=1))) %>%  
  layout(barmode = "stack", xaxis = list(range=c(0.4,8.5))) 

enter image description here

Upvotes: 3

Jota
Jota

Reputation: 17611

One way to address this is to give unique names to all models of car and use that in plotly, but it's going to make the legend messier and impact the color mapping. Here are a few options:

dat$carsID <- make.unique(as.character(dat$cars))
# dat$carsID <- apply(dat, 1, paste0, collapse = " ") # alternative

plot_ly(dat) %>% 
  add_trace(data = dat, type = "bar", x = carb, y = mpg, color = carsID) %>%  
  layout(barmode = "stack") 

plot_ly(dat) %>% 
  add_trace(data = dat, type = "bar", x = carb, y = mpg, color = carsID,
            colors = rainbow(length(unique(carsID)))) %>%  
  layout(barmode = "stack")

I'll look more tomorrow to see if I can improve the legend and color mapping.

Upvotes: 2

Related Questions