『RユーザのためのRStudio[実践]入門』の改訂2版が出ます。

『RユーザのためのRStudio[実践]入門』(通称「宇宙本」)の改訂版が出ます。 私は引き続きdplyr/tidyrの章と、追加された付録の片方(lubridateについて)を担当しています。 カバーも装い新たになったので、愛称は#宇宙船本になりました(まあ好きに呼んでもらえれば大丈夫です)。

全体の流れは変わっていないので、そのへんの説明は初版の時に書いたブログをご参照ください。 今回は、自分の担当箇所の中で変わった部分を中心に書こうと思います。

新しく書いたこと

tidyrパッケージのさまざまな関数

初版ではtidyrはgather()spread()に触れるのみでしたが、 2版ではextract()fill()complete()などの関数についても説明しています。 見どころはこの問題にtidyrでどう立ち向かうかです。

lubridateパッケージ

付録として、lubridateパッケージによる日付・時刻データについて書きました。 ここではパッケージの基本的な使い方から始まり、タイムゾーンの扱いの注意点などについても触れています。 また、やや応用的なトピックとして、私がzipanguパッケージの曜日計算を高速化した時のコードや、

sliderパッケージを使った移動平均の計算についても軽く触れています(ほんとに軽く)。

加筆修正したこと

pivot_longer(), pivot_wider()

初版ではgather()/spread()でしたが、pivot_longer()pivot_wider()で書き直しました。

across()

初版はまだdplyr 0.xの時代だったので、複数列への操作はscoped variants(_if() / _at() / _all())を使っていましたが、 2版ではばっちりacross()を使っています。

across()は便利ですが、なんとなくで使うと事故が起こることもあります、という細かい点にも触れています。 たとえば、以下のコードは、数値の列それぞれのmean()sd()を知りたいというコードですが、 mean_*列・sd_*列に加えて、sd_mean_*というNAが入っているだけの謎の列ができてしまっています。 なぜなのか、また、これを防ぐためにはどうすればいいかわかるでしょうか?

library(dplyr, warn.conflicts = FALSE)

mtcars %>%
  summarise(
    across(where(is.numeric), mean, .names = "mean_{col}"),
    across(where(is.numeric), sd, .names = "sd_{col}")
  )
#>   mean_mpg mean_cyl mean_disp  mean_hp mean_drat mean_wt mean_qsec mean_vs
#> 1 20.09062   6.1875  230.7219 146.6875  3.596563 3.21725  17.84875  0.4375
#>   mean_am mean_gear mean_carb   sd_mpg   sd_cyl  sd_disp    sd_hp   sd_drat
#> 1 0.40625    3.6875    2.8125 6.026948 1.785922 123.9387 68.56287 0.5346787
#>       sd_wt  sd_qsec     sd_vs     sd_am   sd_gear sd_carb sd_mean_mpg
#> 1 0.9784574 1.786943 0.5040161 0.4989909 0.7378041  1.6152          NA
#>   sd_mean_cyl sd_mean_disp sd_mean_hp sd_mean_drat sd_mean_wt sd_mean_qsec
#> 1          NA           NA         NA           NA         NA           NA
#>   sd_mean_vs sd_mean_am sd_mean_gear sd_mean_carb
#> 1         NA         NA           NA           NA

Created on 2021-05-10 by the reprex package (v2.0.0)

summarise()

dplyr 1.0のリリース直後はやや混乱がありましたが、summarise().groups引数(とりあえず.groups = "drop"を指定しておけば安心)や、 長さ1以上の結果も使えるようになったことを書きました。

参考:

select()の列指定

最近は列の除外には-ではなくて!を使うようになったので、本書のコードもこちらにあわせました。

参考:

tidy evalについて

初版ではtidy eval(!!)について説明していましたが、 最近はtidy evalをなるべく使わない方向になっているので、それを反映して最小限の説明にしました。 .dataは出てきますが{{ }}については省略しています。

書かなかったこと

|>

パイプ演算子は、R 4.1で入る|>については特に触れず、magrittrの%>%のままとしました。 |>はまだ実験段階なので、本に書くほどには仕様が定まってなさそう、という保守的な判断です。

pivot_*()の細かな使い方

ここは迷ったんですが、tidy data関連の変形は概念として少し難しいことと(特に複数列を同時に操作するようなパターン)、 流れ的に入れづらかったので基本的な使い方以外は省略しています。

参考:

purrrパッケージ、データフレームのネスト

purrrについては、初心者には難しそう、という理由で引き続き内容に含めていません(異論は認めます)。 データフレームのネストも、その後にpurrr的な操作が必要になるのでちょっと難しいと判断しました。

あと、dplyr 1.0.0で追加されたgroup_nest()group_map()がいまいち盛り上がってるように見えない、 というのも理由のひとつです。この先どうなるか気になるところです...

rowwise()

rowwise()は便利ですが、これもちょっとイメージが難しそうなので内容に含めませんでした。 あと、rowwise()を使う場面とpurrrを使う場面はけっこうかぶっていて、どっちをどう使うか、使い分けに混乱しそう、ということもあります。

参考:

データフレームのauto-splicing(dplyrのmutate()などの関数の挙動)

これはacross()がどう動いているかに関わるもので、面白いんですが、 まあ知らなくてもやっていけそう、ということで省略しました。詳しくは以下の「across()はデータフレームを返す関数」を参照。

clockパッケージ

clockパッケージは、おそらく今後、時刻・日付データの処理においてlubridateパッケージと双璧を成すであろうパッケージです。 説明を書こうか迷ったのですが、リリースされたばかりでまだ変化が大きそうなことと、lubridateパッケージを置き換えるものではない、 と明言されているので、まあlubridateパッケージを使っていて困ることはなさそう、と判断しました。

最後に:初版を持ってるけど改訂2版も買うか迷ってる方へ

ここまでマニアックな説明を書いておいてなんなのですが、いちおう冷静に考えてみてほしいこととして、これは初心者向けの本です。 この本を数年前に買ったのであれば、今のあなたはそこそこ脱初心者していることでしょう。 もちろん、非初心者が初心者向けの本を読んでも色々新たな発見があったりします。 むしろ、惰性でコードを書いてしまう非初心者だからこそ、基礎から学び直すのが重要。それはそうです。

でも、数年という時間は、あなたを偉くしたり、次のライフステージに進めたりします。 忙しくなってそんなに本を読めない、のに、足元が不安だからと初心者向けの本を買っては積んだままにしていませんか?? ...私はしています(涙)

読んでいただけるとありがたいし、けっこう気合を入れて加筆修正したので初版を持っていても買う価値はあるとは思うんですが、 もしそういう状況にあるなら、自信がなくてもとりあえず前に進んでいただけるとうれしいです。 あくまで初心者が前に進む手助けをするのが初心者向けの本の役割だと考えているので。