35

割り当てを使用して、共有関数を適用することにより、:=一度に多くの列を置き換えるエレガントな方法を見つけようとしています。data.tableこれの典型的な使用法はgsub、テーブル内のすべての文字列に文字列関数 (例: ) を適用することです。data.frameこれを行う方法をに拡張することは難しくありませんがdata.table、物事の方法と一致する方法を探してdata.tableいます。

例えば:

library(data.table)

m <- matrix(runif(10000), nrow = 100)
df <- df1 <- df2 <- df3 <- as.data.frame(m)
dt <- as.data.table(df)
head(names(df))
head(names(dt))

## replace V20-V100 with sqrt

# data.frame approach
# by column numbers
df1[20:100] <- lapply(df1[20:100], sqrt)
# by reference to column numbers
v <- 20:100
df2[v] <- lapply(df2[v], sqrt)
# by reference to column names
n <- paste0("V", 20:100)
df3[n] <- lapply(df3[n], sqrt)

# data.table approach
# by reference to column names
n <- paste0("V", 20:100)
dt[, n] <- lapply(dt[, n, with = FALSE], sqrt)

:=割り当てを使用して列名のベクトルをループする方が効率的であることを理解しています:

for (col in paste0("V", 20:100)) dt[, col := sqrt(dt[[col]]), with = FALSE]

data.table式で参照するのが好きではないので、これは好きではありませんj。また、列名を知って:=いれば、割り当てに使用できることも知っています。lapply

dt[, c("V20", "V30", "V40", "V50", "V60") := lapply(list(V20, V30, V40, V50, V60), sqrt)]

(列名が不明な式を作成することで、これを拡張できます。)

以下は、私がこれについて試したアイデアですが、それらを機能させることができませんでした。私は間違いを犯していますか、それとも私が見逃している別のアプローチがありますか?

# possible data.table approaches?
# by reference to column names; assignment works, but not lapply
n <- paste0("V", 20:100)
dt[, n := lapply(n, sqrt), with = FALSE]
# by (smaller for example) list; lapply works, but not assignment
dt[, list(list(V20, V30, V40, V50, V60)) := lapply(list(V20, V30, V40, V50, V60), sqrt)]
# by reference to list; neither assignment nor lapply work
l <- parse(text = paste("list(", paste(paste0("V", 20:100), collapse = ", "), ")"))
dt[, eval(l) := lapply(eval(l), sqrt)]
4

3 に答える 3

43

はい、あなたはここで問題になっています:

:=割り当てを使用して列名のベクトルをループする方が効率的であることを理解しています:

for (col in paste0("V", 20:100)) dt[, col := sqrt(dt[[col]]), with = FALSE]

余談ですが、それを行う新しい方法は次のとおりです。

for (col in paste0("V", 20:100))
  dt[ , (col) := sqrt(dt[[col]])]

with = FALSEは、 の左辺または右辺のどちらを参照しているかを読み取るのが容易ではなかったからです:=。脇に終わります。

ご存知のように、これは各列を 1 つずつ処理するため効率的です。そのため、ワーキング メモリは一度に 1 列だけ必要になります。これにより、動作するか、恐ろしいメモリ不足エラーで失敗するかの違いが生じる可能性があります。

lapplyのRHSの問題:=は、RHS ( lapply) が最初に評価されることです。つまり、80 列の結果が作成されます。これは、80 列に相当する新しいメモリを割り当てて設定する必要があります。したがって、その操作を成功させるには、80 列分の空き RAM が必要です。その RAM の使用量は、80 個の新しい列を data.table の列ポインター スロットに割り当てる ( plonking ) というその後の瞬時の操作に対して支配的です。

@Frank が指摘したように、多数の列 (たとえば 10,000 以上) がある場合、[.data.tableメソッドへのディスパッチの小さなオーバーヘッドが加算され始めます)。そのオーバーヘッドを排除するために、「ループ可能」として説明されていdata.table::setます。このタイプの操作にはループを使用します。これは最速の方法であり、書き込みと読み取りがかなり簡単です。?set:=for

for (col in paste0("V", 20:100))
  set(dt, j = col, value = sqrt(dt[[col]]))

わずか 80 列ですが、問題になることはほとんどありません。set(多数の列よりも多数の行をループする方が一般的であることに注意してください。)ただし、ループしても、質問で言及したシンボル名setへの繰り返し参照の問題は解決しません。dt

aj式でdata.tableを参照するのが好きではないので、これは好きではありません。

同意した。したがって、私ができる最善のことは、ループの代わりに:=使用することです。get

for (col in paste0("V", 20:100))
  dt[, (col) := sqrt(get(col))]

ただし、キャリーを使用するとオーバーヘッドが発生するのgetではないかと心配しています。#1380jで行われたベンチマーク。また、おそらく、RHS で使用するのは混乱を招きますが、LHS では使用できません。LHS をシュガーして、同様に許可できることに対処するには、#1381 :get()get()

for (col in paste0("V", 20:100))
  dt[, get(col) := sqrt(get(col))]

また、# 1382valuesetスコープ内で of を実行することもできます。DT

for (col in paste0("V", 20:100))
  set(dt, j = col, value = sqrt(get(col))
于 2015-10-07T19:25:52.707 に答える
8

これはあなたが探しているものですか?

dt[ , names(dt)[20:100] :=lapply(.SD, function(x) sqrt(x) ) , .SDcols=20:100]

.SDusingは事前にテーブルのコピーを作成するためあまり効率的ではないと聞いたことがありますが、テーブルが大きくない場合(明らかに、システムの仕様によっては相対的です)、大きな違いが生じるとは思えません。

于 2013-06-05T15:49:21.517 に答える