グループ化されたデータ

dplyr の動詞は、グループ化されたデータフレーム(grouped_dfオブジェクト)に適用すると特に強力です。 この vignette では、以下のことを説明します。

まずは、dplyrをロードするところから始めましょう。:

library(dplyr)

group_by()

最も重要なグループ化のための動詞は、group_by()です。この動詞は、データフレームとグループ化するための1つ以上の変数を受け取ります。:

by_species <- starwars %>% group_by(species)
by_sex_gender <- starwars %>% group_by(sex, gender)

データを表示するとグループ化されているのがわかります。:

by_species
#> # A tibble: 87 x 14
#> # Groups:   species [38]
#>   name     height  mass hair_color skin_color  eye_color birth_year sex   gender
#>   <chr>     <int> <dbl> <chr>      <chr>       <chr>          <dbl> <chr> <chr> 
#> 1 Luke Sk…    172    77 blond      fair        blue            19   male  mascu…
#> 2 C-3PO       167    75 <NA>       gold        yellow         112   none  mascu…
#> 3 R2-D2        96    32 <NA>       white, blue red             33   none  mascu…
#> 4 Darth V…    202   136 none       white       yellow          41.9 male  mascu…
#> # … with 83 more rows, and 5 more variables: homeworld <chr>, species <chr>,
#> #   films <list>, vehicles <list>, starships <list>
by_sex_gender
#> # A tibble: 87 x 14
#> # Groups:   sex, gender [6]
#>   name     height  mass hair_color skin_color  eye_color birth_year sex   gender
#>   <chr>     <int> <dbl> <chr>      <chr>       <chr>          <dbl> <chr> <chr> 
#> 1 Luke Sk…    172    77 blond      fair        blue            19   male  mascu…
#> 2 C-3PO       167    75 <NA>       gold        yellow         112   none  mascu…
#> 3 R2-D2        96    32 <NA>       white, blue red             33   none  mascu…
#> 4 Darth V…    202   136 none       white       yellow          41.9 male  mascu…
#> # … with 83 more rows, and 5 more variables: homeworld <chr>, species <chr>,
#> #   films <list>, vehicles <list>, starships <list>

訳注: tally = 勘定

また、tally() を使って各グループの行数を数えることもできます。最大のグループを前面に表示したい場合は、sort 引数が便利です。:

by_species %>% tally()
#> # A tibble: 38 x 2
#>   species      n
#>   <chr>    <int>
#> 1 Aleena       1
#> 2 Besalisk     1
#> 3 Cerean       1
#> 4 Chagrian     1
#> # … with 34 more rows

by_sex_gender %>% tally(sort = TRUE)
#> # A tibble: 6 x 3
#> # Groups:   sex [5]
#>   sex    gender        n
#>   <chr>  <chr>     <int>
#> 1 male   masculine    60
#> 2 female feminine     16
#> 3 none   masculine     5
#> 4 <NA>   <NA>          4
#> # … with 2 more rows

既存の変数でグループ化するだけでなく,既存の変数の任意の関数でグループ化することもできます。これは、group_by() の前に mutate() を実行するのと同じです。 :

bmi_breaks <- c(0, 18.5, 25, 30, Inf)

starwars %>%
  group_by(bmi_cat = cut(mass/(height/100)^2, breaks=bmi_breaks)) %>%
  tally()
#> # A tibble: 5 x 2
#>   bmi_cat       n
#>   <fct>     <int>
#> 1 (0,18.5]     10
#> 2 (18.5,25]    24
#> 3 (25,30]      13
#> 4 (30,Inf]     12
#> # … with 1 more row

メタデータのグループ化

グループのデータは、group_keys() で見ることができます。グループごとに1行、グループ変数ごとに1列が用意されています。:

by_species %>% group_keys()
#> # A tibble: 38 x 1
#>   species 
#> * <chr>   
#> 1 Aleena  
#> 2 Besalisk
#> 3 Cerean  
#> 4 Chagrian
#> # … with 34 more rows

