如何编写一个调用data.table的函数的函数?

19

data.table具有一些特殊的语法,需要使用表达式作为ij参数。

这对于编写接受并传递参数到数据表的函数有一些影响,如常见问题解答的第1.16节所详细解释的那样。

但我无法想象如何把它提高到另一个级别。

以下是一个示例。假设我想编写一个包装器函数foo(),对我的数据进行特定汇总,然后再编写一个调用foo()并绘制结果的第二个包装器plotfoo()

library(data.table)


foo <- function(data, by){
  by <- substitute(by)
  data[, .N, by=list(eval(by))]
}

DT <- data.table(mtcars)
foo(DT, gear)

好的,这个可行,因为我得到了我的表格结果:

   by  N
1:  4 12
2:  3 15
3:  5  5

现在,当我写 plotfoo() 时,我尝试做到与之前完全相同,但是我失败了:

plotfoo <- function(data, by){
  by <- substitute(by)
  foo(data, eval(by))
}
plotfoo(DT, gear)

但是这一次我收到了一个错误信息:

Error: evaluation nested too deeply: infinite recursion / options(expressions=)?

好的,eval() 函数造成了问题。让我们把它移除:

plotfoo <- function(data, by){
  by <- substitute(by)
  foo(data, by)
}
plotfoo(DT, gear)

哦不,我收到了一个新的错误信息:

Error in `[.data.table`(data, , .N, by = list(eval(by))) : 
  column or expression 1 of 'by' or 'keyby' is type symbol. Do not quote column names. Useage: DT[,sum(colC),by=list(colA,month(colB))]

而这就是我卡住的地方。

问题:如何编写一个调用函数和调用data.table的函数?


不是解决方案,但如果您删除 substitute(by)eval 并将 gear 作为字符变量传递,例如 foo(DT, "gear"),那么两者都可以工作。 - Arun
2个回答

14
这将有效:
plotfoo <- function(data, by) {
  by <- substitute(by)
  do.call(foo, list(quote(data), by))
}

plotfoo(DT, gear)
#    by  N
# 1:  4 12
# 2:  3 15
# 3:  5  5
说明:

问题在于在plotfoo()中对foo()的调用看起来像以下之一:


foo(data, eval(by))
foo(data, by)

foo 处理这些调用时,它会职责地为第二个形式参数 (by) 进行替换,将 eval(by)by作为 by 的值。但是你希望 by 的值为 gear,就像调用 foo(data, gear) 一样。

do.call() 通过在构造并执行调用之前评估其第二个参数的元素来解决此问题。因此,当你传递给它 by 时,它将对其进行求值以获取其值 (符号gear),然后构造一个看起来(基本上)像这样的调用:

foo(data, gear)

这个非常好用,谢谢,甚至可以满足我隐含但未声明的传递ij参数的需求。 - Andrie

5

我认为你可能陷入了困境。这个可以工作:

library(data.table)
foo <- function(data, by){
  by <- by
  data[, .N, by=by]
}

DT <- data.table(mtcars)
foo(DT, 'gear')

plotfoo <- function(data, by){
  foo(data, by)
}
plotfoo(DT, 'gear')

而这种方法支持传入字符值:

> gg <- 'gear'
> plotfoo <- function(data, by){
+   foo(data, by)
+ }
> plotfoo(DT, gg)
   gear  N
1:    4 12
2:    3 15
3:    5  5

1
抱歉打扰您,我想知道 by <- by 在 foo 函数中是什么意思。 - vodka
啊,是的,你说得没错。对不起,我在试图简化我的例子时,删除了传递参数到“i”或“j”的原始问题。很抱歉 - 我会编辑我的问题。 - Andrie
@vodka:没有特别的意义,只是从编辑原始文件中剩下的。 - IRTFM
@Andrie:Josh或者我的回答是否解决了你在i和j评估方面遇到的问题? - IRTFM
是的,Josh的解决方案确实非常好,尽管我不敢说我理解它! - Andrie
嘿,我承认我的一个最喜欢的函数是在经过相当多的思考后使用do.call编写的。在某些方面,do.call就像R语言子宇宙中的paste0一样。至少这是我对它的看法。 - IRTFM

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