Reputation: 69
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
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