メモ:カテゴリごとにデータフレーム単位で処理をしたいときはdplyr::do()とsplit() + purrr::map()のどちらが速い

追記(2017/07/26): コメント欄で指摘いただいたのでmap_df()を使わないバージョン(f_split2())を追加しました。これはmap_df()が遅いというより、split()した数だけ新しくtibble()をつくるのでその時間だろうなと推測しています。


ベクトルを引数に取る関数はgroup_by()でいいわけですが、データフレームを引数に取るような関数はどうしますか?

do()しますか?

ダーッ!しますか!?

という話を書いたのがこれでした。

元ネタはこっち:

そういえば速度的にはどうなんだろう?と思ってやってみたらあんまり変わらなかったです。 サイズがでかくなるとほとんど差がなくなるのは、データコピーのコストが時間のほとんどを占めるようになるからだと思われます。 あんまりちゃんと調べてないけど、とりあえずメモ。

library(tidyverse)
#> Loading tidyverse: ggplot2
#> Loading tidyverse: tibble
#> Loading tidyverse: tidyr
#> Loading tidyverse: readr
#> Loading tidyverse: purrr
#> Loading tidyverse: dplyr
#> Conflicts with tidy packages ----------------------------------------------
#> filter(): dplyr, stats
#> lag():    dplyr, stats


f_do <- function(d) {
  d %>%
    group_by(category) %>%
    do(mod = lm(y ~ x, data = .))
}

f_split <- function(d) {
  d %>%
    split(.$category) %>%
    map_df(~ tibble(mod = list(lm(y ~ x, data = .))), .id = "category")
}

f_split2 <- function(d) {
  d %>%
    split(.$category) %>%
    map(~ lm(y ~ x, data = .)) %>%
    tibble(category = names(.), mod = .)
}

f_split_simple <- function(d) {
  d %>%
    split(.$category) %>%
    map(~ lm(y ~ x, data = .))
}

set.seed(8)
d1 <- tibble(
  x = runif(100000),
  y = runif(100000),
  category = sample(letters, size = 100000, replace = TRUE)
)

d2 <- tibble(
  x = runif(10000000),
  y = runif(10000000),
  category = sample(letters, size = 10000000, replace = TRUE)
)

microbenchmark::microbenchmark(f_do(d1), f_split(d1), f_split2(d1), f_split_simple(d1))
#> Unit: milliseconds
#>                expr      min       lq     mean   median       uq      max neval
#>            f_do(d1) 135.6477 149.1095 165.9779 159.3123 172.0475 282.2410   100
#>         f_split(d1) 184.6050 204.1939 233.9930 214.8608 244.8180 488.9205   100
#>        f_split2(d1) 122.3677 134.0607 153.3851 145.7615 164.5424 259.3227   100
#>  f_split_simple(d1) 116.8629 128.8910 145.1609 139.3368 157.9667 220.4889   100
microbenchmark::microbenchmark(f_do(d2), f_split(d2), f_split2(d2), f_split_simple(d2), times = 10)
#>                expr      min       lq     mean   median       uq      max neval
#>            f_do(d2) 11.89219 12.63160 13.12377 13.13746 13.52381 14.39874    10
#>         f_split(d2) 12.19248 12.82875 13.65308 13.52989 14.15879 15.27188    10
#>        f_split2(d2) 12.45489 12.74459 13.38824 12.94578 14.17869 15.09227    10
#>  f_split_simple(d2) 12.24817 12.41317 12.80051 12.66055 13.06307 13.92374    10