Data.table: なぜdata.tableは、リスト列サブセットよりもベクトル化列サブセットの方が高速です

作成日 2019年03月27日  ·  3コメント  ·  ソース: Rdatatable/data.table

私はこのdata.tableのものが好きです。その実行速度と、スクリプトの簡潔な方法が同じです。
小さなテーブルでも使っています。
私は定期的に次のようにテーブルをサブセット化します:DT [、。(id1、id5)]
この方法ではありません:DT [、c( "id1"、 "id5")]

今日、私は2つの速度を測定しましたが、小さなテーブルでの速度の違いに驚いています。 倹約的な方法ははるかに遅いです。

この違いは意図されたものですか?

実行速度の点で他のものに収束するための倹約的な方法を作りたいという願望はありますか?
(いくつかの小さなテーブルを繰り返しサブセット化する必要がある場合にカウントされます。)

Ubuntu 18.04
Rバージョン3.5.3(2019-03-11)
data.table 1.12.0
RAM 32GB
Intel®Core™[email protected]×8

library(data.table)
library(microbenchmark)
N  <- 2e8
K  <- 100
set.seed(1)
DT <- data.table(
  id1 = sample(sprintf("id%03d", 1:K), N, TRUE),               # large groups (char)
  id2 = sample(sprintf("id%03d", 1:K), N, TRUE),               # large groups (char)
  id3 = sample(sprintf("id%010d", 1:(N/K)), N, TRUE),       # small groups (char)
  id4 = sample(K,   N, TRUE),                                           # large groups (int)
  id5 = sample(K,   N, TRUE),                                           # large groups (int)
  id6 = sample(N/K, N, TRUE),                                          # small groups (int)
  v1 =  sample(5,   N, TRUE),                                           # int in range [1,5]
  v2 =  sample(5,   N, TRUE),                                           # int in range [1,5]
  v3 =  sample(round(runif(100, max = 100), 4), N, TRUE) # numeric e.g. 23.5749
)

microbenchmark(
  DT[, .(id1, id5)],
  DT[, c("id1", "id5")]
)
Unit: seconds
                  expr      min       lq     mean   median       uq      max neval
     DT[, .(id1, id5)] 1.588367 1.614645 1.929348 1.626847 1.659698 12.33872   100
 DT[, c("id1", "id5")] 1.592154 1.613800 1.937548 1.628082 2.184456 11.74581   100


N  <- 2e5
DT2 <- data.table(
  id1 = sample(sprintf("id%03d", 1:K), N, TRUE),                 # large groups (char)
  id2 = sample(sprintf("id%03d", 1:K), N, TRUE),                 # large groups (char)
  id3 = sample(sprintf("id%010d", 1:(N/K)), N, TRUE),         # small groups (char)
  id4 = sample(K,   N, TRUE),                                             # large groups (int)
  id5 = sample(K,   N, TRUE),                                             # large groups (int)
  id6 = sample(N/K, N, TRUE),                                            # small groups (int)
  v1 =  sample(5,   N, TRUE),                                             # int in range [1,5]
  v2 =  sample(5,   N, TRUE),                                             # int in range [1,5]
  v3 =  sample(round(runif(100, max = 100), 4), N, TRUE)   # numeric e.g. 23.5749
)

microbenchmark(
  DT2[, .(id1, id5)],
  DT2[, c("id1", "id5")]
)
Unit: microseconds
                   expr      min       lq      mean    median        uq      max neval
DT2[, .(id1, id5)] 1405.042 1461.561 1525.5314 1491.7885 1527.8955 2220.860   100
DT2[, c("id1", "id5")]  614.624  640.617  666.2426  659.0175  676.9355  906.966   100

最も参考になるコメント

コードチャンクの前後に3つのバッククォートを1行使用することで、投稿のフォーマットを修正できます。

```
code
```

いくつかの小さなテーブルを繰り返しサブセット化する必要がある場合にカウントされます。

小さなテーブルから列を繰り返し選択することは、避けるべきであり、ほとんどの場合、避けることができるものだと思います...? DT[i, j, by]jは、このように多種多様な入力をサポートおよび最適化するため、解析にオーバーヘッドがかかるのは当然だと思います。


問題に取り組む他の方法について(そして、もっと話したい場合は、Stack Overflowに適しているかもしれません)...テーブルで他に何をしたいかによっては、他の列を削除することもできます。 、 DT[, setdiff(names(DT), cols) := NULL]そしてDTを直接使用し続けます。

それでもサブセットを取得したい場合は、ここで検討したどちらのオプションよりも列ポインターの取得がはるかに高速ですが、この方法で結果を編集すると、元のテーブルに影響します。

library(data.table)
library(microbenchmark)
N <- 2e8
K <- 100
set.seed(1)
DT <- data.table(
id1 = sprintf("id%03d", 1:K), # large groups (char)
id2 = sprintf("id%03d", 1:K), # large groups (char)
id3 = sprintf("id%010d", 1:(N/K)), # small groups (char)
id4 = sample(K), # large groups (int)
id5 = sample(K), # large groups (int)
id6 = sample(N/K), # small groups (int)
v1 = sample(5), # int in range [1,5]
v2 = sample(5), # int in range [1,5]
v3 = round(runif(100, max = 100), 4), # numeric e.g. 23.5749
row = seq_len(N)
)

