145
votes

I am making a dodged bar chart using ggplot with discrete x scale, the x axis are now arranged in alphabetical order, but I need to rearrange it so that it is ordered by the value of the y-axis (i.e., the tallest bar will be positioned on the left).

I tried order or sort, but result in sort the x-axis, but not the bars respectively.

What have I done wrong?

6

6 Answers

109
votes

Try manually setting the levels of the factor on the x-axis. For example:

library(ggplot2)
# Automatic levels
ggplot(mtcars, aes(factor(cyl))) + geom_bar()    

ggplot of the cars dataset with factor levels automatically determined

# Manual levels
cyl_table <- table(mtcars$cyl)
cyl_levels <- names(cyl_table)[order(cyl_table)]
mtcars$cyl2 <- factor(mtcars$cyl, levels = cyl_levels)
# Just to be clear, the above line is no different than:
# mtcars$cyl2 <- factor(mtcars$cyl, levels = c("6","4","8"))
# You can manually set the levels in whatever order you please. 
ggplot(mtcars, aes(cyl2)) + geom_bar()

ggplot of the cars dataset with factor levels reordered manually

As James pointed out in his answer, reorder is the idiomatic way of reordering factor levels.

mtcars$cyl3 <- with(mtcars, reorder(cyl, cyl, function(x) -length(x)))
ggplot(mtcars, aes(cyl3)) + geom_bar()

ggplot of the cars dataset with factor levels reordered using the reorder function

203
votes

The best way for me was using vector with categories in order I need as limits parameter to scale_x_discrete. I think it is pretty simple and straightforward solution.

ggplot(mtcars, aes(factor(cyl))) + 
  geom_bar() + 
  scale_x_discrete(limits=c(8,4,6))

enter image description here

41
votes

You can use reorder:

qplot(reorder(factor(cyl),factor(cyl),length),data=mtcars,geom="bar")

Edit:

To have the tallest bar at the left, you have to use a bit of a kludge:

qplot(reorder(factor(cyl),factor(cyl),function(x) length(x)*-1),
   data=mtcars,geom="bar")

I would expect this to also have negative heights, but it doesn't, so it works!

34
votes

Hadley has been developing a package called forcats. This package makes the task so much easier. You can exploit fct_infreq() when you want to change the order of x-axis by the frequency of a factor. In the case of the mtcars example in this post, you want to reorder levels of cyl by the frequency of each level. The level which appears most frequently stays on the left side. All you need is the fct_infreq().

library(ggplot2)
library(forcats)

ggplot(mtcars, aes(fct_infreq(factor(cyl)))) +
geom_bar() +
labs(x = "cyl")

If you wanna go the other way around, you can use fct_rev() along with fct_infreq().

ggplot(mtcars, aes(fct_rev(fct_infreq(factor(cyl))))) +
geom_bar() +
labs(x = "cyl") 

enter image description here

2
votes

I realize this is old, but maybe this function I created is useful to someone out there:

order_axis<-function(data, axis, column)
{
  # for interactivity with ggplot2
  arguments <- as.list(match.call())
  col <- eval(arguments$column, data)
  ax <- eval(arguments$axis, data)

  # evaluated factors
  a<-reorder(with(data, ax), 
             with(data, col))

  #new_data
  df<-cbind.data.frame(data)
  # define new var
  within(df, 
         do.call("<-",list(paste0(as.character(arguments$axis),"_o"), a)))
}

Now, with this function you can interactively plot with ggplot2, like this:

ggplot(order_axis(df, AXIS_X, COLUMN_Y), 
       aes(x = AXIS_X_o, y = COLUMN_Y)) +
        geom_bar(stat = "identity")

As can be seen, the order_axis function creates another dataframe with a new column named the same but with a _oat the end. This new column has levels in ascending order, so ggplot2 automatically plots in that order.

This is somewhat limited (only works for character or factor and numeric combinations of columns and in ascending order) but I still find it very useful for plotting on the go.

0
votes

@Yuriy Petrovskiy's answer is great if you know the levels you want to plot beforehand. If you don't (e.g. because you don't want to plot levels not present in the data), consider using a limit function instead to specify the order:

library(ggplot2)

my_order <- as.character(c(8,3,4,5,6))    # the `as.character` calls are only
ggplot(mtcars, aes(as.character(cyl))) +  # necessary for the OP's example
  geom_bar() +
  scale_x_discrete(limits = function(x) my_order[my_order %in% x])

From the documentation of scale_x_discrete:

limits
      One of:
      - NULL to use the default scale values
      - A character vector that defines possible values of the scale and their order
      - A function that accepts the existing (automatic) values and returns new ones

Otherwise your graph would end up like this (might be preferable):

ggplot(mtcars, aes(as.character(cyl))) +
  geom_bar() +
  scale_x_discrete(limits = my_order)