by_sex_gender %>% group_keys()
#> # A tibble: 6 x 2
#>   sex            gender   
#> * <chr>          <chr>    
#> 1 female         feminine 
#> 2 hermaphroditic masculine
#> 3 male           masculine
#> 4 none           feminine 
#> # … with 2 more rows

各行がどのグループに属しているかは、group_indices() で確認できます。:

by_species %>% group_indices()
#>  [1] 11  6  6 11 11 11 11  6 11 11 11 11 34 11 24 12 11 11 36 11 11  6 31 11 11
#> [26] 18 11 11  8 26 11 21 11 10 10 10 38 30  7 38 11 37 32 32 33 35 29 11  3 20
#> [51] 37 27 13 23 16  4 11 11 11  9 17 17 11 11 11 11  5  2 15 15 11  1  6 25 19
#> [76] 28 14 34 11 38 22 11 11 11  6 38 11

また、各グループがどの行を含んでいるかは、group_rows() で確認できます。:

by_species %>% group_rows() %>% head()
#> <list_of<integer>[6]>
#> [[1]]
#> [1] 72
#> 
#> [[2]]
#> [1] 68
#> 
#> [[3]]
#> [1] 49
#> 
#> [[4]]
#> [1] 56
#> 
#> [[5]]
#> [1] 67
#> 
#> [[6]]
#> [1]  2  3  8 22 73 85

グループ化変数の名前だけを知りたい場合は、group_vars() を使ってください。:

by_species %>% group_vars()
#> [1] "species"
by_sex_gender %>% group_vars()
#> [1] "sex"    "gender"

グループ化変数の変更と追加

すでにグループ化されているデータセットに group_by() を適用すると、既存のグループ化変数が上書きされます。0 例えば、以下のコードでは species ではなく homeworld でグループ化しています。

by_species %>%
  group_by(homeworld) %>%
  tally()
#> # A tibble: 49 x 2
#>   homeworld       n
#>   <chr>       <int>
#> 1 Alderaan        3
#> 2 Aleen Minor     1
#> 3 Bespin          1
#> 4 Bestine IV      1
#> # … with 45 more rows

グループ化を 強化 するには、.add = TRUE1を使用します。例えば、次のコードは、species と homeporld でグループ化します。:

by_species %>%
  group_by(homeworld, .add = TRUE) %>%
  tally()
#> # A tibble: 58 x 3
#> # Groups:   species [38]
#>   species  homeworld       n
#>   <chr>    <chr>       <int>
#> 1 Aleena   Aleen Minor     1
#> 2 Besalisk Ojom            1
#> 3 Cerean   Cerea           1
#> 4 Chagrian Champala        1
#> # … with 54 more rows

グルーピング変数の削除

すべてのグルーピング変数を削除するには、ungroup() を使います。

by_species %>%
  ungroup() %>%
  tally()
#> # A tibble: 1 x 1
#>       n
#>   <int>
#> 1    87

また、削除したい変数をリストアップして、選択的にグループ解除することもできます。:

by_sex_gender %>% 
  ungroup(sex) %>% 
  tally()
#> # A tibble: 3 x 2
#>   gender        n
#>   <chr>     <int>
#> 1 feminine     17
#> 2 masculine    66
#> 3 <NA>          4

verbs

以下のセクションでは、グループ化が主な dplyr の動詞にどのように影響するかを説明します。

summarise()

summarise() は、各グループの要約を計算します。 つまり、group_keys() から始まり、右辺に要約変数を追加していきます。

by_species %>%
  summarise(
    n = n(),
    height = mean(height, na.rm = TRUE)
  )
#> `summarise()` ungrouping output (override with `.groups` argument)
#> # A tibble: 38 x 3
#>   species      n height
#>   <chr>    <int>  <dbl>
#> 1 Aleena       1     79
#> 2 Besalisk     1    198
#> 3 Cerean       1    198
#> 4 Chagrian     1    196
#> # … with 34 more rows

