Search code examples
rggplot2rlangquasiquotes

Changing legend order in ggplot within a function


I want to plot a data frame within a function. The legend should be ordered in a specific way. To keep things simple in my example I just reverse the order. I actually want to select a specific row and push it to the last position of the legend.

By the way I am creating a new R package, if this is in any way relevant.

Plotting outside of a function

attach(iris)
library(ggplot2)


# This is a normal plot
p <- ggplot(iris, aes(x = Sepal.Width, y = Sepal.Length, fill = Species) ) +
  geom_bar( stat = "identity")
p

# This is a plot with reversed legend
iris$Species <- factor(iris$Species, levels = rev(levels(iris$Species)))
p <- ggplot(iris, aes(x = Sepal.Width, y = Sepal.Length, fill = Species) ) +
  geom_bar( stat = "identity")
p

Plotting inside a function

The most simple approach was just to use variables, which obviously doesn't work

f1 <- function(myvariable) {
  iris$myvariable <- factor(iris$myvariable, levels = rev(levels(iris$myvariable)))
  p <- ggplot(iris, aes(x = Sepal.Width, y = Sepal.Length, fill = Species) ) +
    geom_bar( stat = "identity")
  p
}
f1("Species")

 #> Error in `$<-.data.frame`(`*tmp*`, myvariable, value = integer(0)) : 
  replacement has 0 rows, data has 150 

I tried to use quasiquotation, but this approach only let me plot the data frame. I cannot reverse the order yet.

library(rlang)
# Only plotting works
f2 <- function(myvariable) {
  v1 <- ensym(myvariable)
  p <- ggplot(iris, aes(x = Sepal.Width, y = Sepal.Length, fill = eval(expr(`$`(iris, !!v1))))) +
    geom_bar( stat = "identity")
  p
}
f2("Species")

# This crashes
f3 <- function(myvariable) {
  v1 <- ensym(myvariable)
  expr(`$`(iris, !!v1)) <- factor(expr(`$`(iris, !!v1)), levels = rev(levels(expr(`$`(iris, !!v1)))))
  p <- ggplot(iris, aes(x = Sepal.Width, y = Sepal.Length, fill = eval(expr(`$`(iris, !!v1))))) +
    geom_bar( stat = "identity")
  p
}
f3("Species")

#> Error in `*tmp*`$!!v1 : invalid subscript type 'language'

So the main problem is, that I cannot assign something using quasiquotation.


Solution

  • A couple of things:

    • You can use [[ instead of $ to access data frame columns programmatically.
    • You can use ggplot's aes_string to minimize notes during R CMD check (since you mentioned you're doing a package).
    • You can "send" a factor level to the end with fct_relevel from package forcats.

    Which translates to:

    f <- function(df, var) {
      lev <- levels(df[[var]])
      df[[var]] <- forcats::fct_relevel(df[[var]], lev[1L], after = length(lev) - 1L)
    
      ggplot(df, aes_string(x = "Sepal.Width", y = "Sepal.Length", fill = var)) +
        geom_bar(stat = "identity")
    }
    
    f(iris, "Species")