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.