.groups= 引数は、出力のグループ化構造を制御します。 右側のグループ化変数を削除するという歴史的な動作は、メッセージなしの .groups = "drop_last" や、メッセージ付きの .groups = NULL (デフォルト)に対応します。

by_sex_gender %>% 
  summarise(n = n()) %>% 
  group_vars()
#> `summarise()` regrouping output by 'sex' (override with `.groups` argument)
#> [1] "sex"

by_sex_gender %>% 
  summarise(n = n(), .groups = "drop_last") %>% 
  group_vars()
#> [1] "sex"

バージョン1.0.0以降では、グループを保持したり(.groups = "keep")、削除したり(.groups = "drop")することもできます。

by_sex_gender %>% 
  summarise(n = n(), .groups = "keep") %>% 
  group_vars()
#> [1] "sex"    "gender"

by_sex_gender %>% 
  summarise(n = n(), .groups = "drop") %>% 
  group_vars()
#> character(0)

出力がグループ化変数を持たなくなると、グループ化されていない(つまり通常のティブル)になります。

select(), rename(), relocate()について

rename()relocate() は、既存のカラムの名前や位置に影響を与えるだけなので、グループ化されたデータでもグループ化されていないデータでも同じように動作します。 グループ化された select() は,グループ化変数を常に含むことを除けば、グループ化されていない select とほとんど同じです。

by_species %>% select(mass)
#> Adding missing grouping variables: `species`
#> # A tibble: 87 x 2
#> # Groups:   species [38]
#>   species  mass
#>   <chr>   <dbl>
#> 1 Human      77
#> 2 Droid      75
#> 3 Droid      32
#> 4 Human     136
#> # … with 83 more rows

グループ化された変数が不要な場合は、まず ungroup() を実行する必要があります。(この設計は間違いの可能性もありますが、今はこれで我慢しています。)

arrange()

グループ化された arrange() は、.by_group = TRUE を設定しない限り、グループ化されていない arrange() と同じです。 グループ化された arrange() は、グループ化されていない arrange() と同じです。

by_species %>%
  arrange(desc(mass)) %>%
  relocate(species, mass)
#> # A tibble: 87 x 14
#> # Groups:   species [38]
#>   species  mass name    height hair_color skin_color  eye_color birth_year sex  
#>   <chr>   <dbl> <chr>    <int> <chr>      <chr>       <chr>          <dbl> <chr>
#> 1 Hutt     1358 Jabba …    175 <NA>       green-tan,… orange         600   herm…
#> 2 Kaleesh   159 Grievo…    216 none       brown, whi… green, y…       NA   male 
#> 3 Droid     140 IG-88      200 none       metal       red             15   none 
#> 4 Human     136 Darth …    202 none       white       yellow          41.9 male 
#> # … with 83 more rows, and 5 more variables: gender <chr>, homeworld <chr>,
#> #   films <list>, vehicles <list>, starships <list>

by_species %>%
  arrange(desc(mass), .by_group = TRUE) %>%
  relocate(species, mass)
#> # A tibble: 87 x 14
#> # Groups:   species [38]
#>   species   mass name    height hair_color skin_color eye_color birth_year sex  
#>   <chr>    <dbl> <chr>    <int> <chr>      <chr>      <chr>          <dbl> <chr>
#> 1 Aleena      15 Ratts …     79 none       grey, blue unknown           NA male 
#> 2 Besalisk   102 Dexter…    198 none       brown      yellow            NA male 
#> 3 Cerean      82 Ki-Adi…    198 white      pale       yellow            92 male 
#> 4 Chagrian    NA Mas Am…    196 none       blue       blue              NA male 
#> # … with 83 more rows, and 5 more variables: gender <chr>, homeworld <chr>,
#> #   films <list>, vehicles <list>, starships <list>

2番目の例では、speciesgroup_by()ステートメントから)、次にmass(species 内)でソートされていることに注意してください。

mutate()transmute()について

ベクトル化された関数を使った単純なケースでは、グループ化された mutate() とグループ化されていない mutate() は同じ結果になります。 summary 関数と併用する場合は異なります。

