user2085797
user2085797

Reputation: 69

ggplot2: pass factor variable to facet_wrap

i am trying to replace line-by-line scripting to produce ggplot2 graphics with functions - but have come unstuck with facet_wrap

To generalise the problem, I have created this toy dataset - which plots a value over time for two subjects ("IDs)

day <- c(0,  3,  5,  7,  9, 14,  0,  3,  5,  7,  9, 14)
value <- c(0.0, 3.6, 6.7, 7.6, 8.7, 0.0, 0.0, 1.0, 1.2, 8.3, 1.2, 0.0)
ID <- as.factor(c(1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2))
df <- data.frame(ID, day, value)

With the following scripting approach, i can produce line plots of the value for the day - facetted on the two subjects

ggplot(df, aes(day, value)) + geom_line() + facet_wrap(~ID) 

I then try to do this by creating and then calling a function using this code:

fun <- function(data, x, y, fac){
  p <- ggplot(df, aes(x, y)) + geom_line() 
  p + facet_wrap(~fac)
}
fun(df, day, value, ID)

But I get the following error:

 Error: At least one layer must contain all faceting variables: `fac`.
* Plot is missing `fac`
* Layer 1 is missing `fac`

Interesting, if I hardcode "ID" into the function, everything works - and I don't even have to pass a faceting variables

fun <- function(data, x, y){
  p <- ggplot(df, aes(x, y)) + geom_line() 
  p + facet_wrap(~ID)
}
fun(df, day, value) 

But this sort of defeats the generalising benefit of creating a function

I would be grateful if someone could tell me what i am doing wrong and what might be the work-around

Thanks

PS. I have done a check for an answer on StackExchange, and although there are several somewhat similar questions about facet_wrap, either I could not get the suggested solutions to work, or else did not understand them

Upvotes: 0

Views: 847

Answers (1)

teunbrand
teunbrand

Reputation: 37933

Because non-standard evaluation changes the scoping rules in subtle ways, you cannot simply feed variables into aes() or vars() calls within functions (the ~ formula notation wraps vars()). A more detailed treatment of this problem you can find here: https://ggplot2.tidyverse.org/articles/ggplot2-in-packages.html

One of the solutions is to wrap you variable names into {{...}}, which signals that the non standard evaluation should occur with whatever expression is given as the argument, instead of the evaluated value of the argument expression.

library(ggplot2)

day <- c(0,  3,  5,  7,  9, 14,  0,  3,  5,  7,  9, 14)
value <- c(0.0, 3.6, 6.7, 7.6, 8.7, 0.0, 0.0, 1.0, 1.2, 8.3, 1.2, 0.0)
ID <- as.factor(c(1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2))
df <- data.frame(ID, day, value)

fun <- function(data, x, y, fac){
  p <- ggplot(df, aes({{x}}, {{y}})) + geom_line() 
  p + facet_wrap(vars({{fac}}))
}

fun(df, day, value, ID)

Created on 2020-05-27 by the reprex package (v0.3.0)

Also, note that you should also double bracket the x and y variables. The only reason that it worked before was because the day and value were variables in the global environment (and of the length nrow(df)). The following returns an error if run in a clean environment:

library(ggplot2)

df <- data.frame(
  ID = as.factor(c(1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2)), 
  day = c(0,  3,  5,  7,  9, 14,  0,  3,  5,  7,  9, 14), 
  value = c(0.0, 3.6, 6.7, 7.6, 8.7, 0.0, 0.0, 1.0, 1.2, 8.3, 1.2, 0.0)
)

fun <- function(data, x, y, fac){
  p <- ggplot(df, aes(x, y)) + geom_line() 
  p + facet_wrap(vars({{fac}}))
}

fun(df, day, value, ID)
#> Error in FUN(X[[i]], ...): object 'day' not found

Created on 2020-05-27 by the reprex package (v0.3.0)

Upvotes: 1

Related Questions