名前はまだない

データ分析とかの備忘録か, 趣味の話か, はたまた

dplyr ver.1.0.0に追加された関数 + α

はじめに

dplyr ver.1.0.0が公開されました。

主な変更点については、こちらにまとめられています。

新しい関数などはなかなか取り入れないので、こちらにまとめておいて、しっかり使っていきたいと思います。

slice

今まで、sample_n()で行っていたような、レコードのランダムサンプリングは、slice_*系にまとまることになりました。

> iris %>% slice_sample(n = 4) 
  Sepal.Length Sepal.Width Petal.Length Petal.Width    Species
1          7.2         3.6          6.1         2.5  virginica
2          6.0         3.4          4.5         1.6 versicolor
3          5.8         2.7          5.1         1.9  virginica
4          6.8         2.8          4.8         1.4 versicolor

n=を付けないと意図した動きをしてくれません。

特定のカラムの上位N件を取得する関数としてslice_max()があります。

> iris %>% slice_max(Sepal.Length, n=5) 
  Sepal.Length Sepal.Width Petal.Length Petal.Width   Species
1          7.9         3.8          6.4         2.0 virginica
2          7.7         3.8          6.7         2.2 virginica
3          7.7         2.6          6.9         2.3 virginica
4          7.7         2.8          6.7         2.0 virginica
5          7.7         3.0          6.1         2.3 virginica

下位N件を取得する関数としてslice_min()もあります。

> iris %>% slice_min(Sepal.Length, n=5) 
  Sepal.Length Sepal.Width Petal.Length Petal.Width Species
1          4.3         3.0          1.1         0.1  setosa
2          4.4         2.9          1.4         0.2  setosa
3          4.4         3.0          1.3         0.2  setosa
4          4.4         3.2          1.3         0.2  setosa
5          4.5         2.3          1.3         0.3  setosa

N番目に小さな値が2つある場合、N+1件抽出されます。

> iris %>% slice_min(Sepal.Length, n=3) 
  Sepal.Length Sepal.Width Petal.Length Petal.Width Species
1          4.3         3.0          1.1         0.1  setosa
2          4.4         2.9          1.4         0.2  setosa
3          4.4         3.0          1.3         0.2  setosa
4          4.4         3.2          1.3         0.2  setosa

slice_head(), slice_tail()もある。

summarise

summarise後にグルーピングを変更したい場合は、またgroup_by()ungroup()などを行わないといけなかった。

ver1.0.0からは.group()引数でグルーピングを制御できるようになっている。

> starwars %>% 
+   group_by(sex, gender, species) %>% 
+   summarise(height = mean(height), .groups = "drop") 
# A tibble: 42 x 4
   sex            gender    species    height
   <chr>          <chr>     <chr>       <dbl>
 1 female         feminine  Clawdite      168
 2 female         feminine  Human          NA
 3 female         feminine  Kaminoan      213
 4 female         feminine  Mirialan      168
 5 female         feminine  Tholothian    184
 6 female         feminine  Togruta       178
 7 female         feminine  Twi'lek       178
 8 hermaphroditic masculine Hutt          175
 9 male           masculine Aleena         79
10 male           masculine Besalisk      198
# … with 32 more rows

where

今まで、条件にあったカラムに対する処理として、*_if()*_at()*_all()などがありました。

ver1.0.0からは、それが非推奨となり新しくwhere()の使用が推奨されるようになります。

以下の二つは同じ出力となります。

starwars %>% 
   select(where(is.character)) 
starwars %>% 
  select_if(is.character)

<出力>

   name               hair_color    skin_color  eye_color sex    gender    homeworld species
   <chr>              <chr>         <chr>       <chr>     <chr>  <chr>     <chr>     <chr>  
 1 Luke Skywalker     blond         fair        blue      male   masculine Tatooine  Human  
 2 C-3PO              NA            gold        yellow    none   masculine Tatooine  Droid  
 3 R2-D2              NA            white, blue red       none   masculine Naboo     Droid  
 4 Darth Vader        none          white       yellow    male   masculine Tatooine  Human  
 5 Leia Organa        brown         light       brown     female feminine  Alderaan  Human  
 6 Owen Lars          brown, grey   light       blue      male   masculine Tatooine  Human  
 7 Beru Whitesun lars brown         light       blue      female feminine  Tatooine  Human 

関数を意味する~と併用することも可能です。

以下は、num型かつ平均値が3.5以上のカラムを選択ような方法です。

iris %>% 
   select(where(~ is.numeric(.x) && mean(.x) > 3.5))

relocate

今まで、カラムの整列を柔軟を行う方法はなかったと思います。

今バージョンからrelocate()がその役割を果たしてくれるようになりました。

heightmassの前の列に持ってくるには次にようにします。

> starwars %>% relocate(height, .before = mass)
# A tibble: 87 x 14
   name  height  mass hair_color skin_color eye_color birth_year sex   gender homeworld species
   <chr>  <int> <dbl> <chr>      <chr>      <chr>          <dbl> <chr> <chr>  <chr>     <chr>  
 1 Luke…    172    77 blond      fair       blue            19   male  mascu… Tatooine  Human  
 2 C-3PO    167    75 NA         gold       yellow         112   none  mascu… Tatooine  Droid  
 3 R2-D2     96    32 NA         white, bl… red             33   none  mascu… Naboo     Droid  
 4 Dart…    202   136 none       white      yellow          41.9 male  mascu… Tatooine  Human  

