戯言日記

Rの話だと思ったら唐突にサバゲーが混じってくる何か。

dplyr1.0.0 が来てた話(自分が気になった変更点まとめ)

最新情報を積極的に追うタイプではなかったので、気が付いたら大きいバージョンアップが来てた。

cloud.r-project.org

なお0.9はない。たぶんRの闇に消えた(適当)。
というかWindowsにしろiPhoneにしろ、ナンバリングで9を飛ばしたがるのはなんでだ。

tidyverse側も発表している。こっちの方が分かりやすい。

www.tidyverse.org

本アップデートのテーマは「function lifecycle」らしい。直訳すると「関数のライフサイクル」。どういうことだってばよ。
一応、「関数を置き換えたことの意味」的なことが重要と言っているので、作った当時と現在の中で変わってきたことを取り入れたってことだと思ってる。違うかもしれないけど。
大型アップデート(0.8 → 1.0)なので、デジモンで例えると成長期から成熟期に変わったってとこですかね。ということは完全体と究極体が残ってんのかよすげぇ。

あと個人的に最大のニュースはロゴが変わったことです。すげぇかっこいい。


詳細な解説はRのプロフェッショナルにお任せして、ざっくり読んだ内容で気になったところだけピックアップ。
あと英訳は完全に意訳なので、正確な文章を読みたければ上のリンク先へどうぞ。誤訳・勘違いもぜひ指摘よろしくお願いします、速攻で直します。

※文章中で"data.frame"と"データフレーム"などR上の表記と日本語表記を使い分けている理由ですが、「こっちの方が読みやすいかな~」とか思いながら文章ごとに選定しているため特に意味はないです。


勝手に何かを変更する挙動がかなり減った

今までデータを弄っていると勝手に因子になったり文字列になったり、挙句の果てには並び替えまでされたりしてきたが、その辺がかなり改善した模様。
一部を取り上げると、

  1. bind_colがdata.frameを結合する時、tibbleにしなくなった

  2. bind_rows()*_join()summarize()mutate()でのfactorの取り扱いを変更
    ・factorとcharacterをくっつけると黙ってcharacterにする
    (以前:警告を出してた)
    ・異なる複数のfactorをくっつけると新しいfactorを作る
    (以前:characterにしていた)

  3. distinct()の変更
    ・列を勝手にソートしなくなった
    ・欠損列に使うとエラーを発生

  4. right_join()が結合する側に従って列を並べ替えなくなった
    right_join(x, y)のxに従うという意味で合ってるはず

痒い所に手が届くというか、「なんでそうなるんや」と画面の前で叫んだ挙動がだいぶ減ると思う。
ただ、1.に関してはfactorだと思ってたらcharacterになってた、みたいな現象が起きかねないので、早めに確認した方がいいかも。

ちなみに、R 4.0.0で文字列を自動的に因子に変換しなくなった上に、rbind()もデータフレームに対して使う時に因子の水準を削除しなくなったとのこと(斜め読みしただけなので間違ってたらすみません)なので、全体の流れとしてこういう仕様になっていく可能性はある。


summarise()mutate()の出力が便利になった

簡単に言うと、「出力結果がベクトルだろうとデータフレームだろうと出力できる」ようになった。何それ強い。
複数の結果を返す関数がぱっと思いつかないから例では無理やりtibbleとかで返してるけど、自作関数とかを利用する場合はなかなか便利そう。

#これまで
iris %>% 
  dplyr::group_by(Species) %>% 
  dplyr::summarize( min=min(Sepal.Length), max=max(Sepal.Length) )

#これから
iris %>% 
  dplyr::group_by(Species) %>% 
  dplyr::summarize( tibble(min=min(Sepal.Length), max=max(Sepal.Length)) )

#やや強引だけど、こんなことも
iris %>% 
  dplyr::group_by(Species) %>% 
  dplyr::summarize( data = matrix( c(min(Sepal.Length), max(Sepal.Length)), nrow = 2) )

#mutate()ならこんな感じで動かせる
iris %>% 
  dplyr::group_by(Species) %>% 
  dplyr::mutate( tibble(Sepal.Ratio=Sepal.Length/Sepal.Width, Petal.Ratio=Petal.Length/Petal.Width) )


_if()_at()_all()を使わない関数処理

上の「function lifecycle」が言及してる(であろう)部分。開発側としては無くしたいっぽい。
というのも、それぞれの関数にバリエーションを用意するよりも、共通で使える分岐があった方が便利じゃね?ってことらしい。
下でも説明するけど、across()とかの新しい関数も入ってきたりと本気で変わってきているので、乗り換えられるならその方がいい。


select()rename()、新しいrelocate()で列の指定が簡単になったよ!

ブール論理(!、&、|)とかwhere(is.numeric)とかを使えるように変更したとのこと。summarize()とかmutateで出来ていたことをこっちでも出来るようにしたイメージ。
それと名前が重複しているデータフレームを使わないようになったって記載もあった。試した感じ、たぶん同じ名前があるとエラーが起きるってことかと。むしろ今まで出てなかったのか。

