题 从data.table到eval的函数创建表达式


鉴于 data.table DAT:

dat <- data.table(x_one=1:10, x_two=1:10, y_one=1:10, y_two=1:10) 

我想要一个函数,它在两个相似的行之间创建一个表达式,给出它们的“根”名称,例如: x_one - x_two

myfun <- function(name) {
  one <- paste0(name, '_one')
  two <- paste0(name, '_two')

  parse(text=paste(one, '-', two))
}

现在,只使用一个根名称可以按预期工作并生成一个向量。

dat[, eval(myfun('x')),]

[1] 0 0 0 0 0 0 0 0 0 0

但是,尝试使用。分配该输出的名称 list 技术失败:

dat[, list(x_out = eval(myfun('x'))),]

Error in eval(expr, envir, enclos) : object 'x_one' not found

我可以通过添加一个“解决”这个问题 with(dat, ...) 但这似乎不是数据

dat[, list(x_out = with(dat, eval(myfun('x'))),
           y_out = with(dat, eval(myfun('y')))),]

    x_out y_out
 1:     0     0
 2:     0     0
 3:     0     0
 4:     0     0
 5:     0     0
 6:     0     0
 7:     0     0
 8:     0     0
 9:     0     0
10:     0     0

如果我想要一个像上面那样的输出,那么生成和评估这些表达式的正确方法是什么?

万一有帮助, sessionInfo() 输出低于。我记得能够做到这一点,或接近它的东西,但它已经有一段时间了 data.table 自...更新

R version 2.15.1 (2012-06-22)

Platform: x86_64-pc-linux-gnu (64-bit)

locale:
 [1] LC_CTYPE=en_US.UTF-8       LC_NUMERIC=C               LC_TIME=en_US.UTF-8        LC_COLLATE=en_US.UTF-8    
 [5] LC_MONETARY=en_US.UTF-8    LC_MESSAGES=en_US.UTF-8    LC_PAPER=C                 LC_NAME=C                 
 [9] LC_ADDRESS=C               LC_TELEPHONE=C             LC_MEASUREMENT=en_US.UTF-8 LC_IDENTIFICATION=C       

attached base packages:
[1] graphics  grDevices utils     datasets  stats     grid      methods   base     

other attached packages:
 [1] Cairo_1.5-1      zoo_1.7-7        stringr_0.6.1    doMC_1.2.5       multicore_0.1-7  iterators_1.0.6  foreach_1.4.0   
 [8] data.table_1.8.2 circular_0.4-3   boot_1.3-5       ggplot2_0.9.1    reshape2_1.2.1   plyr_1.7.1      

loaded via a namespace (and not attached):
 [1] codetools_0.2-8    colorspace_1.1-1   dichromat_1.2-4    digest_0.5.2       labeling_0.1       lattice_0.20-6    
 [7] MASS_7.3-20        memoise_0.1        munsell_0.3        proto_0.3-9.2      RColorBrewer_1.0-5 scales_0.2.1      
[13] tools_2.15.1      

22
2017-08-08 20:18


起源




答案:


一个解决方案就是把 list(...) 在函数输出中。

我倾向于使用 as.quoted,从@hadley实施的方式窃取 .() 在里面 plyr 包。

library(data.table)
library(plyr)
dat <- data.table(x_one=1:10, x_two=1:10, y_one=1:10, y_two=1:10) 
myfun <- function(name) {
  one <- paste0(name, '_one')
  two <- paste0(name, '_two')
  out <- paste0(name,'_out')
 as.quoted(paste('list(',out, '=',one, '-', two,')'))[[1]]
}


dat[, eval(myfun('x')),]

#    x_out
# 1:     0
# 2:     0
# 3:     0
# 4:     0
# 5:     0
# 6:     0
# 7:     0
# 8:     0
# 9:     0
#10:     0

要一次执行两列,您可以调整通话

myfun <- function(name) {
  one <- paste0(name, '_one')
  two <- paste0(name, '_two')
  out <- paste0(name,'_out')
  calls <- paste(paste(out, '=', one, '-',two), collapse = ',')


 as.quoted(paste('list(', calls,')'))[[1]]
}


dat[, eval(myfun(c('x','y'))),]

#   x_out y_out
# 1:     0     0
# 2:     0     0
# 3:     0     0
# 4:     0     0
# 5:     0     0
# 6:     0     0
# 7:     0     0
# 8:     0     0
# 9:     0     0
# 0:     0     0

至于原因.....

在这个解决方案中整个调用'list(..) 在parent.frame中被评估为data.table。