heightmassの後の列に持ってくるには次にようにします。

> starwars %>% relocate(height, .after = mass)
# A tibble: 87 x 14
   name   mass height hair_color skin_color eye_color birth_year sex   gender homeworld species
   <chr> <dbl>  <int> <chr>      <chr>      <chr>          <dbl> <chr> <chr>  <chr>     <chr>  
 1 Luke…    77    172 blond      fair       blue            19   male  mascu… Tatooine  Human  
 2 C-3PO    75    167 NA         gold       yellow         112   none  mascu… Tatooine  Droid  
 3 R2-D2    32     96 NA         white, bl… red             33   none  mascu… Naboo     Droid  
 4 Dart…   136    202 none       white      yellow          41.9 male  mascu… Tatooine  Human

where() を用いるとより柔軟なカラムの並び替えが可能です。

> starwars %>% relocate(where(is.character), where(is.numeric)) 
# A tibble: 87 x 14
   name  hair_color skin_color eye_color sex   gender homeworld species height  mass birth_year
   <chr> <chr>      <chr>      <chr>     <chr> <chr>  <chr>     <chr>    <int> <dbl>      <dbl>
 1 Luke… blond      fair       blue      male  mascu… Tatooine  Human      172    77       19  
 2 C-3PO NA         gold       yellow    none  mascu… Tatooine  Droid      167    75      112  
 3 R2-D2 NA         white, bl… red       none  mascu… Naboo     Droid       96    32       33  
 4 Dart… none       white      yellow    male  mascu… Tatooine  Human      202   136       41.9

ここでも.after引数も併用可能です。

across

summarise_if()のようなsummarise_*系は非推奨となりました。

その代わりに、同じような処理を行うためにacross()との併用が推奨することになりました。

where()を用いてカラムを選択します。

> iris %>% 
+   group_by(Species) %>% 
+   summarise(across(.cols = where(is.numeric),
+                    .fns = mean),
+             across(where(is.factor), nlevels),
+             n = n())
`summarise()` ungrouping output (override with `.groups` argument)
# A tibble: 3 x 6
  Species    Sepal.Length Sepal.Width Petal.Length Petal.Width     n
  <fct>             <dbl>       <dbl>        <dbl>       <dbl> <int>
1 setosa             5.01        3.43         1.46       0.246    50
2 versicolor         5.94        2.77         4.26       1.33     50
3 virginica          6.59        2.97         5.55       2.03     50

なお、以下の場合は生成されるカラム名が被ってしまうため、エラーになります。

iris %>% 
  group_by(Species) %>%
  summarise(across(.cols = where(is.numeric), .fns = mean),
            across(.cols = where(is.numeric), .fns = sd))

複数の関数を適用する場合は、list()で指定します。

iris %>%
  group_by(Species) %>%
  summarise(across(contains("Length"), list(mean = mean, sd = sd)))

生成されるカラム名命名規則.name引数で指定することができます。

iris %>%
  group_by(Species) %>%
  summarise(across(starts_with("Sepal"), list(mean, sd), .names = "{col}.fn{fn}"))

rowwise

通常、レコード方向の操作が苦手です。

df <- tibble(id = 1:4, w = runif(4), x = runif(4), y = runif(4), z = runif(4))
> df  %>% mutate(m = mean(c(x, y, z)))
# A tibble: 4 x 6
     id     w      x     y     z     m
  <int> <dbl>  <dbl> <dbl> <dbl> <dbl>
1     1 0.924 0.0631 0.369 0.863 0.511
2     2 0.158 0.222  0.422 0.201 0.511
3     3 0.578 0.185  0.601 0.954 0.511
4     4 0.226 0.491  0.786 0.971 0.511

rowwise()を挟むことで、レコードごとの処理を行うことができます。

> df %>% rowwise() %>% mutate(m = mean(c(x, y, z)))
# A tibble: 4 x 6
# Rowwise: 
     id     w      x     y     z     m
  <int> <dbl>  <dbl> <dbl> <dbl> <dbl>
1     1 0.924 0.0631 0.369 0.863 0.432
2     2 0.158 0.222  0.422 0.201 0.282
3     3 0.578 0.185  0.601 0.954 0.580
4     4 0.226 0.491  0.786 0.971 0.749

c_across()と併用することで、柔軟にカラムの選択が行えます。

以下のようにするとw列からz列までのカラムに対して、各レコード毎に関数を適用することできる。

df %>%
  rowwise() %>%
  mutate(
    sum = sum(c_across(w:z)),
    sd = sd(c_across(w:z))
  )
# A tibble: 4 x 7
# Rowwise: 
     id     w      x     y     z   sum    sd
  <int> <dbl>  <dbl> <dbl> <dbl> <dbl> <dbl>
1     1 0.924 0.0631 0.369 0.863  2.22 0.411
2     2 0.158 0.222  0.422 0.201  1.00 0.117
3     3 0.578 0.185  0.601 0.954  2.32 0.314
4     4 0.226 0.491  0.786 0.971  2.47 0.328

pivot_*

v1.0.0から追加されたものではないですが、個人的に使い方が覚えられないので、一緒にまとめておきます。

<あとで>