首先:感谢@MattDowle;自从我开始使用data.table
以来,R
是我遇到过的最好的事情之一。
第二:我知道data.table
中变量列名的各种用例的许多变通方法,包括:
可能更多我没有参考。
但是:即使我学会了上面记录的所有技巧,以至于我从来不必查看它们以提醒自己如何使用它们,我仍然会发现使用作为参数传递给函数的列名非常繁琐的任务。
我正在寻找的是以下解决方法/工作流程的“最佳实践认可”替代方案。考虑到我有一堆类似数据的列,并且希望对这些列或它们的集合执行一系列类似的操作,其中操作具有任意高的复杂性,并且列名称组传递给指定的每个操作在变量中。
我意识到这个问题听起来很人为,但我却以惊人的频率遇到它。这些示例通常非常混乱,很难将与此问题相关的功能分开,但我最近偶然发现了一个相当简单的简化用作MWE的方法:
library(data.table)
library(lubridate)
library(zoo)
the.table <- data.table(year=1991:1996,var1=floor(runif(6,400,1400)))
the.table[,`:=`(var2=var1/floor(runif(6,2,5)),
var3=var1/floor(runif(6,2,5)))]
# Replicate data across months
new.table <- the.table[, list(asofdate=seq(from=ymd((year)*10^4+101),
length.out=12,
by="1 month")),by=year]
# Do a complicated procedure to each variable in some group.
var.names <- c("var1","var2","var3")
for(varname in var.names) {
#As suggested in an answer to Link 3 above
#Convert the column name to a 'quote' object
quote.convert <- function(x) eval(parse(text=paste0('quote(',x,')')))
#Do this for every column name I'll need
varname <- quote.convert(varname)
anntot <- quote.convert(paste0(varname,".annual.total"))
monthly <- quote.convert(paste0(varname,".monthly"))
rolling <- quote.convert(paste0(varname,".rolling"))
scaled <- quote.convert(paste0(varname,".scaled"))
#Perform the relevant tasks, using eval()
#around every variable columnname I may want
new.table[,eval(anntot):=
the.table[,rep(eval(varname),each=12)]]
new.table[,eval(monthly):=
the.table[,rep(eval(varname)/12,each=12)]]
new.table[,eval(rolling):=
rollapply(eval(monthly),mean,width=12,
fill=c(head(eval(monthly),1),
tail(eval(monthly),1)))]
new.table[,eval(scaled):=
eval(anntot)/sum(eval(rolling))*eval(rolling),
by=year]
}
当然,这里对数据和变量的特殊影响是无关紧要的,所以请不要关注它或建议改进以完成它在这种特定情况下所完成的事情。我正在寻找的是一个通用策略,用于将data.table
动作的任意复杂过程重复应用于列列表或列表列表,在变量中指定或作为参数传递给a的工作流程。函数,其中过程必须以编程方式引用变量/参数中指定的列,并且可能包括更新,连接,分组,调用data.table
特殊对象.I
,.SD
等;但是比上面的那个更简单,更优雅,更短或更容易设计或实现或理解,或者需要经常进行quote
ing和eval
-ing的其他设计。
特别请注意,因为程序可能相当复杂并且涉及重复更新data.table
然后引用更新的列,标准的lapply(.SD,...), ... .SDcols = ...
方法通常不是可行的替代方法。同时用eval(a.column.name)
替换DT[[a.column.name]]
的每次调用既不简化也不完全一般,因为据我所知,这与其他data.table
操作不相称。
你描述的问题与data.table
没有严格的关系。
复杂的查询不能轻易地转换为机器可以解析的代码,因此我们无法逃避为复杂操作编写查询的复杂性。
想象一下如何以编程方式构造以下data.table
查询的查询
DT[, c(f1(v1, v2, opt=TRUE),
f2(v3, v4, v5, opt1=FALSE, opt2=TRUE),
lapply(.SD, f3, opt1=TRUE, opt2=FALSE))
, by=.(id1, id2)]
使用dplyr
或SQL - 假设所有列(id1,id2,v1 ... v5)或甚至选项(opt,opt1,opt2)都应作为变量传递。
由于上述原因,我认为您不能轻易完成您的问题中所述的要求:
比上面的或其他需要经常进行
quote
ing和eval
-ing的更简单,更优雅,更短或更容易设计或实现或理解。
虽然与其他编程语言相比,base R提供了非常有用的工具来处理这些问题。
你已经找到了使用get
,mget
,DT[[col_name]]
,parse
,quote
,eval
的建议。
DT[[col_name]]
可能是构建复杂查询的最简单方法,因为您可以只对字符串进行操作,但它不提供基本的语言语法验证。因此,您最终可能会尝试解析R解析器不接受的字符串。此外,data.table
提出了一个安全问题。parse
/ 2655#issuecomment-376781159是最常建议处理此类问题的人。 get
和mget
在内部被get
捕获并翻译成预期的列。所以你假设你的任意复杂查询将能够被mget
和正确输入的预期列分解。[.data.table
非常相似,具有前缀的变量将在[.data.table
内被取消引用。 。在将来的版本中,点阵前缀可能允许以下调用:get
[.data.table
和col1="a"; col2="b"; col3="g"; col4="x"; col5="y"
DT[..col4==..col5, .(s1=sum(..col1), s2=sum(..col2)), by=..col3]
。 quote
和eval
几乎被解释为从头开始手写。此方法不依赖于quote
功能来管理对列的引用。我们可以期望所有优化的工作方式与您手动编写这些查询的方式相同。我发现它也更容易调试,因为在任何时候你都可以打印引用的表达式来查看实际传递给eval
查询的内容。此外,发生错误的空间也较小。使用R语言对象构造复杂查询有时很棘手,很容易将过程包装到函数中,因此它可以应用于不同的用例并且可以轻松地重复使用。需要注意的是,此方法独立于data.table
。它使用R语言结构。您可以在语言章节的计算机官方data.table
中找到更多相关信息。data.table
提交了一个名为macro的新概念提案。简而言之,它是R Language Definition的包装器,因此您仍然需要对R语言对象进行操作。欢迎您在那里发表评论。举个例子。我将所有逻辑包装到#1579函数中。调用DT[eval(qi), eval(qj), eval(qby)]
将打印在do_vars
而不是do_vars(donot=TRUE)
上计算的表达式。下面的代码应该在OP代码之后运行。
data.table
eval
我试图在data.table中做到这一点。“这不是那么糟糕”......但是经过一段尴尬的时间后,我放弃了。 Matt说的是“片段然后加入”,但我无法找到优雅的方法来做这些片段,特别是因为最后一个取决于之前的步骤。
我不得不说,这是一个非常出色的问题,我也经常遇到类似的问题。我喜欢data.table,但有时我仍然很挣扎。我不知道我是否在使用data.table或问题的复杂性。
这是我采取的不完整的方法。
实际上,我可以想象,在正常过程中,您将存储更多的中间变量,这些变量可用于计算这些值。
expected = copy(new.table)
new.table = the.table[, list(asofdate=seq(from=ymd((year)*10^4+101), length.out=12, by="1 month")), by=year]
do_vars = function(x, y, vars, donot=FALSE) {
name.suffix = function(x, suffix) as.name(paste(x, suffix, sep="."))
do_var = function(var, x, y) {
substitute({
x[, .anntot := y[, rep(.var, each=12)]]
x[, .monthly := y[, rep(.var/12, each=12)]]
x[, .rolling := rollapply(.monthly, mean, width=12, fill=c(head(.monthly,1), tail(.monthly,1)))]
x[, .scaled := .anntot/sum(.rolling)*.rolling, by=year]
}, list(
.var=as.name(var),
.anntot=name.suffix(var, "annual.total"),
.monthly=name.suffix(var, "monthly"),
.rolling=name.suffix(var, "rolling"),
.scaled=name.suffix(var, "scaled")
))
}
ql = lapply(setNames(nm=vars), do_var, x, y)
if (donot) return(ql)
lapply(ql, eval.parent)
invisible(x)
}
do_vars(new.table, the.table, c("var1","var2","var3"))
all.equal(expected, new.table)
#[1] TRUE
谢谢你的提问。您最初的方法在解决大多数问题方面都有很长的路要走。
这里我稍微调整了引用函数,并改变了解析方法并将整个RHS表达式作为字符串而不是单个变量进行评估。
理由是:
do_vars(new.table, the.table, c("var1","var2","var3"), donot=TRUE)
#$var1
#{
# x[, `:=`(var1.annual.total, y[, rep(var1, each = 12)])]
# x[, `:=`(var1.monthly, y[, rep(var1/12, each = 12)])]
# x[, `:=`(var1.rolling, rollapply(var1.monthly, mean, width = 12,
# fill = c(head(var1.monthly, 1), tail(var1.monthly, 1))))]
# x[, `:=`(var1.scaled, var1.annual.total/sum(var1.rolling) *
# var1.rolling), by = year]
#}
#
#$var2
#{
# x[, `:=`(var2.annual.total, y[, rep(var2, each = 12)])]
# x[, `:=`(var2.monthly, y[, rep(var2/12, each = 12)])]
# x[, `:=`(var2.rolling, rollapply(var2.monthly, mean, width = 12,
# fill = c(head(var2.monthly, 1), tail(var2.monthly, 1))))]
# x[, `:=`(var2.scaled, var2.annual.total/sum(var2.rolling) *
# var2.rolling), by = year]
#}
#
#$var3
#{
# x[, `:=`(var3.annual.total, y[, rep(var3, each = 12)])]
# x[, `:=`(var3.monthly, y[, rep(var3/12, each = 12)])]
# x[, `:=`(var3.rolling, rollapply(var3.monthly, mean, width = 12,
# fill = c(head(var3.monthly, 1), tail(var3.monthly, 1))))]
# x[, `:=`(var3.scaled, var3.annual.total/sum(var3.rolling) *
# var3.rolling), by = year]
#}
#