将表达式传递给内部函数

20

深入探究 R 评估的奥秘... 这与我的上一个问题密切相关(如何编写一个在数据框内评估表达式的 R 函数)。假设我想编写一个函数 topfn,它接受一个数据框和涉及该数据框列名的表达式作为参数。我想将这两个参数都传递给另一个函数 fn,该函数实际上在数据框的“环境”中评估表达式。而且当传递数据框和表达式时,我希望 fntopfn 都能正常工作。

我的第一次尝试是按照上述问题的答案建议进行定义:

 fn <- function(dfr, expr) {
   mf <- match.call()
   eval( mf$expr, envir = dfr )
 }

并且定义topfn像这样:

topfn <- function(df, ex) {
  mf <- match.call()
  fn(df, mf$ex) 
}

现在如果我有一个数据框:

df <- data.frame( a = 1:5, b = 1:5 )

内部函数fn运行良好:

> fn(df,a)
[1] 1 2 3 4 5

但是topfn不起作用:

> topfn(df,a)
mf$ex
为了解决这个问题,我先检查了 topfn(df,a) 的类。
> class(topfn(df,a))
[1] "call"

这给了我一个有些丑陋的想法,可以通过重新定义fn来实现:

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

现在两个函数都可以工作:

> fn(df,a)
[1] 1 2 3 4 5
> topfn(df,a)
[1] 1 2 3 4 5

就像我所说的,这看起来像是一个丑陋的hack。是否有更好的方法(或更标准的习惯用法)让这些工作起来?我已经查阅了Lumley的奇怪命名的《标准非标准评估规则文档》http://developer.r-project.org/nonstandard-eval.pdf 但在阅读后并没有特别大的收获。如果能提供任何指向示例函数源代码的指针也会很有帮助。


2
正如我建议的那样,您需要仔细区分交互使用的函数和从其他函数调用的函数。使用替代和其他技巧调用任何函数都非常困难。换句话说,没有一种干净的方式让fntopfn同时处理相同类型的输入 - 这是一件好事。 - hadley
1
@hadley 我理解你的观点:为交互使用而设计的函数可以使用替代/其他技巧,使其易于在控制台上输入(即无需引号等)。而仅设计为由其他函数调用的函数不应使用此类技巧,因为它们可能会出现不可预测的错误。特别是@Richie在他的答案末尾提供的解决方案,即使它在我上面的特定场景中有效,但在其他场景中使用时可能会出现问题(特别是fn),我想这就是你的意思,对吗? - Prasad Chalasani
1
如果你能提供更多关于你试图实现什么的详细信息,我将能够给出更具体的建议——在抽象的情况下帮助是很困难的。 - hadley
1
两种选择:走plyr/ggplot路线并使用特殊函数quote。.来自plyr的函数可能适合您的需求。否则,编写每个函数的程序员和交互式版本:交互式版本捕获表达式,然后调用。无论哪种方式,都会在长期运行中为您节省很多痛苦。 - hadley
@PrasadChalasani 看起来 R 评估页面已经移动到这里 https://adv-r.hadley.nz/evaluation.html - Espen Riskedal
显示剩余3条评论
2个回答

17

通过将字符串传递给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")

编辑:

您可以让用户输入表达式,但为了方便起见,在内部使用字符串。

topfn更改为

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

另一个编辑:

这似乎有效:

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)

1
@richie:谢谢,是的,但我正在寻找一种避免传递字符串的方法。我希望这些函数能够像R中许多其他接受非引用表达式作为参数的函数一样工作。 - Prasad Chalasani
请参考我在之前问题中的回答,了解 fn 的第三种(格子样式)规范。https://dev59.com/s2445IYBdhLWcg3w0dZD - Richie Cotton
@Richie 是的,我看到了,谢谢。这实际上是使内部函数工作的最简单方法,但将其适应于上面的嵌套情况同样具有问题。 - Prasad Chalasani
@Richie,我看到了你的更新:topfn现在可以接受表达式作为参数,但是内部的fn仍然只能接受字符串。我希望两个函数都能够接受表达式参数。背景是我写了一个很有用的函数fn,它接受一个表达式作为参数,我还写了另一个很有用的包装器topfn,它也接受表达式作为参数,现在我想将它传递给fn - Prasad Chalasani
1
@Prasad:希望这个版本可以;过多使用 evalsubstitute 使我头疼。 - Richie Cotton
+1 @Richie 因为找到了正确的 eval/substitute 咒语而受到赞扬!谢谢...现在我可以摆脱我的丑陋黑客了。 - Prasad Chalasani

1

你可以使用三个点来收集参数并将它们传递给另一个函数,这是你的意思吗?

ftop=function(...) f(...)
f=function(a,b) a[b]

a=data.frame(b=10)

ftop(a,"b")

f(a,"b")

不太对。我不想使用点号,我希望将显式参数传递给内部函数。 - Prasad Chalasani

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接