R dplyr, tidyr でのグルーピング/集約/変換処理まとめ
これの続き。よく使う集約/変換処理もまとめておく。
準備
library(dplyr) library(tidyr) (df <- dplyr::tbl_df(iris)) # Source: local data frame [150 x 5] # # Sepal.Length Sepal.Width Petal.Length Petal.Width Species # 1 5.1 3.5 1.4 0.2 setosa # 2 4.9 3.0 1.4 0.2 setosa # 3 4.7 3.2 1.3 0.2 setosa # .. ... ... ... ... ...
グルーピング/集約
ある列の値ごとに集計
Species 列ごとに Sepal.Length 列の合計を算出する場合、
df %>% dplyr::group_by(Species) %>% dplyr::summarise(Sepal.Length.Total = sum(Sepal.Length)) # Source: local data frame [3 x 2] # # Species Sepal.Length.Total # 1 setosa 250.3 # 2 versicolor 296.8 # 3 virginica 329.4 # Standard Evaluation df %>% dplyr::group_by_('Species') %>% dplyr::summarise_('Sepal.Length.Total' = 'sum(Sepal.Length)') # 略
同一の集計を 複数列に対して行う場合は dplyr::summarise_each
が便利。全列の合計を取得するなら、
df %>% dplyr::group_by(Species) %>% dplyr::summarise_each(dplyr::funs(sum)) # Source: local data frame [3 x 5] # # Species Sepal.Length Sepal.Width Petal.Length Petal.Width # 1 setosa 250.3 171.4 73.1 12.3 # 2 versicolor 296.8 138.5 213.0 66.3 # 3 virginica 329.4 148.7 277.6 101.3
vars
引数で集計対象列を指定することもできる。通常版 (Non-standard evaluation/NSE) と Standard evaluation で列名のつき方が違う、、、。
df %>% dplyr::group_by(Species) %>% dplyr::summarise_each(dplyr::funs(sum), vars = c(Petal.Width = Petal.Width, Petal.Length = Petal.Length)) # Source: local data frame [3 x 3] # # Species vars.Petal.Width vars.Petal.Length # 1 setosa 12.3 73.1 # 2 versicolor 66.3 213.0 # 3 virginica 101.3 277.6 # Standard evaluation df %>% dplyr::group_by_('Species') %>% dplyr::summarise_each_(dplyr::funs(sum), vars = c('Petal.Width', 'Petal.Length')) # Source: local data frame [3 x 5] # # Species Petal.Width_sum Petal.Length_sum Petal.Width_mean Petal.Length_mean # 1 setosa 12.3 73.1 0.246 1.462 # 2 versicolor 66.3 213.0 1.326 4.260 # 3 virginica 101.3 277.6 2.026 5.552
関数名を文字列渡しする場合は dplyr::funs_
。
df %>% dplyr::group_by(Species) %>% dplyr::summarise_each(dplyr::funs_('sum'), vars=c(Petal.Width = Petal.Width, Petal.Length = Petal.Length)) # 略 # Standard evaluation df %>% dplyr::group_by_('Species') %>% dplyr::summarise_each_(dplyr::funs_('sum'), vars=c('Petal.Width', 'Petal.Length')) # 略
また、集約関数は複数渡すこともできる。
df %>% dplyr::group_by(Species) %>% dplyr::summarise_each(funs = c(dplyr::funs(sum), dplyr::funs(mean)), vars=c(Petal.Width = Petal.Width, Petal.Length = Petal.Length)) # Source: local data frame [3 x 5] # # Species vars.Petal.Width_sum vars.Petal.Length_sum vars.Petal.Width_mean vars.Petal.Length_mean # 1 setosa 12.3 73.1 0.246 1.462 # 2 versicolor 66.3 213.0 1.326 4.260 # 3 virginica 101.3 277.6 2.026 5.552 # Standard evaluation df %>% dplyr::group_by_('Species') %>% dplyr::summarise_each_(funs = c(dplyr::funs(sum), dplyr::funs(mean)), vars=c('Petal.Width', 'Petal.Length')) # Source: local data frame [3 x 5] # # Species Petal.Width_sum Petal.Length_sum Petal.Width_mean Petal.Length_mean # 1 setosa 12.3 73.1 0.246 1.462 # 2 versicolor 66.3 213.0 1.326 4.260 # 3 virginica 101.3 277.6 2.026 5.552
NSEの dplyr::groupby
に文字列を渡した場合、特にエラーが出ず全集約されるので注意。
# NG! df %>% dplyr::group_by('Species') %>% dplyr::summarise_each_(funs = c(dplyr::funs(sum), dplyr::funs(mean)), vars=c('Petal.Width', 'Petal.Length')) # Source: local data frame [1 x 5] # # "Species" Petal.Width_sum Petal.Length_sum Petal.Width_mean Petal.Length_mean # 1 Species 179.9 563.7 1.199333 3.758
行持ち / 列持ち変換
複数列持ちの値を行持ちに展開 (unpivot / melt)
複数列で持っている値を行持ちに展開する処理は、reshape2::melt
から名前が変わって tidyr::gather
になった。この処理の名前として一般的なのは unpivot ではないのか、、。
tidyr
でも Standard evaluationが使える。
df %>% tidyr::gather(variable, value, c(Sepal.Length, Sepal.Width, Petal.Length, Petal.Width)) # Source: local data frame [600 x 3] # # Species variable value # 1 setosa Sepal.Length 5.1 # 2 setosa Sepal.Length 4.9 # 3 setosa Sepal.Length 4.7 # .. ... ... ... # Standard evaluation df %>% tidyr::gather_('variable', 'value', c('Sepal.Length', 'Sepal.Width', 'Petal.Length', 'Petal.Width')) # 略
複数行持ちの値を列持ちに変換 (pivot)
tidyr::spread
。spread
では集計処理は入らない (行列の入替のみ) ので、まず 行/列として展開する 2列がユニークとなるサンプルデータを用意する。
# spreadへの入力データ。Species (列にする値) と variable (行にする値) の組がユニークでないとダメ。 (unpivot <- df %>% tidyr::gather(variable, value, c(Sepal.Length, Sepal.Width, Petal.Length, Petal.Width)) %>% dplyr::group_by(Species, variable) %>% dplyr::summarise(Total = sum(value))) # Source: local data frame [12 x 3] # Groups: Species # # Species variable Total # 1 setosa Sepal.Length 250.3 # 2 setosa Sepal.Width 171.4 # 3 setosa Petal.Length 73.1 # 4 setosa Petal.Width 12.3 # 5 versicolor Sepal.Length 296.8 # 6 versicolor Sepal.Width 138.5 # 7 versicolor Petal.Length 213.0 # 8 versicolor Petal.Width 66.3 # 9 virginica Sepal.Length 329.4 # 10 virginica Sepal.Width 148.7 # 11 virginica Petal.Length 277.6 # 12 virginica Petal.Width 101.3 unpivot %>% data.frame %>% tidyr::spread(Species, Total) # variable setosa versicolor virginica # 1 Sepal.Length 250.3 296.8 329.4 # 2 Sepal.Width 171.4 138.5 148.7 # 3 Petal.Length 73.1 213.0 277.6 # 4 Petal.Width 12.3 66.3 101.3 # Standard evaluation unpivot %>% data.frame %>% tidyr::spread_('Species', 'Total') # 略
(たぶんバグだと思うが) grouped_df
を spread
に渡すと以下の通りエラーになるので、上では spread
の前に data.frame
への変換を挟んでいる。
→ 10/16 追記: バグでした。dplyr
3.1 で修正予定。
class(unpivot) # [1] "grouped_df" "tbl_df" "tbl" "data.frame" # NG! unpivot %>% tidyr::spread(Species, Total) # エラー: index out of bounds
列の分割 / 結合
列の値を複数列に分割
上で作ったデータから variable 列の値をドットで区切って二列に分けたい、なんてときには tidyr::separate
。NSE の場合も into
に渡す値は文字列 vector
でないとダメっぽい (新しい列名を作る処理になるからかなあ、 dplyr::rename
では作成する列名もシンボルで渡せるのに)。
df %>% tidyr::gather(variable, value, c(Sepal.Length, Sepal.Width, Petal.Length, Petal.Width)) %>% tidyr::separate(variable, into = c('Parts', 'Scale'), sep='\\.') # Source: local data frame [600 x 4] # # Species Parts Scale value # 1 setosa Sepal Length 5.1 # 2 setosa Sepal Length 4.9 # 3 setosa Sepal Length 4.7 # .. ... ... ... ... # Standard evaluation df %>% tidyr::gather_('variable', 'value', c('Sepal.Length', 'Sepal.Width', 'Petal.Length', 'Petal.Width')) %>% tidyr::separate_('variable', into = c('Parts', 'Scale'), sep='\\.') # 略
正規表現でグルーピングした値で列分割したい場合は tidyr::extract
。
df %>% tidyr::gather(variable, value, c(Sepal.Length, Sepal.Width, Petal.Length, Petal.Width)) %>% tidyr::extract(variable, into = c('Parts', 'Scale'), regex='(.+)\\.(.+)') # Source: local data frame [600 x 4] # # Species Parts Scale value # 1 setosa Sepal Length 5.1 # 2 setosa Sepal Length 4.9 # 3 setosa Sepal Length 4.7 # .. ... ... ... ... # Standard evaluation df %>% tidyr::gather_('variable', 'value', c('Sepal.Length', 'Sepal.Width', 'Petal.Length', 'Petal.Width')) %>% tidyr::extract_('variable', into = c('Parts', 'Scale'), regex='(.+)\\.(.+)') # 略
複数列の値を一列に結合
tidyr::separate
と逆の操作はtidyr::unite
でできる。
df %>% tidyr::gather(variable, value, c(Sepal.Length, Sepal.Width, Petal.Length, Petal.Width)) %>% tidyr::separate(variable, into = c('Parts', 'Scale'), sep='\\.') %>% tidyr::unite('variable', c(Parts, Scale), sep='.') # Source: local data frame [600 x 3] # # Species variable value # 1 setosa Sepal.Length 5.1 # 2 setosa Sepal.Length 4.9 # 3 setosa Sepal.Length 4.7 # .. ... ... ... # Standard evaluation df %>% tidyr::gather_('variable', 'value', c('Sepal.Length', 'Sepal.Width', 'Petal.Length', 'Petal.Width')) %>% tidyr::separate_('variable', into = c('Parts', 'Scale'), sep='\\.') %>% tidyr::unite_('variable', c('Parts', 'Scale'), sep='.') # 略