cols = c("id1", "id5")
microbenchmark(times = 3,
  expression = DT[, .(id1, id5)],
  index = DT[, c("id1", "id5")],
  dotdot = DT[, ..cols],
  oddball = setDT(lapply(setNames(cols, cols), function(x) DT[[x]]))[],
  oddball2 = setDT(unclass(DT)[cols])[]
)

Unit: microseconds
       expr         min           lq         mean      median           uq         max neval
 expression 1249753.580 1304355.3415 1417166.9297 1358957.103 1500873.6045 1642790.106     3
      index 1184056.302 1191334.4835 1396372.3483 1198612.665 1502530.3715 1806448.078     3
     dotdot 1084521.234 1240062.2370 1439680.6980 1395603.240 1617260.4300 1838917.620     3
    oddball      92.659     171.8635     568.5317     251.068     806.4680    1361.868     3
   oddball2      66.582     125.9505     150.7337     185.319     192.8095     200.300     3

(私はあなたの例からランダム化を取り除いて、せっかちだったのでベンチマークで#回減らしました。)

Rのリストサブセット(上記のunclassの後に使用されます)を直接呼び出す方法を見つけたことがありません。

「結果を編集すると元のテーブルが変更されます」とは、次のことを意味します。

myDT = data.table(a = 1:2, b = 3:4)

# standard way
res <- myDT[, "a"]
res[, a := 0]
myDT
#    a b
# 1: 1 3
# 2: 2 4

# oddball, grabbing pointers
res2 <- setDT(unclass(myDT)["a"])
res2[, a := 0]
myDT
#    a b
# 1: 0 3
# 2: 0 4

全てのコメント3件

コードチャンクの前後に3つのバッククォートを1行使用することで、投稿のフォーマットを修正できます。

```
code
```

いくつかの小さなテーブルを繰り返しサブセット化する必要がある場合にカウントされます。

小さなテーブルから列を繰り返し選択することは、避けるべきであり、ほとんどの場合、避けることができるものだと思います...? DT[i, j, by]jは、このように多種多様な入力をサポートおよび最適化するため、解析にオーバーヘッドがかかるのは当然だと思います。


問題に取り組む他の方法について(そして、もっと話したい場合は、Stack Overflowに適しているかもしれません)...テーブルで他に何をしたいかによっては、他の列を削除することもできます。 、 DT[, setdiff(names(DT), cols) := NULL]そしてDTを直接使用し続けます。

それでもサブセットを取得したい場合は、ここで検討したどちらのオプションよりも列ポインターの取得がはるかに高速ですが、この方法で結果を編集すると、元のテーブルに影響します。

library(data.table)
library(microbenchmark)
N <- 2e8
K <- 100
set.seed(1)
DT <- data.table(
id1 = sprintf("id%03d", 1:K), # large groups (char)
id2 = sprintf("id%03d", 1:K), # large groups (char)
id3 = sprintf("id%010d", 1:(N/K)), # small groups (char)
id4 = sample(K), # large groups (int)
id5 = sample(K), # large groups (int)
id6 = sample(N/K), # small groups (int)
v1 = sample(5), # int in range [1,5]
v2 = sample(5), # int in range [1,5]
v3 = round(runif(100, max = 100), 4), # numeric e.g. 23.5749
row = seq_len(N)
)

cols = c("id1", "id5")
microbenchmark(times = 3,
  expression = DT[, .(id1, id5)],
  index = DT[, c("id1", "id5")],
  dotdot = DT[, ..cols],
  oddball = setDT(lapply(setNames(cols, cols), function(x) DT[[x]]))[],
  oddball2 = setDT(unclass(DT)[cols])[]
)

Unit: microseconds
       expr         min           lq         mean      median           uq         max neval
 expression 1249753.580 1304355.3415 1417166.9297 1358957.103 1500873.6045 1642790.106     3
      index 1184056.302 1191334.4835 1396372.3483 1198612.665 1502530.3715 1806448.078     3
     dotdot 1084521.234 1240062.2370 1439680.6980 1395603.240 1617260.4300 1838917.620     3
    oddball      92.659     171.8635     568.5317     251.068     806.4680    1361.868     3
   oddball2      66.582     125.9505     150.7337     185.319     192.8095     200.300     3

(私はあなたの例からランダム化を取り除いて、せっかちだったのでベンチマークで#回減らしました。)

Rのリストサブセット(上記のunclassの後に使用されます)を直接呼び出す方法を見つけたことがありません。

「結果を編集すると元のテーブルが変更されます」とは、次のことを意味します。

myDT = data.table(a = 1:2, b = 3:4)

# standard way
res <- myDT[, "a"]
res[, a := 0]
myDT
#    a b
# 1: 1 3
# 2: 2 4

# oddball, grabbing pointers
res2 <- setDT(unclass(myDT)["a"])
res2[, a := 0]
myDT
#    a b
# 1: 0 3
# 2: 0 4

わかりました。今日、私は何か新しくてスピーディーなもの(奇妙なボール)を学びました。スピードと倹約的なコーディングの間にはトレードオフがあることに注意しています。 だからガラスは半分いっぱいです! ありがとう!

#852関連だと思います

このページは役に立ちましたか?
0 / 5 - 0 評価