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

时间:2012-08-08 20:18:24

标签: r data.table

鉴于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, ...)来解决这个问题,但这似乎不是data.table-ish

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      

2 个答案:

答案 0 :(得分:19)

一种解决方案是将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(..)的整个调用在作为data.table的parent.frame中进行评估。

[.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]]
}

if(在您的情况下)

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作为环境可能更安全(但速度更慢),因为它对iby也很健壮,例如

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

马修编辑:

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

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

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

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

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

    如果只有一个内容,那就是:当用于涉及options(datatable.verbose=TRUE)的动态查询时,请使用data.tableeval检查{{1}}是否仍然有效。

答案 1 :(得分:2)

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

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()构造更糟糕。