まず、@ MattDowleに感謝します。 data.table
は、R
を使い始めて以来、私に起こった最高の出来事の1つです。
2番目:data.table
の変数列名のさまざまな使用例について、次のような多くの回避策を知っています。
そしておそらくもっと私は参照していません。
しかし、上記のすべてのトリックを学び、それらを使用する方法を思い出すためにそれらを調べる必要がなくなったとしても、関数にパラメーターとして渡される列名を操作することは非常に難しいことがわかります面倒な作業。
私が探しているのは、次の回避策/ワークフローの「ベストプラクティスで承認された」代替策です。類似したデータの列がたくさんあり、これらの列またはそれらのセットに対して類似した操作のシーケンスを実行したいと考えます。操作は任意に非常に複雑で、指定された各操作に渡される列名のグループ変数内。
私はこの問題soundsが考案されていることを理解していますが、驚くべき頻度でそれに遭遇します。例は通常非常に乱雑なので、この質問に関連する機能を分離することは困難ですが、ここで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
アクションの任意に複雑な手順を列のリストまたは列のリストのリストに繰り返し適用するワークフローの一般的な戦略であり、変数で指定されるか、引数として渡されますここで、プロシージャは変数/引数で指定された列をプログラムで参照する必要があり、更新、結合、グループ化、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を頻繁に必要とするものよりもシンプルで、エレガントで、短く、設計、実装、または理解が容易です。
他のプログラミング言語と比較すると、ベース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
_内でde-referencedになります。 。将来のリリースでは、ドットドットプレフィックスで次のような呼び出しが許可される可能性があります。_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
は、ほとんど最初から手書きで書かれたものとして解釈されます。このメソッドは、列への参照を管理する_data.table
_機能に依存しません。すべての最適化が、これらのクエリを手動で書くのと同じように機能することを期待できます。引用符で囲まれた式を印刷して、実際に_data.table
_クエリに渡されているものを確認できるため、いつでもデバッグが容易であることがわかりました。さらに、バグが発生するスペースが少なくなります。 R言語オブジェクトを使用して複雑なクエリを作成するのは難しい場合があります。プロシージャを関数にラップして、さまざまなユースケースに適用して簡単に再利用できるようにするのは簡単です。このメソッドは_data.table
_から独立していることに注意してください。 R言語の構造を使用します。詳細については、公式の R言語定義言語の計算の章を参照してください。DT[eval(qi), eval(qj), eval(qby)]
のラッパーであるため、R言語オブジェクトを操作する必要があります。そこにコメントを書き込んでください。例に進みます。すべてのロジックを_do_vars
_関数にラップします。 do_vars(donot=TRUE)
を呼び出すと、eval
ではなく_data.table
_で計算される式が出力されます。以下のコードは、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
_
_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]
#}
#
_
私はこれを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]
質問ありがとうございます。元のアプローチは、ほとんどの問題を解決するのに大いに役立ちます。
ここでは、クォート関数を少し調整し、RHS式全体を個々の変数ではなく文字列として解析および評価するようにアプローチを変更しました。
推論は:
library(data.table)
library(lubridate)
library(Zoo)
set.seed(1)
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]
# function to paste, parse & evaluate arguments
evalp <- function(..., envir=parent.frame()) {eval(parse(text=paste0(...)), envir=envir)}
# Do a complicated procedure to each variable in some group.
var.names <- c("var1","var2","var3")
for(varname in var.names) {
# 1. For LHS, use paste0 to generate new column name as string (from @eddi's comment)
# 2. For RHS, use evalp
new.table[, paste0(varname, '.annual.total') := evalp(
'the.table[,rep(', varname, ',each=12)]'
)]
new.table[, paste0(varname, '.monthly') := evalp(
'the.table[,rep(', varname, '/12,each=12)]'
)]
# Need to add envir=.SD when working within the table
new.table[, paste0(varname, '.rolling') := evalp(
'rollapply(',varname, '.monthly,mean,width=12,
fill=c(head(', varname, '.monthly,1), tail(', varname, '.monthly,1)))'
, envir=.SD
)]
new.table[,paste0(varname, '.scaled'):= evalp(
varname, '.annual.total / sum(', varname, '.rolling) * ', varname, '.rolling'
, envir=.SD
)
,by=year
]
# Since we're working with strings, more freedom
# to work programmatically
new.table[, paste0(varname, '.row.percent') := evalp(
'the.table[,rep(', varname, '/ (', paste(var.names, collapse='+'), '), each=12)]'
)]
}