Search code examples
rggplot2forcatsgeom-colposition-dodge

Order factor according to value of dodged column in geom_col plot


The following code

library(tidyverse)

adf <- data.frame(a=c(5,6,8), b=c(3,2,1), z=factor(c('a','b','c'),levels=c('b','c','a')))

ldf <- adf |> pivot_longer(cols = c(a,b))

p <- ggplot()
p <- p+geom_col(data=ldf,
                aes(x=z,
                    y=value,
                    fill=name),
                position = position_dodge())
print(p)

generates the plot enter image description here

I'd like two plots. For one plot, I would like to arrange the factor on the x-axis in order of the (decreasing say) value of the a column. I would like another plot where I order by (decreasing) b values. I could do this by changing the dataframe and re-leveling the factor according to a and plot, and then b and plot.

But, and this is the question part, is there a way to do this at the ggplot level and wrap the z variable in the aes with some form of fct_reorder (or other) from the forcats package to do this? The data given to ggplot is in long format so it is not possible to refer to the original columns 'a' and 'b' as in fct_reorder(z, a). i.e. something like aes(x=fct_reorder(z, ???), y=value, ...)

Is re-ordering z explicitly in the original dataframe the only option?


Solution

  • We could also use forcats::fct_inorder after ordering with the sorting variable. The "trick" here is that FALSE comes before TRUE in sorting, so the code below will put "b" at the top, sort by descending value, and then do the same for "a". fct_inorder will order based on the first appearance of each level, so in this case the "b" ordering will be shown.

    ggplot(ldf |> 
             arrange(name != "b", -value) |>
             mutate(z = factor(z) |> fct_inorder()),
           aes(z, value, fill = name)) +
      geom_col(position = position_dodge()) 
    

    enter image description here