2

I am attempting to create a diverging stacked bar like here, and am experiencing a similar issue to this SO question. My approach is slightly different though as I am managing it all via a single dataset, rather than two and my colours are independent of my data.

Reprex as follows:

library(tidyverse)
library(RColorBrewer)
x <- tribble(
  ~response, ~count,
  0,         -27,
  1,          -9,
  2,         -41,
  3,         -43,
  4,         -58,
  5,        -120,
  5,         120,
  6,         233,
  7,         379,
  8,         388,
  9,         145,
  10,         61
) %>% 
  mutate(response = factor(response))

ggplot(x, aes(x = 1, y = count, fill = response)) +
  geom_col() +
  scale_fill_brewer(palette = "RdBu") +
  coord_flip()

This gives me an image like this:enter image description here

The issue is to do with the ordering of the stacked data on the right hand side of the zero where they stacking appears to be in descending order. Any thoughts on how to fix this would be greatly appreciated (expected ordering would be 0-10, not 0-5,10-5)

Dan
  • 2,625
  • 5
  • 27
  • 42

3 Answers3

3

A tough one! I played with ordering and it seems that geom_bar and geom_col don't like it when you combine positive and negative values in the common same order. So I divided your data inside the dataframe for positive and negative values, generated colors for every response value and used two geoms for positive and negative values separately:

library(tidyverse)
library(RColorBrewer)
x <- tribble(
  ~response, ~count,
  0,         -27,
  1,          -9,
  2,         -41,
  3,         -43,
  4,         -58,
  5,        -120,
  5,         120,
  6,         233,
  7,         379,
  8,         388,
  9,         145,
  10,         61
) %>% 
  # Get absolute values and add dummy to distuingish positive and negative values
  mutate(subzero = count < 0,
         count = abs(count))

# Generate variable with colors from ColorBrewer for every response level (ugly but works)
colors <- brewer.pal(length(unique(x$response)),"RdBu")
x$colors <- NA
for (i in 1:nrow(x)){
  x$colors[i] <- colors[x$response[i]+1]
}


ggplot() +
  geom_bar(data = x[x$subzero==T,], aes(x = "", y = -count, fill = reorder(colors, response)), position="stack", stat="identity") +
  geom_bar(data = x[x$subzero==F,], aes(x = "", y = count, fill = reorder(colors, -response)), position="stack", stat="identity") +
  geom_hline(yintercept = 0, color =c("black")) +
  scale_fill_identity("Response", labels = unique(x$response), breaks=unique(x$colors), guide="legend") +
  coord_flip() +
  labs(y="",x="") +
  theme(legend.position = "bottom", legend.direction = "horizontal") +
  scale_y_continuous(breaks=seq(-1400,1400,200), limits=c(-1400,1400))

UPD: made Y-scale balanced so it look more clear enter image description here

Alex Knorre
  • 620
  • 4
  • 15
1

Although not intuitive (for me), use:

ggplot(x, aes(x = 1, y = order(count), fill = response)) +
  geom_col() +
  scale_fill_brewer(palette = "RdBu",direction=1) +
  coord_flip()

It takes into account the ordering based on response (rather than order(response))

timfaber
  • 2,060
  • 1
  • 15
  • 17
  • It's a tricky one, because ultimately this should be similar to a likert scale ordering 0:10, left to right. The reason there are 2 records for 5 is because I want half to be either side of the 'midpoint' of the chart. This ordering does't quite fit the purpose. – Dan Jun 06 '17 at 13:05
  • Ah yes it is, noticed afterwards the order is reversed (relative to the legend). Also, the values represent the ordered values (not the actual counts) making this a suboptimal answer :) Alternatively you can order the variables according to count, create a manual scale including `RColorBrewer::brewer.pal(11,"RdBu")` (repeating 5) and attach it your df. This can be used as input to `scale_fill_manual` – timfaber Jun 06 '17 at 14:44
0

You can use position_stack(reverse=TRUE):

ggplot(x, aes(x = 1, y = count, fill = response)) +
  geom_col(position = position_stack(reverse=TRUE)) +
  scale_fill_brewer(palette = "RdBu") +
  coord_flip()
Marcelo
  • 4,234
  • 1
  • 18
  • 18