Rのdplyrパッケージを使っていて困ることのひとつに、非標準評価(NSE)をめぐるトラブルがある。いざ困ったときにすぐ調べられるように、以前、dplyrのvignettesのひとつ”Programming with dplyr“を通読してメモを取った。
ところがdplyrの仕様はあれこれ変わり、このvignetteの中身もすっかり変わってしまった。考えてみたら、前に目を通したのは2017年だもんな。あのころの私は若かった。[すいませんいまちょっと適当なことをいいました]
というわけで、最新版のvignetteを読み直してメモをとった。以前はquo(), enquo(), quo_name()などが乱れ飛ぶややこしい話だったのだが、びっくりするくらいにシンプルな話に変わっている。すごい。
イントロダクション
dplyrはtidy evaluationと呼ばれる非標準評価を用いる。tidy evaluationには data maskingとtidy selectionがある。
data masking
たとえばdf %>% group_by(var1)というとき、環境の中にはvar1がなくても、var1はdfの列名(データ変数名)だと解釈されますよね。そういうののこと。arrange(), count(), filter(), group_by(), mutate(), summarize()で用いられる。
以下では環境の中にある変数を環境変数、データフレームにある変数をデータ変数と呼ぶ。
データ変数を(データ変数名を直接入力するのではなくて)環境変数から得ることをindirectionという。主に2つのケースがある。
ケースその1、関数の引数のなかにデータ変数があるとき。dplyrの関数のなかで、データ変数名を{{ … }} で囲むとよい。たとえば
var_summary <- function(data, var){ data %>% summarize(min = min({{ var }})) } mtcars %>% group_by(cyl) %>% var_summary(mpg)
[この例、前掲した2017年のメモでは、関数に裸の変数名 var を渡すのではなく quo(var) として渡して関数のなかで !!var としてアンクオートするか、裸の関数名 var を渡して関数のなかで一旦 var <- enquo(var) としてから!!var としてアンクオートせよ、と書いてあった。ずいぶん簡単な話になりましたねえ]
ケースその2, 環境変数が文字列としてデータ変数名を持っているとき。.data[[…]]を使う。
for (var in names(mtcars)) { mtcars %>% count(.data[[var]]) %>% print() }
tidy selection
たとえばselect(df, starts_with(“a”))とか書けるじゃないですか。こういう変数選択は、across(), relocate(), rename(), select(), pull()でできる。裏ではtidyselect パッケージが動いている。詳細は ?dplyr_tidy_selectをみなさい。
さて、ここでもindirectionという問題が生じる。
ケースその1、関数の引数のなかにデータ変数があるとき。このときもdplyrの関数の中で、データ変数を{{ … }}で囲むとよい。
summarise_mean <- function(data, vars){ data %>% summarize(accross({{ vars }}, mean) } mtcars %>% group_by(cyl) %>% summarise_mean(where(is.numeric))
ケースその2, 環境変数が文字列としてデータ変数名を持っているとき。これは簡単で、all_of(), any_of()を使えばよろしい。
How to
[7つのコツが紹介されている。2項目のみメモする。あとのは、うんまあそうなんだろうなという話なので省略]
- パッケージを作っているとき、dplyrでデータ変数名を裸で使っているとcheckの際にNOTESが出てうざい → @ importFrom rlang .data と宣言しておき、データ変数名の前にいちいち.dataと書け [これが面倒くさくて困ってるんですけど…]
- 関数の中で新しく作る変数名を引数として渡したい → = の代わりに:=を使え。左辺では” “でくくったなかで{{…}}が使える。例:
my_summarize <- function(data, expr) { data %>% summarize( "mean_{{expr}}" := mean({{ expr }}) ) }