# Subtract off global mean
starwars %>% 
  select(name, homeworld, mass) %>% 
  mutate(standard_mass = mass - mean(mass, na.rm = TRUE))
#> # A tibble: 87 x 4
#>   name           homeworld  mass standard_mass
#>   <chr>          <chr>     <dbl>         <dbl>
#> 1 Luke Skywalker Tatooine     77         -20.3
#> 2 C-3PO          Tatooine     75         -22.3
#> 3 R2-D2          Naboo        32         -65.3
#> 4 Darth Vader    Tatooine    136          38.7
#> # … with 83 more rows

# Subtract off homeworld mean
starwars %>% 
  select(name, homeworld, mass) %>% 
  group_by(homeworld) %>% 
  mutate(standard_mass = mass - mean(mass, na.rm = TRUE))
#> # A tibble: 87 x 4
#> # Groups:   homeworld [49]
#>   name           homeworld  mass standard_mass
#>   <chr>          <chr>     <dbl>         <dbl>
#> 1 Luke Skywalker Tatooine     77         -8.38
#> 2 C-3PO          Tatooine     75        -10.4 
#> 3 R2-D2          Naboo        32        -32.2 
#> 4 Darth Vader    Tatooine    136         50.6 
#> # … with 83 more rows

または、min_rank() のようなウィンドウ関数で。:

# Overall rank
starwars %>% 
  select(name, homeworld, height) %>% 
  mutate(rank = min_rank(height))
#> # A tibble: 87 x 4
#>   name           homeworld height  rank
#>   <chr>          <chr>      <int> <int>
#> 1 Luke Skywalker Tatooine     172    29
#> 2 C-3PO          Tatooine     167    21
#> 3 R2-D2          Naboo         96     5
#> 4 Darth Vader    Tatooine     202    72
#> # … with 83 more rows

# Rank per homeworld
starwars %>% 
  select(name, homeworld, height) %>% 
  group_by(homeworld) %>% 
  mutate(rank = min_rank(height))
#> # A tibble: 87 x 4
#> # Groups:   homeworld [49]
#>   name           homeworld height  rank
#>   <chr>          <chr>      <int> <int>
#> 1 Luke Skywalker Tatooine     172     5
#> 2 C-3PO          Tatooine     167     4
#> 3 R2-D2          Naboo         96     1
#> 4 Darth Vader    Tatooine     202    10
#> # … with 83 more rows

filter()

グループ化された filter() は、効果的に mutate() を実行して論理変数を生成し、その変数が TRUE である行のみを保持する。 これはグループ化されたフィルタが要約関数と一緒に使えることを意味する。 例えば、それぞれの speicies の最も背の高いキャラクターを見つけることができる。

by_species %>%
  select(name, species, height) %>% 
  filter(height == max(height))
#> # A tibble: 35 x 3
#> # Groups:   species [35]
#>   name                  species        height
#>   <chr>                 <chr>           <int>
#> 1 Greedo                Rodian            173
#> 2 Jabba Desilijic Tiure Hutt              175
#> 3 Yoda                  Yoda's species     66
#> 4 Bossk                 Trandoshan        190
#> # … with 31 more rows

また、filter() を使ってグループ全体を削除することもできます。例えば、次のコードでは、メンバーが一人しかいないグループをすべて削除しています。

by_species %>%
  filter(n() != 1) %>% 
  tally()
#> # A tibble: 9 x 2
#>   species      n
#>   <chr>    <int>
#> 1 Droid        6
#> 2 Gungan       3
#> 3 Human       35
#> 4 Kaminoan     2
#> # … with 5 more rows

slice() とその仲間

slice() とその仲間(slice_head(), slice_tail(), slice_sample(), slice_min(), slice_max())は、グループ内の行を選択します。 例えば、それぞれの speicies の中の最初の観測値を選択することができます。

by_species %>%
  relocate(species) %>% 
  slice(1)
