2

I'm trying to write a function that allows me to input text strings for field, operator, and value, and return a simple dplyr::filter function that I can then apply to a dataset.

Example:

library(dplyr)
field <- "Species"
operator <- "=="
value <- "virginica"
myfun <- substitute(
   function(x) filter(x, EXPR(FIELD, VALUE)),
   list(
     FIELD = as.symbol(field),
     EXPR = as.symbol(operator),
     VALUE = value
   )
 )
myfun
function(x) filter(x, Species == "virginica")

So far, so good, right? Looks like we're all ready to roll. But not so fast:

> myfun(iris)
Error in myfun(iris) (from foo.R!10Zf0E#19) : could not find function "myfun"

If I type class(myfun), it turns out that I've created something called a call. But I really wanted a function. Is there a way to turn the call into a function, or rewrite the above code so that I actually end up with a working function?

Ronak Shah
  • 377,200
  • 20
  • 156
  • 213
Aaron Cooley
  • 438
  • 3
  • 8

3 Answers3

3

Well, that was easy. The solution is to wrap the substitute() with an eval(), like so:

> myfun <- eval(
      substitute(
        function(x) filter(x, EXPR(FIELD, VALUE)),
        list(
          FIELD = as.symbol(field),
          EXPR = as.symbol(operator),
          VALUE = value
        )
      )
    )

Then myfun(iris) works as intended.

Aaron Cooley
  • 438
  • 3
  • 8
1

A more "R" way would be to use formals and body

f <- function() {}
formals(f) <- alist(x = )
body(f) <- substitute(
  filter(x, EXPR(FIELD, VALUE)),
  list(FIELD = as.symbol(field), EXPR = as.symbol(operator), VALUE = value)
)
f
# function (x)
#   filter(x, Species == "virginica")
f(iris)

Plus you can add on formals and lines to your function:

formals(f) <- c(formals(f), alist(y = 120))
f
# function (x, y = 120)
#   filter(x, Species == "virginica")
body(f) <- as.call(c(as.name('{'), quote(x <- head(x, y)), body(f)))
f
# function (x, y = 120)
# {
#   x <- head(x, y)
#   filter(x, Species == "virginica")
# }
f(iris)

And also edit the lines of the function:

body(f)[[2]] <- quote(x <- tail(x, y))
# function (x, y = 120)
# {
#   x <- tail(x, y)
#   filter(x, Species == "virginica")
# }
f(iris)
rawr
  • 20,481
  • 4
  • 44
  • 78
0

We could create the filter expression dynamically and use eval(parse()) or eval(parse_expr()).

myfun <- function(x, field, operator, value) {
     dplyr::filter(x, eval(rlang::parse_expr(sprintf('%s %s "%s"', 
                          field, operator, value))))
}

field <- "Species"
operator <- "=="
value <- "virginica"
myfun(iris, field, operator, value)

#   Sepal.Length Sepal.Width Petal.Length Petal.Width   Species
#1           6.3         3.3          6.0         2.5 virginica
#2           5.8         2.7          5.1         1.9 virginica
#3           7.1         3.0          5.9         2.1 virginica
#4           6.3         2.9          5.6         1.8 virginica
#5           6.5         3.0          5.8         2.2 virginica
#6           7.6         3.0          6.6         2.1 virginica
#7           4.9         2.5          4.5         1.7 virginica
#8           7.3         2.9          6.3         1.8 virginica
#9           6.7         2.5          5.8         1.8 virginica
#10          7.2         3.6          6.1         2.5 virginica
#...
#...

where

sprintf('%s %s "%s"', field, operator, value)
#[1] "Species == \"virginica\""
Ronak Shah
  • 377,200
  • 20
  • 156
  • 213