#論理判断で簡単に列を取り出せる
iris %>% dplyr::select( where(is.numeric) ) %>% head()

#「名前が重複してるよ」ってエラーを返してくれる
colnames(iris) <- c("Length","Width","Length","Width","Species")
iris %>% dplyr::select(Length)

relocate()は列の順番の組換えができる関数で、括弧内で列名や条件を指定すると当てはまる列が前に来る。

iris %>% dplyr::relocate( contains("Length") )


across()で複数列の処理を簡易化

dplyr.tidyverse.org

新しい関数が来た。といっても、これだけで動くわけではなくて、dplyrの関数と併用することで機能する。
例えばsummarize()で複数の列に同じ挙動をさせる時に、これまでだと1つずつ指定していたものを纏めてやってくれる。列の指定は名前の他、論理判断やwhere(is.numeric)も使用可能。

#今までは1つずつ指定
iris %>% 
  dplyr::group_by(Species) %>% 
  dplyr::summarize(Sepal.Length = mean(Sepal.Length),
                   Sepal.Width = mean(Sepal.Width),
                   Petal.Length = mean(Petal.Length),
                   Petal.Width = mean(Petal.Width) )

#今後は纏めて指定できる
iris %>% 
  dplyr::group_by(Species) %>% 
  dplyr::summarize( dplyr::across(Sepal.Length:Petal.Width, mean) )

公式の考えとしては、「これまで難しかった要約ができるようになる」とのことらしいけど、確かに可読性やら面倒な処理やらは綺麗に纏められる気がする。
これまでsummarize_all()とかを繋げていた処理も、この方法ならsummarize()内に纏めて一括で出せるし。

細かいことを書こうとしたらアホみたいに記事が長くなったから別で纏めようとは思うけど、慣れるとらくになりそう。


rowwise()で行の計算を簡単に行う

これも今後推していきたい機能らしい。
簡単に言うと、行方向でのデータ集計を楽にしてくれる。

基本的にはrowwise()を使うことで横向きの要約ができるようにしてくれる機能らしい。
範囲指定する場合にはc_across()を利用する必要があるので、そこは注意。

rowwise()を使うとこうなる
iris %>% dplyr::rowwise() %>%
  dplyr::mutate( sum = sum(c(Sepal.Length, Sepal.Width, Petal.Length ,Petal.Width)) )

#rowwise()が抜けると酷いことになる
iris %>%
  dplyr::mutate( sum = sum(c(Sepal.Length, Sepal.Width, Petal.Length ,Petal.Width)) )

#c_acrossを利用して列の一括指定ができる
iris %>% dplyr::rowwise() %>%
  dplyr::mutate( sum = sum(c_across(Sepal.Length:Petal.Width)) )

#c_acrossなしだと変な結果が戻る
iris %>% dplyr::rowwise() %>%
  dplyr::mutate( sum = sum(Sepal.Length:Petal.Width) )


slice()のセットが増える

個人的には結構推したい部分。
slice_head()slice_tail()の利便性はヤバい。コードがすっきりする。

その他、

・ランダムに行を持ってこれるslice_sample()

・最大値・最小値を含む行を持ってくるslice_min()slice_max()

なんかも使いどころがありそう。slice()なので行を丸ごと持ってきてくれるのが割と助かる。


その他、細かい変更点

  1. group_modify()がグループ分けの因子を先頭列に持ってくる

  2. n()row_number()が自動でdplyr版に変更されない

  3. grouped_dfの形式をサポートしなくなる

  4. lead()とlag()に入れるデータがより厳密になります

  5. 拡張データフレームのクラス指定は最初にやる


廃止・置き換え関連


まずは置き換え関連(この関数はやめて、こっちを使ってね系)。

  1. add_rownames()tibble::rownames_to_column()に変更

  2. as.tbl()tbl_df()s_tibble()に変更

  3. combine()vctrs::vec_c()に変更

  4. funs()list()に変更

  5. group_by(add = ).addに変更

  6. group_by(.dots = )group_by_prepare(.dots = )!!!に変更

  7. location()changes()lobstr::ref()に変更


その他、個別のお話。

  1. na_matchesは使用率低いしデフォルトの方を使う方がいいよね

  2. add_count()drop指定するの意味ないから止めよう

  3. bench_tbls()compare_tbls()compare_tbls2()eval_tbls()eval_tbls2()は少ししか使われてないから、すまないけど他の手段で直接的に比較してくれ

  4. 現在のグループIDを取り出したければ、group_indices()じゃなくてcur_group_id()を使ってくれ

  5. group_keys()とかgroup_indices()でグループ分けしないで

  6. progress_estimated()はやんわり非推奨にしとくね、プログレスバーの表示はうちの責任じゃないんで(超意訳)。


全体的に、データを操作する際に勝手な変更をしなくなる変更が多い感じ。
クラス変更とデータの自動ソートは正直勘弁してほしい場面が多いので、今回の変更は非常に助かる。というか全部の関数でそうしてほしい。

一部省略した項目についてはそのうち纏めようと思います。

Enjoy!