更新(2019/02/19):
いろいろ議論があって、group_by()
のデフォルトの挙動はこれまでと同じ(empty groupはつくらない)ようになりました。
group_by()
するときに.drop = FALSE
とした場合だけ、empty groupがつくられます。
dplyr 0.8.0がもうすぐ(来年1月上旬?)リリースされます。
この公式記事を読んでおけばいいかと思ったんですが、今回は、group_by()
に大きな変更がいくつもあります。
まじで大きな変更なので、「もう俺たちが知っているgroup_by()
じゃない」くらいのつもりで臨みましょう。
便利な新機能もあるんですが、それは日を改める*1ことにして、
breaking changeに絞って書きます。
factor
の扱い
今のdplyrは、factor
を文字列と同じように扱って、そのベクトル中に存在する値を見てグループをつくります。
一方、0.8.0からは、ベクトル中に存在しないfactor
のlevelも含めてグループをつくることもできるようになります。
例えば、以下の例ではx
列にc
というデータはありませんが、factor
のlevelとして存在しているので.drop = FALSE
を指定するとc
のグループができます。
summarise()
の結果にc
も登場しているのに注目してください。
library(dplyr, warn.conflicts = FALSE) d <- data.frame(x = factor(c("a", "b", "b"), levels = c("a", "b", "c")), val = 1:3) d %>% group_by(x) %>% summarise(n = n(), mean = mean(val)) #> # A tibble: 2 x 3 #> x n mean #> <fct> <int> <dbl> #> 1 a 1 1 #> 2 b 2 2.5 d %>% group_by(x, .drop = FALSE) %>% summarise(n = n(), mean = mean(val)) #> # A tibble: 3 x 3 #> x n mean #> <fct> <int> <dbl> #> 1 a 1 1 #> 2 b 2 2.5 #> 3 c 0 NaN
あと、もう一つ注目すべきは、c
グループのmean
がNaN
になっていることです。
これは、mean(numeric(0))
の結果がNaN
なのでこうなります。
このように、長さゼロのベクトルをうまく扱えない集計関数もあるので、
そのあたりで予期せぬエラーが起こるかもしれません。注意しましょう。
グループ情報の持ち方
これまで、group_by()
によってつくられるグループ化されたデータフレーム(grouped_df
)は、
グループ化に使う列名をメタデータとして持っていました。
mutate()
やfilter()
などを実行するたびにグループ分けが動的に計算されます。
一方、0.8.0からはgroup_by()
した時点のグループを保持し続けることもできます。
保持するかどうかは.preserve
という引数で選びます。
どのような挙動か試してみましょう。grouped_df
が持っているグループ情報はgroup_data()
を使うと見ることができます(他にもいろいろ関数がありますが後述)。
d <- data.frame(group_id = c(1L, 1L, 2L, 2L), value = 1:4) g <- d %>% group_by(group_id) group_data(g) #> # A tibble: 2 x 2 #> group_id .rows #> <int> <list> #> 1 1 <int [2]> #> 2 2 <int [2]>
.rows
というのはそのグループに所属する行です。
さて、このデータからgroup_id
が1
の行をfilter()
で取り除くとグループ情報がどう変わるか見てみましょう。
g1 <- g %>% filter(value >= 3) group_data(g1) #> # A tibble: 1 x 2 #> group_id .rows #> <int> <list> #> 1 2 <int [2]> g2 <- g %>% filter(value >= 3, .preserve = TRUE) group_data(g2) #> # A tibble: 2 x 2 #> group_id .rows #> <int> <list> #> 1 1 <int [0]> #> 2 2 <int [2]>
.preserve = TRUE
を指定した方は<int [0]>
と表示されているように、もうこのグループに所属する行ありません。
にも関わらず、グループとしては存在し続けています。
filter()
とslice()
の結果の順序
これは公式記事のやつがわかりやすかったので似たようなコードを載せておきます。
x
でグループ化したgrouped_df
にfilter()
やslice()
を使うと、行がグループごとに寄せられた結果になります。
d <- tibble( x = c(1, 2, 1, 2, 1, 2), y = c(1, 100, 3, 10, 5, 1) ) d #> # A tibble: 6 x 2 #> x y #> <dbl> <dbl> #> 1 1 1 #> 2 2 100 #> 3 1 3 #> 4 2 10 #> 5 1 5 #> 6 2 1 d %>% group_by(x) %>% slice(1:2) #> # A tibble: 4 x 2 #> # Groups: x [2] #> x y #> <dbl> <dbl> #> 1 1 1 #> 2 1 3 #> 3 2 100 #> 4 2 10
グループ内の順序は入れ替わったりしないので通常は問題にならないと思いますが、
グループごとの計算結果で行を絞り込む(グループ内の順位がn位以上の行、とか)処理をしている場合は注意が必要です。
行の順序が問題になるような処理をする場合は、ちゃんと直前でarrange()
するようにしましょう。
*1:つまり・・・・我々がその気になればこの記事を書くのは10年20年後ということも可能だろう・・・ということ・・・・!