do()とかrowwise()は今から覚える必要はない(たぶん)

追記(2020/07/04): この記事の予想は外れ、 rowwise() は dplyr 1.0.0 で華麗な復活を遂げました。


追記(2017/11/17): RStudio Communityで質問してみたところ、「もう機能追加されないしドキュメントでも言及されないけど、まあ数年は残るんじゃね?」というのがHadleyの回答でした。

てことで、いまdo()を使いこなしてる人はそんなに急いで他に移動する必要はなさそうです。必要以上に不安を煽ってしまったようですみません。。


まず知ってほしいのは、do()rowwise()は、ドキュメントに明記はされていないものの、将来的に捨てられる線が濃厚だということです。

I don't think we should invest further development time into do.
(https://github.com/tidyverse/dplyr/issues/2970#issuecomment-341538148)

rowwise() is not something I believe is a good idea anymore, so it is not being actively developed. (https://github.com/tidyverse/dplyr/issues/3144#issuecomment-341530896)

すぐ消えたりはしないので、いま十分使いこなしているならすぐに捨て去る必要はないでしょう。でもまあ、あんまり依存しすぎるとあれなので、できれば違うやり方を覚えましょう。

do()の代替手段

データ内の列を使ってごにょごにょ計算するのはmutate()summarise()で代替できますが、data.frame全体を引数に取るような関数(lm()とか)を扱う場合は不便です。do()はそういう場合に使うものです。

ドキュメントに載ってる例からひとつ挙げると、各グループから上位2行を取ってくる、という処理はこんな感じで書けます。

library(dplyr, warn.conflicts = FALSE)

mtcars %>%
  group_by(cyl) %>%
  do(head(., 2))
#> # A tibble: 6 x 11
#> # Groups:   cyl [3]
#>     mpg   cyl  disp    hp  drat    wt  qsec    vs    am  gear  carb
#>   <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
#> 1  22.8     4 108.0    93  3.85 2.320 18.61     1     1     4     1
#> 2  24.4     4 146.7    62  3.69 3.190 20.00     1     0     4     2
#> 3  21.0     6 160.0   110  3.90 2.620 16.46     0     1     4     4
#> 4  21.0     6 160.0   110  3.90 2.875 17.02     0     1     4     4
#> 5  18.7     8 360.0   175  3.15 3.440 17.02     0     0     3     2
#> 6  14.3     8 360.0   245  3.21 3.570 15.84     0     0     3     4

が、purrrがある今、こんなことをしなくてもsplit()で直接分割してしまえば同じことができます。

library(purrr)

mtcars %>% 
  split(.$cyl) %>%
  map_dfr(head, n = 2)
#>    mpg cyl  disp  hp drat    wt  qsec vs am gear carb
#> 1 22.8   4 108.0  93 3.85 2.320 18.61  1  1    4    1
#> 2 24.4   4 146.7  62 3.69 3.190 20.00  1  0    4    2
#> 3 21.0   6 160.0 110 3.90 2.620 16.46  0  1    4    4
#> 4 21.0   6 160.0 110 3.90 2.875 17.02  0  1    4    4
#> 5 18.7   8 360.0 175 3.15 3.440 17.02  0  0    3    2
#> 6 14.3   8 360.0 245 3.21 3.570 15.84  0  0    3    4

あるいは、tidyr::nest()でリスト列に入れてしまって、それをmutate()するという手もあります。

library(tidyr)
library(dplyr, warn.conflicts = FALSE)

mtcars_nest <- mtcars %>%
  group_by(cyl) %>%
  nest(.key = "df")

mtcars_nest
#> # A tibble: 3 x 2
#>     cyl                 df
#>   <dbl>             <list>
#> 1     6  <tibble [7 x 10]>
#> 2     4 <tibble [11 x 10]>
#> 3     8 <tibble [14 x 10]>

mtcars_nest %>%
  mutate(df = lapply(df, head, n = 2)) %>%
  unnest(df)
#> # A tibble: 6 x 11
#>     cyl   mpg  disp    hp  drat    wt  qsec    vs    am  gear  carb
#>   <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
#> 1     6  21.0 160.0   110  3.90 2.620 16.46     0     1     4     4
#> 2     6  21.0 160.0   110  3.90 2.875 17.02     0     1     4     4
#> 3     4  22.8 108.0    93  3.85 2.320 18.61     1     1     4     1
#> 4     4  24.4 146.7    62  3.69 3.190 20.00     1     0     4     2
#> 5     8  18.7 360.0   175  3.15 3.440 17.02     0     0     3     2
#> 6     8  14.3 360.0   245  3.21 3.570 15.84     0     0     3     4

dplyrでやると途中のデータ形式も必ずdata.frameに縛られてしまうので、purrrの方が個人的にはおすすめです。

rowwise()の代替手段

rowwise() は、do()と組み合わせて使うもので、1行ずつ処理することができます。

これもドキュメントに載ってるやつから例を拾うと、seq()に渡すパラメータの組み合わせをdata.frameとして用意しておいて、それを使うような場合です。

library(dplyr, warn.conflicts = FALSE)

df <- expand.grid(x = 1:3, y = 3:1)
df
#>   x y
#> 1 1 3
#> 2 2 3
#> 3 3 3
#> 4 1 2
#> 5 2 2
#> 6 3 2
#> 7 1 1
#> 8 2 1
#> 9 3 1

df %>%
  rowwise() %>%
  do(i = seq(.$x, .$y))
#> Source: local data frame [9 x 1]
#> Groups: <by row>
#> 
#> # A tibble: 9 x 1
#>           i
#> *    <list>
#> 1 <int [3]>
#> 2 <int [2]>
#> 3 <int [1]>
#> 4 <int [2]>
#> 5 <int [1]>
#> 6 <int [2]>
#> 7 <int [1]>
#> 8 <int [2]>
#> 9 <int [3]>

これも、purrrで解決できます。pmap()を使いましょう。

df %>%
  pmap(~ seq(.x, .y))
#> [[1]]
#> [1] 1 2 3
#> 
#> [[2]]
#> [1] 2 3
#> 
#> [[3]]
#> [1] 3
#> 
#> [[4]]
#> [1] 1 2
#> 
#> [[5]]
#> [1] 2
#> 
#> [[6]]
#> [1] 3 2
#> 
#> [[7]]
#> [1] 1
#> 
#> [[8]]
#> [1] 2 1
#> 
#> [[9]]
#> [1] 3 2 1

元のデータフレームに含めたいときはmutate()を使いましょう。pmap()の第一引数に.(data.frame全体)を渡さないといけない点に注意。

df %>%
  mutate(result = pmap(., ~ seq(.x, .y)))
#>   x y  result
#> 1 1 3 1, 2, 3
#> 2 2 3    2, 3
#> 3 3 3       3
#> 4 1 2    1, 2
#> 5 2 2       2
#> 6 3 2    3, 2
#> 7 1 1       1
#> 8 2 1    2, 1
#> 9 3 1 3, 2, 1

まとめ

どうでしょうか。do()rowwise()を使わなくても生きていけそうですか? 今は、dplyrとpurrrの役割分担の過渡期なので、正直むずかしいこともあるのかもしれません。こういうときどうするの??というのがあればr-wakalangまで質問をぜひ!