data.table内の指定されたすべての列に同じ関数を適用する方法


86

特定の列で同じ操作を実行したいdata.tableがあります。これらの列の名前は文字ベクトルで示されます。この特定の例では、これらすべての列に-1を掛けたいと思います。

いくつかのおもちゃのデータと関連する列を指定するベクトル:

library(data.table)
dt <- data.table(a = 1:3, b = 1:3, d = 1:3)
cols <- c("a", "b")

今、私はそれをこのようにして、文字ベクトルをループしています:

for (col in 1:length(cols)) {
   dt[ , eval(parse(text = paste0(cols[col], ":=-1*", cols[col])))]
}

forループなしでこれを直接行う方法はありますか?

回答:


151

これはうまくいくようです:

dt[ , (cols) := lapply(.SD, "*", -1), .SDcols = cols]

結果は

    a  b d
1: -1 -1 1
2: -2 -2 2
3: -3 -3 3

ここにはいくつかのトリックがあります:

  • には括弧があるため(cols) :=、結果はcols「cols」という名前の新しい変数ではなく、で指定された列に割り当てられます。
  • .SDcolsこれらの列のみを表示していることを呼び出しに通知し、これらの列に関連付けられているata.SDSubsetを使用できるようにしDます。
  • lapply(.SD, ...).SD(すべてのdata.framesやdata.tablesのように)列のリストである、で動作します。lapplyリストを返すので、最終的にjはのようになりcols := list(...)ます。

編集:@Arunが述べたように、おそらくより速い別の方法があります:

for (j in cols) set(dt, j = j, value = -dt[[j]])

22
別の方法は、で使用setすることfor-loopです。もっと速くなると思います。
アルン2013年

3
@Arun編集しました。それはあなたが意味したことですか?今まで使ったことがありませんset
フランク

8
+1素晴らしい答え。はい、私もこのような場合のforループを好みsetます。
Matt Dowle 2013年

2
はい、使用set()は高速のようです。私のデータセットでは最大4倍高速です。すごい。
Konstantinos

2
ありがとう、@ JamesHirschorn。よくわかりませんが、イントロビネットgithub.com/Rdatatable/data.table/wiki/Getting-startedに表示される標準のイディオムである.SDを使用するよりも、その方法で列をサブセット化する方がオーバーヘッドが大きいと思われます。このイディオムの理由の一部は、テーブル名を2回入力しないようにするためだと思います。
フランク

20

列の名前も変更したい場合は、回答を追加したいと思います。これは、複数の列の対数を計算する場合に非常に便利です。これは、経験的な作業でよくあることです。

cols <- c("a", "b")
out_cols = paste("log", cols, sep = ".")
dt[, c(out_cols) := lapply(.SD, function(x){log(x = x, base = exp(1))}), .SDcols = cols]

1
ルールに基づいて名前を変更する方法はありますか?たとえば、dplyrでは、iris%>%mutate_at(vars(matches( "Sepal"))、list(times_two =〜。* 2))を実行すると、新しい名前に「_times_two」が追加されます。
kennyB

1
私はそれが可能だとは思いませんが、それについてはよくわかりません。
hannes1 0119

これにより、の名前の列が追加されますが、そのout_colsままにcolsしておきます。したがって、明示的に1)log.aとlog.bのみを要求することによってそれらを排除する必要があります。aを[,.(outcols)]最後までチェーンし、dtviaに再保存し<-ます。2)チェーンされた古い列を削除します[,c(cols):=NULL]。非連鎖溶液3)がされdt[,c(cols):=...]、続いてsetnames(dt, cols, newcols)
mpag

@mpag、はい、それは本当ですが、実証研究の私のユースケースでは、ほとんどの場合、データセットに両方のシリーズが必要です。
hannes1 0119

11

更新:以下は、forループなしでそれを行うためのきちんとした方法です

dt[,(cols):= - dt[,..cols]]

これは、コードを読みやすくするための優れた方法です。しかし、パフォーマンスに関しては、以下のマイクロベンチマークの結果によると、フランクのソリューションに遅れをとっています。

mbm = microbenchmark(
  base = for (col in 1:length(cols)) {
    dt[ , eval(parse(text = paste0(cols[col], ":=-1*", cols[col])))]
  },
  franks_solution1 = dt[ , (cols) := lapply(.SD, "*", -1), .SDcols = cols],
  franks_solution2 =  for (j in cols) set(dt, j = j, value = -dt[[j]]),
  hannes_solution = dt[, c(out_cols) := lapply(.SD, function(x){log(x = x, base = exp(1))}), .SDcols = cols],
  orhans_solution = for (j in cols) dt[,(j):= -1 * dt[,  ..j]],
  orhans_solution2 = dt[,(cols):= - dt[,..cols]],
  times=1000
)
mbm

Unit: microseconds
expr                  min        lq      mean    median       uq       max neval
base_solution    3874.048 4184.4070 5205.8782 4452.5090 5127.586 69641.789  1000  
franks_solution1  313.846  349.1285  448.4770  379.8970  447.384  5654.149  1000    
franks_solution2 1500.306 1667.6910 2041.6134 1774.3580 1961.229  9723.070  1000    
hannes_solution   326.154  405.5385  561.8263  495.1795  576.000 12432.400  1000
orhans_solution  3747.690 4008.8175 5029.8333 4299.4840 4933.739 35025.202  1000  
orhans_solution2  752.000  831.5900 1061.6974  897.6405 1026.872  9913.018  1000