#> # A tibble: 38 x 14
#> # Groups:   species [38]
#>   species  name    height  mass hair_color skin_color eye_color birth_year sex  
#>   <chr>    <chr>    <int> <dbl> <chr>      <chr>      <chr>          <dbl> <chr>
#> 1 Aleena   Ratts …     79    15 none       grey, blue unknown           NA male 
#> 2 Besalisk Dexter…    198   102 none       brown      yellow            NA male 
#> 3 Cerean   Ki-Adi…    198    82 white      pale       yellow            92 male 
#> 4 Chagrian Mas Am…    196    NA none       blue       blue              NA male 
#> # … with 34 more rows, and 5 more variables: gender <chr>, homeworld <chr>,
#> #   films <list>, vehicles <list>, starships <list>

同様に,変数の最小の n 個の値を選択するには,slice_min() を用いることができます.

by_species %>%
  filter(!is.na(height)) %>% 
  slice_min(height, n = 2)
#> # A tibble: 48 x 14
#> # Groups:   species [38]
#>   name      height  mass hair_color skin_color eye_color birth_year sex   gender
#>   <chr>      <int> <dbl> <chr>      <chr>      <chr>          <dbl> <chr> <chr> 
#> 1 Ratts Ty…     79    15 none       grey, blue unknown           NA male  mascu…
#> 2 Dexter J…    198   102 none       brown      yellow            NA male  mascu…
#> 3 Ki-Adi-M…    198    82 white      pale       yellow            92 male  mascu…
#> 4 Mas Amed…    196    NA none       blue       blue              NA male  mascu…
#> # … with 44 more rows, and 5 more variables: homeworld <chr>, species <chr>,
#> #   films <list>, vehicles <list>, starships <list>

グループ化された情報の計算

dplyrの動詞の中では、cur_ という接頭辞を持つ関数群を使って、「現在の」グループの様々なプロパティにアクセスすることができます。 これらの関数は、通常、dplyr の日常的な使用に必要ですが、dplyr の動詞の典型的な制約から解放されるので便利です。

cur_data()

cur_data() は、現在のグループを、グループ変数を除いて返します。 データフレーム全体を取得する関数に与えると便利です。 例えば、以下のコードは、各種族に mass ~ height の線形モデルをフィットさせます。

by_species %>%
  filter(n() > 1) %>% 
  mutate(mod = list(lm(mass ~ height, data = cur_data())))
#> # A tibble: 58 x 15
#> # Groups:   species [9]
#>   name     height  mass hair_color skin_color  eye_color birth_year sex   gender
#>   <chr>     <int> <dbl> <chr>      <chr>       <chr>          <dbl> <chr> <chr> 
#> 1 Luke Sk…    172    77 blond      fair        blue            19   male  mascu…
#> 2 C-3PO       167    75 <NA>       gold        yellow         112   none  mascu…
#> 3 R2-D2        96    32 <NA>       white, blue red             33   none  mascu…
#> 4 Darth V…    202   136 none       white       yellow          41.9 male  mascu…
#> # … with 54 more rows, and 6 more variables: homeworld <chr>, species <chr>,
#> #   films <list>, vehicles <list>, starships <list>, mod <list>

cur_group()cur_group_id() です。

cur_group_id() は、現在のグループにユニークな数値-を与えます。これは、外部のデータ構造にインデックスを作成したい場合に役立つことがあります。

by_species %>%
  arrange(species) %>% 
  select(name, species, homeworld) %>% 
  mutate(id = cur_group_id())
#> # A tibble: 87 x 4
#> # Groups:   species [38]
#>   name            species  homeworld      id
#>   <chr>           <chr>    <chr>       <int>
#> 1 Ratts Tyerell   Aleena   Aleen Minor     1
#> 2 Dexter Jettster Besalisk Ojom            2
#> 3 Ki-Adi-Mundi    Cerean   Cerea           3
#> 4 Mas Amedda      Chagrian Champala        4
#> # … with 83 more rows

  1. なお、dplyr 1.0.0では、引数が add = TRUE から .add = TRUE に変更されました。↩︎