私は同様のタスクを1行にまとめる癖があります。例えば、私は上のフィルタに必要がある場合はa
、b
、およびc
データテーブルに、私は1つにそれらを一緒に出してあげる[]
論理積を持ちます。昨日、私は特定のケースではこれが信じられないほど遅く、代わりにチェイニングフィルターをテストしたことに気付きました。以下に例を示します。
まず、乱数ジェネレータをシードし、data.tableをロードして、ダミーデータセットを作成します。
# Set RNG seed
set.seed(-1)
# Load libraries
library(data.table)
# Create data table
dt <- data.table(a = sample(1:1000, 1e7, replace = TRUE),
b = sample(1:1000, 1e7, replace = TRUE),
c = sample(1:1000, 1e7, replace = TRUE),
d = runif(1e7))
次に、メソッドを定義します。最初のアプローチでは、フィルターを連鎖させます。2番目は、フィルターをAND演算します。
# Chaining method
chain_filter <- function(){
dt[a %between% c(1, 10)
][b %between% c(100, 110)
][c %between% c(750, 760)]
}
# Anding method
and_filter <- function(){
dt[a %between% c(1, 10) & b %between% c(100, 110) & c %between% c(750, 760)]
}
ここでは、同じ結果になることを確認します。
# Check both give same result
identical(chain_filter(), and_filter())
#> [1] TRUE
最後に、私はそれらをベンチマークします。
# Benchmark
microbenchmark::microbenchmark(chain_filter(), and_filter())
#> Unit: milliseconds
#> expr min lq mean median uq max
#> chain_filter() 25.17734 31.24489 39.44092 37.53919 43.51588 78.12492
#> and_filter() 92.66411 112.06136 130.92834 127.64009 149.17320 206.61777
#> neval cld
#> 100 a
#> 100 b
reprexパッケージ(v0.3.0)によって2019-10-25に作成されました
この場合、チェーンすると実行時間が約70%短縮されます。これはなぜですか?つまり、データテーブルの内部では何が起こっているのでしょうか。の使用に対する警告は何も見ていなかった&
ので、違いが非常に大きいことに驚きました。どちらの場合も同じ条件を評価するため、違いはありません。ANDの場合、&
は迅速な演算子であり、チェーンの場合は3回フィルタリングするのではなく、データテーブルを1回(つまり、ANDの結果の論理ベクトルを使用して)フィルタリングするだけで済みます。
ボーナス質問
この原則は、データテーブル操作全般に当てはまりますか?タスクのモジュール化は常により良い戦略ですか?
base
次の作業を実行して、ベクターを用いて観察を: chain_vec <- function() { x <- which(a < .001); x[which(b[x] > .999)] }
とand_vec <- function() { which(a < .001 & b > .999) }
。(ここでa
、およびb
は同じ長さのベクトルですrunif
- n = 1e7
これらのカットオフに使用しました)。