気ままにデータ分析

データサイエンティスト見習いの学習メモ

【dplyr】_if()、_at()、_all()が全部across()で済むようになった。

dplyr1.0.0がリリースされました。
今回のアップデートの中で、across()関数が導入されたのは大きな変化の1つでしょう。


 
何ができるようになったかというと、
今まではfilter()関数やmutate()関数などで複数列を同時に処理する際に
suffixが_if()_at()_all()のscope functionを適宜使い分けてましたが、これらが全部across()関数で済むようになるよって話。

覚えることが減った。やったね。

across()でできること

どう置き換わったのか、例をいくつか。
以下のようなデータフレームを用意します。

set.seed(1)
df <- tibble(
  id=1:10,
  test1=runif(10,max=100),
  test2=runif(10,max=100),
  test3=runif(10,max=100),
  test4=runif(10,max=100),
  weight=runif(10, min = 40, max = 80),
  type=c('A','C','C','B','C','A','C','A','A','B')
)
> df
# A tibble: 10 x 7
      id test1 test2 test3 test4 weight type 
   <int> <dbl> <dbl> <dbl> <dbl>  <dbl> <chr>
 1     1 47.8  91.3   33.9  43.5   49.6 A    
 2     2 86.1  29.4   83.9  71.3   42.4 C    
 3     3 43.8  45.9   34.7  40.0   65.7 C    
 4     4 24.5  33.2   33.4  32.5   75.1 B    
 5     5  7.07 65.1   47.6  75.7   71.2 C    
...

生徒10人にidが割り振られ、4つのテストの点数と体重、属性(A,B,C)のデータがあります。


データフレームの数字を全て四捨五入したければ

df %>%
  mutate_if(is.numeric, round)

とするところをacross()関数で以下のように表現できます。

df %>%
  mutate(across(is.numeric, round))
> df
# A tibble: 10 x 7
      id test1 test2 test3 test4 weight type 
   <dbl> <dbl> <dbl> <dbl> <dbl>  <dbl> <chr>
 1     1    27    21    93    48     73 A    
 2     2    37    18    21    60     66 C    
 3     3    57    69    65    49     71 C    
 4     4    91    38    13    19     62 B    
 5     5    20    77    27    83     61 C    
...


今度は、weightに関しては四捨五入せずにそのままにしたいとしましょう。
つまりテストの点だけ四捨五入して、体重のデータは小数点まで表示させたいようなシチュエーションです。
列名を指定するために今度はmutate_if()ではなくmutate_at()を用いる必要がありました。

df %>% 
  mutate_at(c("test1","test2","test3","test4"), round)

こちらもacross()関数で

df %>% 
  mutate(across(test1:test4,round))
> df
# A tibble: 10 x 7
      id test1 test2 test3 test4 weight type 
   <int> <dbl> <dbl> <dbl> <dbl>  <dbl> <chr>
 1     1    27    21    93    48   72.8 A    
 2     2    37    18    21    60   65.9 C    
 3     3    57    69    65    49   71.3 C    
 4     4    91    38    13    19   62.1 B    
 5     5    20    77    27    83   61.2 C    
...

と表現できます。複数列を簡略に抽出するためのvars()関数も必要なくなりました。


また、mutate_all()関数はacross()関数の中にeverything()を用いることで同じ処理ができるようになります。

df %>%
  mutate_all(mean)  

#across()を用いた場合
df %>% 
  mutate(across(everything(), mean))

_all()に関しては便利になったのか正直よく分かりませんが、_at()_if()がうまいこと1つになったのがacross()関数と言えそうです。
今回は簡単な例でやってみましたが、実務で扱うレベルだと大分記述が楽になるのではないでしょうか。