まずは蝋の翼から。

学んだことを書きながら確認・整理するためのメモブログ。こういうことなのかな?といったことをふわっと書いたりしていますが、理解が浅いゆえに的はずれなことも多々あると思うのでツッコミ歓迎

{{ }}を用いると、普段のdplyrの書き方を関数内でもほぼできるようになった模様

dplyrではNSEなどの関係で、関数内で普段通りのdplyr的な書き方はできなくて色々とややこしかったのが、rlang 0.4.0からは{{ }}を使って直感的な書き方ができるようになったらしい。

www.tidyverse.org

www.r-bloggers.com

関数を使わないでコピペを繰り返すやり方

例えばirisデータで、Species毎に各列の平均値を求める場合、関数を使わない場合以下のようにする。

library(tidyverse)

iris2 = iris %>%
  tibble()

# # A tibble: 150 x 5
# Sepal.Length Sepal.Width Petal.Length Petal.Width Species
# <dbl>       <dbl>        <dbl>       <dbl> <fct>  
#   1          5.1         3.5          1.4         0.2 setosa 
# 2          4.9         3            1.4         0.2 setosa 
# 3          4.7         3.2          1.3         0.2 setosa 
# 4          4.6         3.1          1.5         0.2 setosa 
# 5          5           3.6          1.4         0.2 setosa 
# 6          5.4         3.9          1.7         0.4 setosa 
# 7          4.6         3.4          1.4         0.3 setosa 
# 8          5           3.4          1.5         0.2 setosa 
# 9          4.4         2.9          1.4         0.2 setosa 
# 10          4.9         3.1          1.5         0.1 setosa 
# # … with 140 more rows

# 列を変えて3回繰り返す

iris2 %>% 
  group_by(Species) %>%
  summarise(avg = mean(Sepal.Length, na.rm = TRUE))
# # A tibble: 3 x 2
# Species      avg
# <fct>      <dbl>
#   1 setosa      5.01
# 2 versicolor  5.94
# 3 virginica   6.59

iris2 %>% 
  group_by(Species) %>%
  summarise(avg = mean(Sepal.Width, na.rm = TRUE))

# # A tibble: 3 x 2
# Species      avg
# <fct>      <dbl>
#   1 setosa      3.43
# 2 versicolor  2.77
# 3 virginica   2.97


iris2 %>% 
  group_by(Species) %>%
  summarise(avg = mean(Petal.Length, na.rm = TRUE))
# # A tibble: 3 x 2
# Species      avg
# <fct>      <dbl>
#   1 setosa      1.46
# 2 versicolor  4.26
# 3 virginica   5.55

# 以下略

関数化をしたやりかた(直感的な書き方:error)

何回も同じコードをコピペはナンセンスなので、関数化する。そのまま書くと以下のようになりそうだがエラーとなる。

mean_by <- function(data, by, var) {
  data %>%
    group_by(by) %>%
    summarise(avg = mean(var, na.rm = TRUE))
}

iris2 %>%
  mean_by(Species, Sepal.Length)
#  エラー: Column `by` is unknown 

iris2 %>%
  mean_by(Species, Sepal.Width)
#  エラー: Column `by` is unknown 

...

関数化をしたやりかた(旧rlangを使ったわかりづらい書き方)

そのため、今まではsymや!!などで独特の書き方をする必要があった。

mean_by <- function(data, by, var) {
  
  by <- sym(by)
  var <- sym(var)
  
  data %>%
    group_by(!!by) %>%
    summarise(avg = mean(!!var, na.rm = TRUE))
}

iris2 %>%
  mean_by('Species', 'Sepal.Length')

# # A tibble: 3 x 2
# Species      avg
# <fct>      <dbl>
#   1 setosa      5.01
# 2 versicolor  5.94
# 3 virginica   6.59

iris2 %>%
  mean_by('Species', 'Sepal.Width')

# # A tibble: 3 x 2
# Species      avg
# <fct>      <dbl>
#   1 setosa      3.43
# 2 versicolor  2.77
# 3 virginica   2.97

knknkn.hatenablog.com

teramonagi.hatenablog.com

関数化をしたやりかた(直感的な書き方)

先程の直感的な書き方とほぼ同じだが、関数の引数で指定している部分は {{ }}で囲うと引数が渡されるようになったようだ。

mean_by <- function(data, by, var) {
  data %>%
    group_by({{by}}) %>%
    summarise(avg = mean({{var}}, na.rm = TRUE))
}


iris2 %>%
  mean_by(Species, Sepal.Width)

# # A tibble: 3 x 2
# Species      avg
# <fct>      <dbl>
#   1 setosa      5.01
# 2 versicolor  5.94
# 3 virginica   6.59

iris2 %>%
  mean_by(Species, Sepal.Length)

# # A tibble: 3 x 2
# Species      avg
# <fct>      <dbl>
#   1 setosa      3.43
# 2 versicolor  2.77
# 3 virginica   2.97

おまけ。列名での変数名の使用

過去記事で、列名に動的に名前をつけるために :=を使いましたが同様のやり方で可能。

knknkn.hatenablog.com

なお、{ }は関数での引数のうち文字列となる変数(glue同様の使い方になる)、{{ }}は引数の列名(シングルコートなしのNSE)となる。

mean_by <- function(data, by, var, prefix = 'avg') {
  data %>%
    group_by({{by}}) %>%
    summarise('{prefix}_{{var}}' := mean({{var}}, na.rm = TRUE))
}


iris2 %>%
  mean_by(Species, Sepal.Width)

# # A tibble: 3 x 2
# Species    avg_Sepal.Width
# <fct>                <dbl>
#   1 setosa                3.43
# 2 versicolor            2.77
# 3 virginica             2.97