下のチャートに示すように

performance_comparison_chart

私の以前の回答:以下も機能します

for (j in cols)
  dt[,(j):= -1 * dt[,  ..j]]

これは基本的に1年半前のフランクの答えと同じです。
Dean MacGregor 2018

1
おかげで、フランクの答えはセットを使用していました。数百万行の大きなdata.tableを操作すると、:=演算子が関数よりも優れていることがわかります
Orhan Celik 2018

2
私が古い質問に答えを追加した理由は次のとおりです:私も同様の問題を抱えていました、私はグーグル検索でこの投稿に出くわしました。その後、私は自分の問題の解決策を見つけました、そしてそれはここにも当てはまると思います。実際、私の提案では、質問の時点では存在していなかった、ライブラリの新しいバージョンで使用できるdata.tableの新しい関数を使用しています。共有するのは良い考えだと思いました。同様の問題を抱えている他の人がここでグーグル検索をすることになると思います。
Orhan Celik 2018

1
dt3行で構成されるベンチマークを行っていますか?
Uwe 2018

3
ハネスの答えは別の計算をしているので、他のものと比較すべきではありませんよね?
フランク

2

上記の解決策はいずれも、グループごとの計算では機能しないようです。以下は私が得た最高のものです:

for(col in cols)
{
    DT[, (col) := scale(.SD[[col]], center = TRUE, scale = TRUE), g]
}

1

列の文字列ベクトルに基づいて新しい列を作成する例を追加します。Jflyの回答に基づく:

dt <- data.table(a = rnorm(1:100), b = rnorm(1:100), c = rnorm(1:100), g = c(rep(1:10, 10)))

col0 <- c("a", "b", "c")
col1 <- paste0("max.", col0)  

for(i in seq_along(col0)) {
  dt[, (col1[i]) := max(get(col0[i])), g]
}

dt[,.N, c("g", col1)]

0
library(data.table)
(dt <- data.table(a = 1:3, b = 1:3, d = 1:3))

Hence:

   a b d
1: 1 1 1
2: 2 2 2
3: 3 3 3

Whereas (dt*(-1)) yields:

    a  b  d
1: -1 -1 -1
2: -2 -2 -2
3: -3 -3 -3

1
Fyi、タイトルの「すべての指定された列」は、質問者がそれを列のサブセット(おそらくすべてではない)に適用することに興味を持っていたことを意味しました。
フランク

1
@フランク確かに!その場合、OPはdt [、c( "a"、 "b")] *(-1)を実行できます。
amonk

1
さて、完全に言ってみましょうdt[, cols] <- dt[, cols] * (-1)
グレゴールトーマス

DT [、..cols] *(-1) -新しい構文が必要なように、<[COLS、] dtがあるようだ
アーサー・イップ

0

dplyr関数はdata.tablesで機能するので、これdplyrも「forループを回避する」ソリューションです:)

dt %>% mutate(across(all_of(cols), ~ -1 * .))

私は(行と列を追加すること)オルハンのコードを使用して、それをベンチマークし、あなたが表示されますdplyr::mutateacross、主に速く、他のソリューションとlapply使用data.tableソリューションより遅いのほとんどよりも実行されます。

library(data.table); library(dplyr)
dt <- data.table(a = 1:100000, b = 1:100000, d = 1:100000) %>% 
  mutate(a2 = a, a3 = a, a4 = a, a5 = a, a6 = a)
cols <- c("a", "b", "a2", "a3", "a4", "a5", "a6")

dt %>% mutate(across(all_of(cols), ~ -1 * .))
#>               a       b      d      a2      a3      a4      a5      a6
#>      1:      -1      -1      1      -1      -1      -1      -1      -1
#>      2:      -2      -2      2      -2      -2      -2      -2      -2
#>      3:      -3      -3      3      -3      -3      -3      -3      -3
#>      4:      -4      -4      4      -4      -4      -4      -4      -4
#>      5:      -5      -5      5      -5      -5      -5      -5      -5
#>     ---                                                               
#>  99996:  -99996  -99996  99996  -99996  -99996  -99996  -99996  -99996
#>  99997:  -99997  -99997  99997  -99997  -99997  -99997  -99997  -99997
#>  99998:  -99998  -99998  99998  -99998  -99998  -99998  -99998  -99998
#>  99999:  -99999  -99999  99999  -99999  -99999  -99999  -99999  -99999
#> 100000: -100000 -100000 100000 -100000 -100000 -100000 -100000 -100000

library(microbenchmark)
mbm = microbenchmark(
  base_with_forloop = for (col in 1:length(cols)) {
    dt[ , eval(parse(text = paste0(cols[col], ":=-1*", cols[col])))]
  },
  franks_soln1_w_lapply = dt[ , (cols) := lapply(.SD, "*", -1), .SDcols = cols],
  franks_soln2_w_forloop =  for (j in cols) set(dt, j = j, value = -dt[[j]]),
  orhans_soln_w_forloop = for (j in cols) dt[,(j):= -1 * dt[,  ..j]],
  orhans_soln2 = dt[,(cols):= - dt[,..cols]],
  dplyr_soln = (dt %>% mutate(across(all_of(cols), ~ -1 * .))),
  times=1000
)

library(ggplot2)
ggplot(mbm) +
  geom_violin(aes(x = expr, y = time)) +
  coord_flip()

reprexパッケージ(v0.3.0)によって2020-10-16に作成されました

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.