R:将表达式传递给内部函数

Pra*_*ani 14 evaluation r lazy-evaluation

进一步深入研究R评估的奥秘......这与我之前的问题(如何编写一个评估数据框内表达式的R函数)密切相关.假设我想编写一个函数topfn,它接受一个数据框和一个涉及该数据框的列名的表达式.我想将这两个参数传递给另一个fn实际评估数据框"环境"中表达式的函数.我想要两者fntopfn在传递数据框和表达式时正常工作

根据上述问题的答案,我的第一次尝试是定义:

 fn <- function(dfr, expr) {
   mf <- match.call()
   eval( mf$expr, envir = dfr )
 }
Run Code Online (Sandbox Code Playgroud)

并定义topfn如下:

topfn <- function(df, ex) {
  mf <- match.call()
  fn(df, mf$ex) 
}
Run Code Online (Sandbox Code Playgroud)

现在,如果我有一个数据框架

df <- data.frame( a = 1:5, b = 1:5 )
Run Code Online (Sandbox Code Playgroud)

内部函数fn工作正常:

> fn(df,a)
[1] 1 2 3 4 5
Run Code Online (Sandbox Code Playgroud)

但是topfn不起作用:

> topfn(df,a)
mf$ex
Run Code Online (Sandbox Code Playgroud)

为了解决这个问题,我首先检查一下topfn(df,a),

> class(topfn(df,a))
[1] "call"
Run Code Online (Sandbox Code Playgroud)

这给了我一个丑陋的黑客重新定义fn如下的想法:

fn <- function(dfr, expr) {
  mf <- match.call()
  res <- eval(mf$expr, envir = dfr)  
  if(class(res) == 'call')
    eval(expr, envir = dfr) else
  res
}
Run Code Online (Sandbox Code Playgroud)

现在这两个功能都有效:

> fn(df,a)
[1] 1 2 3 4 5
> topfn(df,a)
[1] 1 2 3 4 5
Run Code Online (Sandbox Code Playgroud)

正如我所说,这看起来像一个丑陋的黑客.是否有更好的方法(或更标准的习惯用法)来使这些工作?我已经查阅了Lumley奇怪的标准非标准评估规则文档http://developer.r-project.org/nonstandard-eval.pdf,但在阅读之后并没有特别开明.对于我可以查看示例的函数源代码的任何指针也很有帮助.

Ric*_*ton 14

通过将字符串传递给topfn而不是表达式,最容易避免这种情况.

topfn <- function(df, ex_txt) 
{
  fn(df, ex_txt) 
}

fn <- function(dfr, expr_txt) 
{        
   eval(parse(text = expr_txt), dfr) 
}

df <- data.frame(a = 1:5, b = 1:5 )
fn(df, "a")                              
fn(df, "2 * a + b")
topfn(df, "a")             
topfn(df, "2 * a + b")
Run Code Online (Sandbox Code Playgroud)

编辑:

您可以让用户传入表达式,但为方便起见,请使用下面的字符串.

更改topfn

topfn <- function(df, ex) 
{
  ex_txt <- deparse(substitute(ex))
  fn(df, ex_txt) 
}
topfn(df, a)             
topfn(df, 2 * a + b)
Run Code Online (Sandbox Code Playgroud)

另一个编辑:

这似乎有效:

topfn <- function(df, ex) 
{
  eval(substitute(fn(df, ex)))
}

fn <- function(dfr, expr) 
{        
   eval(substitute(expr), dfr) 
}
fn(df, a)                              
fn(df, 2 * a + b)
topfn(df, a)             
topfn(df, 2 * a + b)
Run Code Online (Sandbox Code Playgroud)