23

鉴于data.table数据:

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      
4

2 回答 2

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

如果(在你的情况下)

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]]是这样list,jsubl[[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)

才不是

2013 年 10 月 4 日编辑

尽管将其.SD用作环境可能更安全(但速度较慢),因为它会变得健壮iby例如

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

从马修编辑:

+10 以上。我自己无法更好地解释它。更进一步,我有时会构建整个data.table 查询,然后再构建eval。有时,这种方式可能会更健壮一些。我把它想象成 SQL;即,我们经常构造一条动态的SQL语句,发送给SQL服务器执行。当您也在调试时,有时也更容易查看构造的查询并在浏览器提示符下运行它。但是,有时这样的查询会很长,因此传入eval,i或者j通过by不重新计算其他组件来提高效率。像往常一样,有很多方法可以给猫剥皮。

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

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

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

根据评论,已添加 FR#2183:“在 DT 范围内将 j=list(xout=eval(...)) 的 eval 更改为 eval”。感谢您的强调。j这就是我的意思eval是嵌套在表达式中的那种复杂性。但是,如果j 以 开头eval则要简单得多,并且已经编码(如上所示)并经过测试,并且应该进行优化。

如果有一个要点,那就是:DT[...,verbose=TRUE]当用于options(datatable.verbose=TRUE)涉及.data.tableeval

于 2012-08-08T23:02:06.030 回答
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()

于 2012-08-08T22:32:51.773 回答