rlangと戯れて気が付いたこやつとの私的正しい付き合い方
掲題の件、Tidy Evalというかrlang
パッケージを、下記の記事をはじめいろいろ使いこなそうと試行錯誤してきた。
いい加減、俺なりの楽なやり方が見つかったのでまとめておく。
eval(parse(text=...))をモダンに書きたい - My Life as a Mock Quant
全ての引数の名前と値をlistとして取得したい - My Life as a Mock Quant
rlangパッケージとかTidy Eval周りをお勉強するための日本語の参考資料は、油谷さんのこれしかほぼこれしかないと思う。
dplyr再入門(Tidyeval編) - Speaker Deck
ちなみに俺はとっかかりとして、このHadleyの動画が一番しっくりきた。 www.youtube.com これを見た後
Programming with dplyr • dplyr
を読めばなんとなく使いこなせるようにはなる(気がする)。
で、結局どうするのがいいの?
結論としては「俺は(いつもの)dplyr
的な書き方を捨てるぞ~Hadley~」である。
なんでそんなことになるのかというと、普通にdplyr
使ってる分には当然楽で超いいんだが、それを関数化したい、似たような処理を何回もやりたい、そんなときが俺には多い。
従って、その状況をどうにかするSomethingを考えた。
たとえばこんなデータフレームがあったとしよう。
> set.seed(71) > df <- data.frame( + x = 1:12, + key1=sample(letters[1:4], 12, replace=TRUE), + key2=sample(letters[1:4], 12, replace=TRUE), + key3=sample(letters[1:4], 12, replace=TRUE) + ) > df x key1 key2 key3 1 1 b a a 2 2 c d b 3 3 b d b 4 4 a d b 5 5 b a b 6 6 d d d 7 7 c d a 8 8 d d c 9 9 b c d 10 10 b b a 11 11 a c d 12 12 d a d
このdata.frame
をkey1
, key2
, key3
のそれぞれでグループ化して件数をカウントしたい、そんな状況を考える。
dplyr
に習熟している賢明な読者諸君はこんな風に書くだろう。
> library("dplyr") > dplyr::group_by(df, key1) %>% dplyr::summarize(count=n()) # A tibble: 4 x 2 key1 count <fct> <int> 1 a 2 2 b 5 3 c 2 4 d 3 > dplyr::group_by(df, key2) %>% dplyr::summarize(count=n()) # A tibble: 4 x 2 key2 count <fct> <int> 1 a 3 2 b 1 3 c 2 4 d 6 > dplyr::group_by(df, key3) %>% dplyr::summarize(count=n()) # A tibble: 4 x 2 key3 count <fct> <int> 1 a 3 2 b 4 3 c 1 4 d 4
「あぁ、dplyr
は最高だ、楽だ」とお思いだろう?わかる、俺にもその気持ちがわかる。
わかるが、俺には似たようなCodeを三回も手で書くのは宗教上禁止されているので、こいつを関数化してしまいたいのである。
そして俺は決意した、所謂「dplyr
でスタンダードな書き方(非標準評価、Non Standard Evaluation, NSE)」を捨てる、と。
そのために以下のような関数を書いた。
> f <- function(df, key){ + if(!rlang::is_quosure(key)){ + key <- rlang::sym(key) + } + dplyr::group_by(df, !!key) %>% dplyr::summarize(count = n()) + }
こいつを使うとpurrr
パッケージと組み合わせて(別に組み合わせないでsapply
とか使えばいいんだけど)、以下のように書ける。
まさに一撃ワンライナーだ。気持ちがいい。
> purrr::map(1:3, ~ f(df, paste0("key", .x))) [[1]] # A tibble: 4 x 2 key1 count <fct> <int> 1 a 2 2 b 5 3 c 2 4 d 3 [[2]] # A tibble: 4 x 2 key2 count <fct> <int> 1 a 3 2 b 1 3 c 2 4 d 6 [[3]] # A tibble: 4 x 2 key3 count <fct> <int> 1 a 3 2 b 4 3 c 1 4 d 4
そして先ほど宣言したように、俺は非標準評価、要するに列名を直渡しするスタイルを捨てたのでその場合にはエラーになる。
> f(key1) Error in rlang::is_quosure(key) : argument "key" is missing, with no default
この状況でも一応、動かすことはできて、その場合にはrlang::quo()
を用いればよい。
一件落着である。
> f(df, rlang::quo(key1)) # A tibble: 4 x 2 key1 count <fct> <int> 1 a 2 2 b 5 3 c 2 4 d 3
「なんでこの関数f
でうまくいくか?」についてはがんばってrlang
を勉強して学んでもらいたい私もまだ道半ばなので解説はなしだ。