Home > database >  How to pass a variable to a function which has already implement non-standard evaluation in its argu
How to pass a variable to a function which has already implement non-standard evaluation in its argu

Time:11-29

I am trying to wrap up a function from an R package. From the source codes, it appears that it has non-standard evaluations for some arguments. How can I write my function to pass the value to the argument that has non-standard evaluation implemented?

Here is a toy example

data <- data.frame(name = 1:10)
#Suppose the one below is the function from that package
toy.fun <- function(dat, var) {
    eval(substitute(var), dat)
}
> toy.fun(data, name)
 [1]  1  2  3  4  5  6  7  8  9 10

Here is what I try to wrap it

toy.fun2 <- function(dat, var2) {
    var_name <- deparse(substitute(var2))
    #example, but for similar purpose. 
    data_subset <- dat[var_name] 
    toy.fun(data_subset, var2)
}
> toy.fun2(data, name)
 Error in eval(substitute(var), dat) : object 'var2' not found

Edit

I should make the question more clear, in which I want to pass different variable name to the function argument in the wrapper for var2. So that when there are different variable names in the data, it could use that name for both data selection, and pass to the function I try to wrap. The source codes for exceedance function from heatwaveR has this ts_y <- eval(substitute(y), data) to capture the input variable y already. This is equivalent to my toy.fun.

I have modified the toy.fun2 for clarity.

CodePudding user response:

Grab the call using match.call, replace the function name and evaluate it. It would also be possible to modify the other arguments as needed. Look at the source of lm to see another example of this approach.

toy.fun2 <- function(dat, var) {
  cl <- match.call()
  cl[[1L]] <- quote(toy.fun)
  eval.parent(cl)
}
toy.fun2(data, name)
##  [1]  1  2  3  4  5  6  7  8  9 10

CodePudding user response:

We can pass arguments to functions using dots or ellipses, see ?dots and ?dotsMethods, a variable length argument.

data <- data.frame(name = 1:10)
toy <- function(dat, var) {
  eval(substitute(var), dat)
}
toy(data, name)
#> [1]  1  2  3  4  5  6  7  8  9 10

toy2 <- function(dat, ...) {
  var_name <- deparse(substitute(...))
  data_subset <- dat[var_name] 
  toy(data_subset, ...)
}
toy2(data, name)
#>  [1]  1  2  3  4  5  6  7  8  9 10

From the R language definition section 2.1.9: If a function has ... as a formal argument then any actual arguments that do not match a formal argument are matched with ....

An excerpt from Chambers (2008) book Software for Data Analysis:

The evaluator looks in the environment of the current evaluation first for the objects contained in that environment itself, and then step by step back through the enclosing environments.

To see what it does, we can use substitute(c(...)) in (toy2) function and find

c(data, name)

Here name and data are found in the enclosing environment.

As an aside - If the wrapper is intended to be used by a wider audience it is a good idea to carefully document where the ellipsis are passed to.

Updated question

A glance at the heatwaveR package source code for the exceedance function I think the ... shouldn't clash.

However, here it is probably a good idea to mention some ways to find further info if needed, since the package does use tidyverse. You might want to take a look at the rlang package, and experiment with a combination of enquo and !!, or in the case of a list, enquos and !!!. A newer addition to handling NSE in the tidyverse is the {{ }} embrace. The tidyverse documentation or chapter 20 of Hadley Wickham's Advanced R may provide more answers.

  • Related