2022-07-23 第100回R勉強会@東京
@eitsupi
rocker/r-ver
他のメンテナー巨大なデータを扱うときにはCSVではなくParquetを使うと便利です。
dplyr
バックエンドの速度比較をやってみた。dtplyr
の日本語の情報が少ないので共有したい。
dplyr
派dbplyr
やdtplyr
を試す機会のなかった方data.table
派Q. data.table
って速いの?
A. dtplyr
ですぐに試せるのでやってみましょう!
(tidyverse
パッケージインストール時にインストールされてます!)
dtplyr
, arrow
, duckdb
dplyr
バックエンド達dplyr
で記述したデータ操作をdplyr
外で実行するパッケージ。
multidplyr
: Rの計算を分散dtplyr
: data.table
のクエリに変換して計算実行dbplyr
: duckdb
などのDBにSQLを送信して計算実行R version 4.2.1 (2022-06-23)
Platform: x86_64-pc-linux-gnu (64-bit)
Running under: Ubuntu 20.04.4 LTS
Matrix products: default
BLAS: /usr/lib/x86_64-linux-gnu/openblas-pthread/libblas.so.3
LAPACK: /usr/lib/x86_64-linux-gnu/openblas-pthread/liblapack.so.3
locale:
[1] LC_CTYPE=en_US.UTF-8 LC_NUMERIC=C
[3] LC_TIME=en_US.UTF-8 LC_COLLATE=en_US.UTF-8
[5] LC_MONETARY=en_US.UTF-8 LC_MESSAGES=en_US.UTF-8
[7] LC_PAPER=en_US.UTF-8 LC_NAME=C
[9] LC_ADDRESS=C LC_TELEPHONE=C
[11] LC_MEASUREMENT=en_US.UTF-8 LC_IDENTIFICATION=C
attached base packages:
[1] stats graphics grDevices utils datasets methods base
other attached packages:
[1] duckdb_0.4.0 DBI_1.1.3 arrow_8.0.0 dtplyr_1.2.1 dplyr_1.0.9
loaded via a namespace (and not attached):
[1] knitr_1.39 magrittr_2.0.3 bit_4.0.4 tidyselect_1.1.2
[5] R6_2.5.1 rlang_1.0.3 fastmap_1.1.0 fansi_1.0.3
[9] stringr_1.4.0 tools_4.2.1 data.table_1.14.2 xfun_0.31
[13] utf8_1.2.2 cli_3.3.0 htmltools_0.5.2 ellipsis_0.3.2
[17] bit64_4.0.5 assertthat_0.2.1 yaml_2.3.5 digest_0.6.29
[21] tibble_3.1.7 lifecycle_1.0.1 crayon_1.5.1 purrr_0.3.4
[25] codetools_0.2-18 vctrs_0.4.1 glue_1.6.2 evaluate_0.15
[29] rmarkdown_2.14 stringi_1.7.6 compiler_4.2.1 pillar_1.7.0
[33] generics_0.1.3 jsonlite_1.8.0 pkgconfig_2.0.3
行数、列数、グループ数を変えてベンチマークするために、
データフレームを以下のような関数で作れるようにしておきます。
.gen_data <- \(n_group, n_row, n_col_value, .seed = 1) {
groups <- seq_len(n_group) |>
rep_len(n_row) |>
as.character()
set.seed(.seed)
runif(n_row * n_col_value, min = 0, max = 100) |>
round() |>
matrix(ncol = n_col_value) |>
tibble::as_tibble(
.name_repair = \(x) paste0("col_value_", seq_len(n_col_value))
) |>
dplyr::mutate(col_group = groups, .before = 1)
}
各パッケージによる計算も関数化しておきます。
二つの関数が同じ結果を返すことを確認します。
各パッケージによる計算も関数化しておきます。
summarise
)行数3条件、グループ数3条件の組み合わせ全9条件で、
bench
パッケージによるベンチマークを取ります。
res_sum <- bench::press(
fn = c("dplyr::summarise"),
n_row = c(1e6, 1e7, 1e8),
n_col_value = c(1),
n_group = c(1e2, 1e3, 1e4),
{
dat <- .gen_data(n_group, n_row, n_col_value)
fn <- eval(parse(text = fn))
bench::mark(
check = dplyr::all_equal,
min_iterations = 5,
dplyr = .use_dplyr(dat, fn),
dtplyr = .use_dtplyr(dat, fn),
arrow = .use_arrow(dat, fn),
duckdb = .use_duckdb(dat, fn)
)
}
)
summarise
)dtplyr
速い!
mutate
)arrow
はgroupに対するmutate
非対応、
duckdb
のgroup
に対するmutate
は現状とても遅くベンチマークが終わらなかったので省略。
dplyr
とdtplyr
ほぼ互角。
summarise
+ across
)以下のような、across
で複数列を対象にする集約計算について、
列数を変えながらベンチマークを取ってみましょう。
.use_across_dplyr <-
function(.data, .fn = dplyr::summarise) {
.data |>
dplyr::group_by(col_group) |>
.fn(
dplyr::across(
tidyselect::starts_with("col_value"),
.fns = ~ sum(.x, na.rm = TRUE)
)
)
}
arrow
は現状across
未対応なので使えません。
summarise
+ across
)更に行数を増やすとduckdb
が最速になりそうに見えますが、私のマシン(RAM16GB割り当て)ではこれ以上のサイズでの実行を完了できず……。
まとめきれなかったもののベンチマーク色々回してて気付いた結果。
dtplyr
のdplyr::summarise()
は列(計算対象外)が増えるだけで遅くなる事象を確認しました。不要な列はあらかじめdplyr::select()
で削除する方が良いかも知れません。
tidyr::pivot_longer()
はdplyr
が速かったです。
↓みたいなこともできるので組み合わせて使いましょう!
arrow::Table
をdtplyr::lazy_dt()
に渡せないバグを見つけたので修正しました。data.table
はマジで速い!dtplyr
で敷居も低い!Enjoy!
#TokyoR