63

まず第一に、@MattDowle に感謝します。data.tableを使い始めて以来、私に起こった最高のことの 1 つRです。

2 番目: の変数列名のさまざまな使用例について、次のような多くの回避策があることを認識していますdata.table

  1. 文字ベクトルに名前が格納されている data.table 変数を選択 / 割り当て
  2. Rの変数を使用してdata.tableに列名を渡す
  3. 変数に保存された名前で data.table 列を参照する
  4. プログラムで列名を data.table に渡す
  5. Data.table メタプログラミング
  6. data.table を呼び出す関数を呼び出す関数の書き方
  7. `data.table` での動的列名の使用
  8. data.table、R の動的列名
  9. グループごとに、data.table で := を使用して複数の列を割り当てる
  10. data.table を使用した「group by」操作で列名を設定する
  11. 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、変数で指定された、または関数に引数として渡された列のリストまたは列のリストのリストに、アクションの任意の複雑な手順を繰り返し適用するワークフローの一般的な戦略です、プロシージャは、変数/引数で指定された列をプログラムで参照する必要があり、更新、結合、グループ化、data.table特別なオブジェクト.Iへの呼び出し.SDなどを含む可能性があります。しかし、上記のものや頻繁に必要とする他のものよりも、よりシンプルで、よりエレガントで、より短く、または設計、実装、または理解が容易なものquote-ingとeval-ing。

特に、手順がかなり複雑になる可能性がありdata.table、更新された列を繰り返し更新してから参照する必要があるため、lapply(.SD,...), ... .SDcols = ...通常、標準的な方法は実行可能な代替手段ではないことに注意してください。また、各呼び出しを で置き換えるeval(a.column.name)ことは、私が知る限りDT[[a.column.name]]、他の操作とはうまくいかないため、あまり単純化されず、一般的に完全に機能しません。data.table

4

3 に答える 3

4

ご質問ありがとうございます。あなたのオリジナルのアプローチは、ほとんどの問題を解決するのに大いに役立ちます。

ここでは、引用関数を少し調整し、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)]'
  )]
}
于 2015-11-09T04:58:21.163 に答える
3

「これは悪くない」と思ってdata.tableでこれをやろうとしました...しかし、恥ずかしい時間が経った後、私はあきらめました。Matt は「分割してから結合する」のようなことを言っていますが、特に最後の部分は前のステップに依存するため、これらの部分を行うエレガントな方法を理解できませんでした。

これは非常に見事に構成された質問であり、私も同様の問題に頻繁に遭遇します。私は 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]
于 2014-09-17T23:14:05.470 に答える