内部的相关代码 [.data.table 是

if (missing(j)) stop("logical error, j missing")
jsub = substitute(j)
if (is.null(jsub)) return(NULL)
jsubl = as.list.default(jsub)
if (identical(jsubl[[1L]],quote(eval))) {
    jsub = eval(jsubl[[2L]],parent.frame())
    if (is.expression(jsub)) jsub = jsub[[1L]]
}

如果(在你的情况下)

j = list(xout = eval(myfun('x'))) 

##then

jsub <- substitute(j) 

 #  list(xout = eval(myfun("x")))

as.list.default(jsub)
## [[1]]
## list
## 
## $xout
## eval(myfun("x"))

所以 jsubl[[1L]] 是 listjsubl[[2L]] 是 eval(myfun("x"))

所以 data.table 还没有找到电话 eval并且不会适当地处理它。

这将起作用,迫使第二次评估在正确的data.table内

# using OP myfun
dat[,list(xout =eval(myfun('x'), dat))]

一样的方法

eval(parse(text = 'x_one'),dat)
# [1]  1  2  3  4  5  6  7  8  9 10

工作,但

 eval(eval(parse(text = 'x_one')), dat)

才不是

编辑10/4/13

虽然它可能更安全(但更慢)使用 .SD 作为环境,因为它将是健壮的 i 要么 by 以及例如

dat[,list(xout =eval(myfun('x'), .SD))]

马修编辑:

+10到以上。我自己无法解释得更好。更进一步,我有时做的是构建 整个 data.table查询然后 eval 那。有时,它可能会更加健壮。我认为它像SQL;即,我们经常构造一个动态SQL语句,该语句被发送到要执行的SQL服务器。在进行调试时,有时也可以更容易地查看构造的查询并在浏览器提示符下运行它。但是,有时这样的查询会很长,所以通过 eval 成 ij 要么 by 通过不重新计算其他组件可以更有效。像往常一样,很多方法可以给猫皮肤。

考虑的微妙原因 eval整个查询包括:

  1. 分组快速的一个原因是它检查了 j 表达第一。如果是的话 list,它会删除名称,但会记住它们。然后呢 eval是的 无名 每个组的列表,然后在最终结果的最后恢复一次名称。其他方法可能很慢的一个原因是一遍又一遍地为每个组重新创建相同的列名矢量。越复杂 j 虽然定义了(例如,如果表达式没有精确地开始 list),内部编码检查逻辑的难度越大。这个领域有很多测试;例如,与...组合 eval如果名称丢失不起作用,则会出现详细程度报告。但是,构建一个“简单”查询(完整查询)和 eval因此,这可能更快,更健壮。

  2. 随着v1.8.2,现在有优化 joptions(datatable.optimize=Inf)。这检查 j 并修改它以进行优化 mean 和 lapply(.SD,...) 成语,到目前为止。这使得数量级差异并且意味着用户需要知道的更少(例如,现在一些维基点已经消失)。我们可以做更多的事情;例如。, DT[a==10] 可以优化到 DT[J(10)] 自动如果 key(DT)[1]=="a" [2014年9月更新 - 现已在v1.9.3中实施]。但同样,如果不是,内部优化会更难在内部进行编码 DT[,mean(a),by=b] 它的 DT[,list(x=eval(expr)),by=b] 哪里 expr 包含一个电话 mean, 例如。所以 eval整个查询可能会发挥得更好 datatable.optimize。在报告中详细说明它正在做什么,如果需要可以关闭优化;例如,测试它所产生的速度差异。

根据评论,FR#2183已被添加:“将j = list(xout = eval(...))的eval改为在DT范围内的eval”。感谢您的重点突出。那是复杂的 j 我的意思是在哪里 eval 嵌套在表达式中。如果 j  启动 同 eval但是,这更简单,已经编码(如上所示)并经过测试,应该进行优化。

如果有一个外卖,那就是:使用 DT[...,verbose=TRUE] 要么 options(datatable.verbose=TRUE) 去检查 data.table 当用于涉及的动态查询时,仍然有效地工作 eval


19
2017-08-08 23:02



有趣!如果有人了解原因 data.table 在我的示例中找不到列名但可以在此版本中找到它们我全都耳朵! - Justin
在我的例子中,整个呼吁 list 评估,而你的将有两个评估环境,可能需要调用不同的环境(从data.table内)。我试图破译源代码 [.data.table 但是认为Matt Doyle必须这样做[并且可能会更新以允许这个] - mnel
做得好!感谢您的侦探工作和解释。 - Justin
+10我自己无法解释得更好。添加了 FR#2183 “将j = list(xout = eval(...))的eval改为在DT范围内的eval”。但是在这种情况下,Justin希望输出列名称也是灵活的,iiuc,所以你的解决方案包括 list(...) 看来,无论如何都需要表达式。 - Matt Dowle
@MatthewDowle和mnel,谢谢你们的全面解释!有一天,我发誓要理解复杂的错误 data.table - Justin


这感觉并不理想,但这是我能想到的最好的。我会把它扔出去只是为了看看它是否有助于得出更好的回应......

vars <- c("x", "y")
res <- do.call(data.table, (lapply(vars, function(X) dat[,eval(myfun(X)),])))
setnames(res, names(res), paste0(vars, "_out"))

## Check the results
head(res, 3)
#    x_out y_out
# 1:     0     0
# 2:     0     0
# 3:     0     0

我不喜欢的部分是 lapply() 将以列表形式创建一个输出副本,然后 data.table() 将(据我所知)将这些数据复制到一个单独的位置,这比你使用的更糟糕 list() 在...内构建 [.data.frame()


2
2017-08-08 22:32



谢谢!您的版本看起来更像我的代码没有 data.table。我总是可以回过头来写出所有的列名,但是我没有得到任何样式点! - Justin