Phi*_*lip 48 r calculated-columns programmatically-created data.table
首先:感谢@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]
}
Run Code Online (Sandbox Code Playgroud)
当然,这里对数据和变量的特殊影响是无关紧要的,所以请不要关注它或建议改进以完成它在这种特定情况下所完成的事情.相反,我正在寻找的是一种通用策略,用于将一个任意复杂的data.table动作过程重复应用于列列表或列表列表,在变量中指定或作为参数传递给函数的工作流程,这里的程序必须以编程方式引用的变量/参数命名的列,并可能包括更新,连接,分组,来电的data.table特殊对象.I,.SD等等; 但是比上面的那个更简单,更优雅,更短或更容易设计或实现或理解,或者需要频繁quote和更频繁的一个eval.
特别请注意,由于过程可能相当复杂并且涉及重复更新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)]
Run Code Online (Sandbox Code Playgroud)
using 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]],data.table优化可能无法很好地发挥作用,因此在这里没有太大用处。 parse这是构造复杂查询的最简单方法,因为您可以对字符串进行操作,但是它不提供基本语言语法验证。因此,您可能最终尝试解析R解析器不接受的字符串。此外,还有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]
Run Code Online (Sandbox Code Playgroud)
quote,eval而不是。quote并且eval几乎是从头开始用手写来解释的。此方法不依赖于data.table管理对列的引用的能力。我们可以预期所有优化的工作方式都与您手工编写这些查询的方式相同。我发现调试起来也更容易,因为在任何时候都可以仅打印带引号的表达式以查看实际传递给的内容data.table查询的内容。此外,发生错误的空间更少。使用R语言对象构造复杂的查询有时很棘手,很容易将过程包装成函数,因此可以在不同的用例中应用并易于重用。重要的是要注意,此方法独立于data.table。它使用R语言构造。您可以在语言一章的“ 计算中的官方R语言定义”中找到有关此信息的更多信息。 DT[eval(qi), eval(qj), eval(qby)]转到示例。我将所有逻辑包装到do_vars函数中。调用do_vars(donot=TRUE)将打印要data.table代替其计算的表达式eval。下面的代码应在OP代码之后运行。
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
Run Code Online (Sandbox Code Playgroud)
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]
#}
#
Run Code Online (Sandbox Code Playgroud)
我尝试在 data.table 中执行此操作,心想“这还不错”……但经过一段令人尴尬的时间后,我放弃了。马特说了类似“分成几部分然后加入”之类的话,但我无法找出完成这些部分的优雅方法,特别是因为最后一个取决于前面的步骤。
我不得不说,这是一个非常精彩的问题,我也经常遇到类似的问题。我喜欢 data.table,但有时我仍然很挣扎。我不知道我是否正在与 data.table 或问题的复杂性作斗争。
这是我采取的不完整的方法。
实际上,我可以想象,在正常过程中,您将存储更多的中间变量,这些变量对于计算这些值非常有用。
library(data.table)
library(zoo)
## Example yearly data
set.seed(27)
DT <- data.table(year=1991:1996,
var1=floor(runif(6,400,1400)))
DT[ , var2 := var1 / floor(runif(6,2,5))]
DT[ , var3 := var1 / floor(runif(6,2,5))]
setkeyv(DT,colnames(DT)[1])
DT
## Convenience function
nonkey <- function(dt){colnames(dt)[!colnames(dt)%in%key(dt)]}
## Annual data expressed monthly
NewDT <- DT[, j=list(asofdate=as.IDate(paste(year, 1:12, 1, sep="-"))), by=year]
setkeyv(NewDT, colnames(NewDT)[1:2])
## Create annual data
NewDT_Annual <- NewDT[DT]
setnames(NewDT_Annual,
nonkey(NewDT_Annual),
paste0(nonkey(NewDT_Annual), ".annual.total"))
## Compute monthly data
NewDT_Monthly <- NewDT[DT[ , .SD / 12, keyby=list(year)]]
setnames(NewDT_Monthly,
nonkey(NewDT_Monthly),
paste0(nonkey(NewDT_Monthly), ".monthly"))
## Compute rolling stats
NewDT_roll <- NewDT_Monthly[j = lapply(.SD, rollapply, mean, width=12,
fill=c(.SD[1],tail(.SD, 1))),
.SDcols=nonkey(NewDT_Monthly)]
NewDT_roll <- cbind(NewDT_Monthly[,1:2,with=F], NewDT_roll)
setkeyv(NewDT_roll, colnames(NewDT_roll)[1:2])
setnames(NewDT_roll,
nonkey(NewDT_roll),
gsub(".monthly$",".rolling",nonkey(NewDT_roll)))
## Compute normalized values
## Compute "adjustment" table which is
## total of each variable, by year for rolling
## divided by
## original annual totals
## merge "adjustment values" in with monthly data, and then
## make a modified data.table which is each varaible * annual adjustment factor
## Merge everything
NewDT_Combined <- NewDT_Annual[NewDT_roll][NewDT_Monthly]
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
4740 次 |
| 最近记录: |