5

I'm facing a behaviour of ggplot2, ordering and stacked barplot that I cannot understand.
I've read some question about it (here,here and so on), but unluckily I cannot find a solution that suits to me. Maybe the answer is easy and I cannot see it. Hope it's not a dupe.

My main goal is to have each stack ordered independently, based on the ordering column (called here ordering).

Here I have some data:

library(dplyr)
library(ggplot2)
dats <- data.frame(id = c(1,1,1,2,2,3,3,3,3),
                   value = c(9,6,4,5,6,4,3,4,5),
                   ordering = c(1,2,3,2,3,1,3,2,4),
                   filling = c('a','b','c','b','a','a','c','d','b')) %>% arrange(id,ordering)

So there is an ID, a value, a value to use to order, and a filling, the data are as they should be ordered in the plot, as looking the ordering column.

I tried to plot it: the idea is to plot as a stacked barchart with x axis the id, the value value, filled by filling, but the filling has as order the value of ordering, in an ascending ordering, i.e. biggest value of ordering at the bottom for each column. The ordering of the filling is somewhat equal as the dataset, i.e. each column has an independent order.

As you can imagine those are fake data, so the number of id can vary.

 id value ordering filling
1  1     9        1       a
2  1     6        2       b
3  1     4        3       c
4  2     5        2       b
5  2     6        3       a
6  3     4        1       a
7  3     4        2       d
8  3     3        3       c
9  3     5        4       b

When I plot them, there is something I do not understand:

library(dplyr) 
dats$filling <- reorder(dats$filling, -dats$ordering)

ggplot(dats,aes(x = id,
                y = value,
                fill = filling)) + 
  geom_bar(stat = "identity",position = "stack") +
  guides(fill=guide_legend("ordering")) 

enter image description here

The second and the third id are not properly ordered, I should have the order of the original dataset.

s__
  • 9,270
  • 3
  • 27
  • 45
  • Yes, the same in the data as ordered: look at the last table, you can find for the 1 column abc, the second ba, the third adcb, as stated in the "ordering" column, that orders each id from the smallest to the bigger value of itself. – s__ Dec 03 '18 at 15:26
  • Also, your question in some ways is better than the dupe, because it provides reproducible data. I would recommend editing your question to make the goal clear. Edit out the distracting bit of doing the `reorder` inline, and state the goal clearly, something like *"I want each stack ordered independently, based on the `ordering` column*" and maybe someone will come along with a clearer answer. If you make those edits (and get or post an answer), I'd be tempted to close the other Q as a dupe of this one because this one will be a much better question. – Gregor Thomas Dec 03 '18 at 15:34
  • I got another solution, using only `ggplot2` and a single `geom_bar`. Will be happy to share here if the question gets reopened. – Julius Vainora Dec 03 '18 at 16:14
  • @JuliusVainora reopened. And as discussed above marked the other question as a dupe of this one as this one has reproducible data and already one better answer. – Gregor Thomas Dec 03 '18 at 16:23

3 Answers3

9

If you use separate geom_bars, you can make the orders different.

dats %>% 
  ggplot(aes(x = id, y = value, fill = reorder(filling,-ordering))) + 
    geom_bar(stat = "identity", position = "stack", data = dats %>% filter(id == 1)) +
    geom_bar(stat = "identity", position = "stack", data = dats %>% filter(id == 2)) +
    geom_bar(stat = "identity", position = "stack", data = dats %>% filter(id == 3)) +
    guides(fill=guide_legend("ordering")) 

enter image description here

More generally:

bars <- map(unique(dats$id)
            , ~geom_bar(stat = "identity", position = "stack"
                       , data = dats %>% filter(id == .x)))

dats %>% 
  ggplot(aes(x = id, y = value, fill = reorder(filling,-ordering))) + 
    bars +
    guides(fill=guide_legend("ordering"))
IceCreamToucan
  • 28,083
  • 2
  • 22
  • 38
  • That's was one of the option, but this is not a real dataset, and I'd like to have a solution that fits for every dataset. Sorry if I was not clear. – s__ Dec 03 '18 at 15:55
  • 3
    I see. You can generate all the separate bars at once using `map`. I edited my answer to show how. – IceCreamToucan Dec 03 '18 at 15:59
5

The problem is that, in your case, different bars should use the same values (levels) of filling in a different order. This conflicts with the way ggplot works: taking the factor levels (which already have a certain order) and applying them in the same way for each bar.

A workaround then is... To create many factor levels.

ggplot(dats, aes(x = id, y = value, fill = interaction(-ordering, id))) + 
  geom_bar(stat = "identity", position = "stack")

enter image description here

This one now is too "generous" by being too detailed. However, what we can do now is to deal with the legend and the different colors:

dats <- arrange(dats, id, -ordering)
aux <- with(dats, match(sort(unique(filling)), filling))
ggplot(dats, aes(x = id, y = value, fill = interaction(-ordering, id))) + 
  geom_bar(stat = "identity", position = "stack") +
  scale_fill_manual("Ordering", values = scales::hue_pal()(4)[dats$filling],
                    labels = with(dats, filling[aux]), 
                    breaks = with(dats, interaction(-ordering, id)[aux]))

enter image description here

Here I first rearrange the rows of dats as to avoid doing that later. Then aux is an auxiliary vector

aux
# [1] 3 2 1 8

giving arbitrary positions (one for each) where levels a, b, c, and d (in this order) appear in dats, which again is useful later. Then I simply set corresponding scale values, labels, and breaks... Lastly, I use scales::hue_pal to recover the original color palette.

Julius Vainora
  • 47,421
  • 9
  • 90
  • 102
-2

The problem here is that the element filling = d only appears in the third group with a low value. One solution, could be to fill non-present values with 0:

library(dplyr)
#> 
#> Attachement du package : 'dplyr'
#> The following objects are masked from 'package:stats':
#> 
#>     filter, lag
#> The following objects are masked from 'package:base':
#> 
#>     intersect, setdiff, setequal, union
library(ggplot2)
dats <- data.frame(id = c(1,1,1,1,2,2,2,2,3,3,3,3),
                   value = c(9,6,4,0,5,6,0,0,4,3,4,5),
                   ordering = c(1,2,3,5,2,3,5,5,1,3,2,4),
                   filling = c('a','b','c','d','b','a','c','d','a','c','d','b')) %>% arrange(id,ordering)


ggplot(dats,aes(x = id,
                y = value,
                fill = reorder(filling,-ordering))) + 
  geom_bar(stat = "identity",position = "stack") +
  guides(fill=guide_legend("ordering")) 

Created on 2018-12-03 by the reprex package (v0.2.1)

denrou
  • 630
  • 3
  • 12
  • 1
    Thanks! Sorry, but the result should be first column abc, second, ba, third, adcb (from the bottom). – s__ Dec 03 '18 at 15:22