Python pandas でのグルーピング/集約/変換処理まとめ
これの pandas 版。
準備
サンプルデータは iris で。
補足 (11/26追記) rpy2
を設定している方は rpy2
から、そうでない方は こちら から .csv
でダウンロードして読み込み (もしくは read_csv
のファイルパスとして直接 URL 指定しても読める)。
import pandas as pd import numpy as np # 表示する行数を設定 pd.options.display.max_rows=5 # iris の読み込みはどちらかで # rpy2 経由で R から iris をロード # import pandas.rpy.common as com # iris = com.load_data('iris') # csv から読み込み # http://aima.cs.berkeley.edu/data/iris.csv names = ['Sepal.Length', 'Sepal.Width', 'Petal.Length', 'Petal.Width', 'Species'] iris = pd.read_csv('iris.csv', header=None, names=names) iris # 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 # .. ... ... ... ... ... # 149 6.2 3.4 5.4 2.3 virginica # 150 5.9 3.0 5.1 1.8 virginica
グルーピング/集約
ある列の値ごとに集計
Species 列ごとに Sepal.Length 列の合計を算出する場合、
iris.groupby('Species')['Sepal.Length'].sum() # Species # setosa 250.3 # versicolor 296.8 # virginica 329.4 # Name: Sepal.Length, dtype: float64
全列の合計を取得する場合 DataFrame.groupby
から直接集約関数を呼べばよい。集約できない列は勝手にフィルタされる。
iris.groupby('Species').sum() # Sepal.Length Sepal.Width Petal.Length Petal.Width # Species # setosa 250.3 171.4 73.1 12.3 # versicolor 296.8 138.5 213.0 66.3 # virginica 329.4 148.7 277.6 101.3
集約対象列の指定は DataFrame
の列選択と同じ。すっきり。
iris.groupby('Species')[['Petal.Width', 'Petal.Length']].sum() # Petal.Width Petal.Length # Species # setosa 12.3 73.1 # versicolor 66.3 213.0 # virginica 101.3 277.6
メソッド呼び出しではなく、別に用意された集約関数を渡したい場合は .apply
。文字列で渡したいときは 渡す際に eval
。
iris.groupby('Species')[['Petal.Width', 'Petal.Length']].apply(np.sum) # Petal.Width Petal.Length # Species # setosa 12.3 73.1 # versicolor 66.3 213.0 # virginica 101.3 277.6 iris.groupby('Species')[['Petal.Width', 'Petal.Length']].apply(eval('np.sum')) # Petal.Width Petal.Length # Species # setosa 12.3 73.1 # versicolor 66.3 213.0 # virginica 101.3 277.6
また、集約関数を複数渡したい場合は .agg
。列名 : 集約関数の辞書を渡すので、列ごとに集約関数を変えることもできる。
iris.groupby('Species').agg({'Petal.Length': [np.sum, np.mean], 'Petal.Width': [np.sum, np.mean]}) # Petal.Length Petal.Width # sum mean sum mean # Species # setosa 73.1 1.462 12.3 0.246 # versicolor 213.0 4.260 66.3 1.326 # virginica 277.6 5.552 101.3 2.026
行持ち / 列持ち変換
複数列持ちの値を行持ちに展開 (unpivot / melt)
複数列で持っている値を行持ちに展開する処理は、pd.melt
。 DataFrame.melt
ではないので注意。
melted = pd.melt(iris, id_vars=['Species'], var_name='variable', value_name='value') melted # Species variable value # 0 setosa Sepal.Length 5.1 # 1 setosa Sepal.Length 4.9 # .. ... ... ... # 598 virginica Petal.Width 2.3 # 599 virginica Petal.Width 1.8 # # [600 rows x 3 columns]
複数行持ちの値を列持ちに変換 (pivot)
DataFrame.pivot
。集約処理付きの別関数 pd.pivot_table
もある。
# pivotするデータの準備。Species (列にする値) と variable (行にする値) の組がユニークでないとダメ。 unpivot = melted.groupby(['Species', 'variable']).sum() unpivot = unpivot.reset_index() unpivot # Species variable value # 0 setosa Petal.Length 73.1 # 1 setosa Petal.Width 12.3 # .. ... ... ... # 10 virginica Sepal.Length 329.4 # 11 virginica Sepal.Width 148.7 # # [12 rows x 3 columns] unpivot.pivot(index='variable', columns='Species', values='value') # Species setosa versicolor virginica # variable # Petal.Length 73.1 213.0 277.6 # Petal.Width 12.3 66.3 101.3 # Sepal.Length 250.3 296.8 329.4 # Sepal.Width 171.4 138.5 148.7
列の分割 / 結合
列の値を複数列に分割
pandas
には tidyr::separate
に直接対応する処理はない。.str.split
では分割された文字列が一つの列にリストとして格納されてしまう。そのため、分割結果のリストを個々の列に格納しなおす必要がある。
2014/11/17修正: v0.15.1 以降では str.split
の return_type='frame'
オプションを利用して簡単にできるようになったので修正。既定 ( return_type='series'
)では、split
されたリストが object
型として 1列に保存されてしまうので注意。
2015/01/16追記: v0.16.1 以降では return_type
オプションが deprecate され、 expand
オプションに置き換えられた。expand=True
を指定すれば同様の処理ができる。
melted2 = melted.copy() melted2 # Species variable value # 0 setosa Sepal.Length 5.1 # 1 setosa Sepal.Length 4.9 # .. ... ... ... # 598 virginica Petal.Width 2.3 # 599 virginica Petal.Width 1.8 # # [600 rows x 3 columns] melted2[['Parts', 'Scale']] = melted2['variable'].str.split('.', return_type ='frame') melted2 # Species variable value Parts Scale # 0 setosa Sepal.Length 5.1 Sepal Length # 1 setosa Sepal.Length 4.9 Sepal Length # .. ... ... ... ... ... # 598 virginica Petal.Width 2.3 Petal Width # 599 virginica Petal.Width 1.8 Petal Width # # [600 rows x 5 columns] # 不要な列を削除 melted2.drop('variable', axis=1) # Species value Parts Scale # 0 setosa 5.1 Sepal Length # 1 setosa 4.9 Sepal Length # .. ... ... ... ... # 598 virginica 2.3 Petal Width # 599 virginica 1.8 Petal Width # # [600 rows x 4 columns]
.str.extract
で正規表現を使ってもできる。
melted3 = melted.copy() melted3[['Parts', 'Scale']] = melted3['variable'].str.extract('(.+)\.(.+)') melted3 = melted3.drop('variable', axis=1) melted3 # Species value Parts Scale # 0 setosa 5.1 Sepal Length # 1 setosa 4.9 Sepal Length # .. ... ... ... ... # 598 virginica 2.3 Petal Width # 599 virginica 1.8 Petal Width # # [600 rows x 4 columns]
複数列の値を一列に結合
普通に文字列結合すればよい。
melted3['variable'] = melted3['Parts'] + '.' + melted3['Scale'] melted3 # Species value Parts Scale variable # 0 setosa 5.1 Sepal Length Sepal.Length # 1 setosa 4.9 Sepal Length Sepal.Length # .. ... ... ... ... ... # 598 virginica 2.3 Petal Width Petal.Width # 599 virginica 1.8 Petal Width Petal.Width # # [600 rows x 5 columns]
Pythonによるデータ分析入門 ―NumPy、pandasを使ったデータ処理
- 作者: Wes McKinney,小林儀匡,鈴木宏尚,瀬戸山雅人,滝口開資,野上大介
- 出版社/メーカー: オライリージャパン
- 発売日: 2013/12/26
- メディア: 大型本
- この商品を含むブログ (9件) を見る
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='.') # 略
簡単なデータ操作を Python pandas で行う
先ほどの R の記事と同じ操作を Python pandas でやる。
Python の場合は Rのようなシンボルの概念がないので、変数が評価される環境を意識する必要が(あまり)ない。
準備
サンプルデータは iris で。
補足 (11/26追記) rpy2
を設定している方は rpy2
から、そうでない方は こちら から .csv
でダウンロードして読み込み (もしくは read_csv
のファイルパスとして直接 URL 指定しても読める)。
import pandas as pd # 表示する行数を設定 pd.options.display.max_rows=5 # iris の読み込みはどちらかで # rpy2 経由で R から iris をロード # import pandas.rpy.common as com # iris = com.load_data('iris') # csv から読み込み # http://aima.cs.berkeley.edu/data/iris.csv names = ['Sepal.Length', 'Sepal.Width', 'Petal.Length', 'Petal.Width', 'Species'] iris = pd.read_csv('iris.csv', header=None, names=names) iris # 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 # .. ... ... ... ... ... # 149 6.2 3.4 5.4 2.3 virginica # 150 5.9 3.0 5.1 1.8 virginica
列操作
列名操作
参照と変更。変更前後の列名は辞書で渡すので、可変長でも楽。
iris.columns # Index([u'Sepal.Length', u'Sepal.Width', u'Petal.Length', u'Petal.Width', u'Species', u'Petal.Mult'], dtype='object') iris.rename(columns={'Species': 'newcol'}) # Sepal.Length Sepal.Width Petal.Length Petal.Width newcol # 1 5.1 3.5 1.4 0.2 setosa # 2 4.9 3.0 1.4 0.2 setosa # .. ... ... ... ... ... # 149 6.2 3.4 5.4 2.3 virginica # 150 5.9 3.0 5.1 1.8 virginica
変数名を用いて列選択
iris['Species'] # 1 setosa # ... # 150 virginica # Name: Species, Length: 150, dtype: object
文字列リストを用いて、複数列を選択する
iris[['Petal.Length', 'Petal.Width']] # Petal.Length Petal.Width # 1 1.4 0.2 # 2 1.4 0.2 # .. ... ... # 149 5.4 2.3 # 150 5.1 1.8 # # [150 rows x 2 columns]
真偽値リストを用いて列選択する
これは R のほうがシンプル。
iris.loc[:,[False, False, True, True, False]] # Petal.Length Petal.Width # 1 1.4 0.2 # 2 1.4 0.2 # .. ... ... # 149 5.4 2.3 # 150 5.1 1.8 # # [150 rows x 2 columns]
列の属性/値が特定の条件に該当する列を選択する
型が float
の列のみ取り出す。
iris.loc[:,iris.dtypes == float] # Sepal.Length Sepal.Width Petal.Length Petal.Width # 1 5.1 3.5 1.4 0.2 # 2 4.9 3.0 1.4 0.2 # .. ... ... ... ... # 149 6.2 3.4 5.4 2.3 # 150 5.9 3.0 5.1 1.8 # # [150 rows x 4 columns]
public ではないが数値型の列のみ取り出すメソッドもある。
iris._get_numeric_data() # Sepal.Length Sepal.Width Petal.Length Petal.Width Petal.Mult # 1 5.1 3.5 1.4 0.2 0.28 # 2 4.9 3.0 1.4 0.2 0.28 # .. ... ... ... ... ... # 149 6.2 3.4 5.4 2.3 12.42 # 150 5.9 3.0 5.1 1.8 9.18 # # [150 rows x 5 columns]
行操作
値が特定の条件を満たす行を抽出する
iris[iris['Species'] == 'virginica'] # Sepal.Length Sepal.Width Petal.Length Petal.Width Species # 101 6.3 3.3 6.0 2.5 virginica # 102 5.8 2.7 5.1 1.9 virginica # .. ... ... ... ... ... # 149 6.2 3.4 5.4 2.3 virginica # 150 5.9 3.0 5.1 1.8 virginica # # [50 rows x 5 columns]
特定の行番号を抽出する
iris.loc[[2, 3, 4]] # Sepal.Length Sepal.Width Petal.Length Petal.Width Species # 2 4.9 3.0 1.4 0.2 setosa # 3 4.7 3.2 1.3 0.2 setosa # 4 4.6 3.1 1.5 0.2 setosa
ランダムサンプリングしたい場合は index
をサンプリングしてスライス。
import random iris.loc[random.sample(iris.index, 5)] # Sepal.Length Sepal.Width Petal.Length Petal.Width Species # 64 6.1 2.9 4.7 1.4 versicolor # 17 5.4 3.9 1.3 0.4 setosa # 14 4.3 3.0 1.1 0.1 setosa # 4 4.6 3.1 1.5 0.2 setosa # 146 6.7 3.0 5.2 2.3 virginica
代入
iris['Petal.Mult'] = iris['Petal.Width'] * iris['Petal.Length'] iris # 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 # .. ... ... ... ... ... # 149 6.2 3.4 5.4 2.3 virginica # 150 5.9 3.0 5.1 1.8 virginica # # Petal.Mult # 1 0.28 # 2 0.28 # .. ... # 149 12.42 # 150 9.18 # # [150 rows x 6 columns]
まとめ
pandas
いいですよ。
Pythonによるデータ分析入門 ―NumPy、pandasを使ったデータ処理
- 作者: Wes McKinney,小林儀匡,鈴木宏尚,瀬戸山雅人,滝口開資,野上大介
- 出版社/メーカー: オライリージャパン
- 発売日: 2013/12/26
- メディア: 大型本
- この商品を含むブログ (9件) を見る
Rの data.table と data.frame を dplyr で区別なく扱う
R を使っていると、組み込み型の data.frame
と大規模データ用パッケージである data.table の差異で思わずはまることがあるので使い方をまとめる。どちらか一方しか使わないようにすれば 差異を気にする必要はないのかも知れないが、、。
基本的には
データ操作用パッケージ dplyr が data.frame
と data.table
両方に対して同じように使えるので、できるだけ dplyr
を使って操作するのがよい。
ある程度 複雑な操作であれば最初から dplyr
を使うと思うが、列選択, 行選択, 代入など 比較的シンプルな操作はつい 通常の書式で書いてしまう (そしてはまる、、)。また、列名を文字列に入れて処理するなど、dplyr
0.2以前では(シンプルには)書けない処理もあった。
dplyr
0.3でこのあたりの処理が素直に書けるようになっているので、その方法と 通常の記法で書いた場合にひっかかるポイントをまとめてみた。
まずdplyrのwrapperに渡す
何か処理を始める前には、data.frame
, data.table
を dplyr
で定義された wrapperオブジェクトに渡すこと。それぞれ、dplyr::tbl_df
, dplyr::tbl_dt
を通しておけば、表示フォーマットなんかをあわせてくれる。特に tbl_df
は data.frame
の表示を既定で省略/列選択時の挙動を一貫したものにしてくれる(下記 補足参照)ので必須。
※代入式を括弧で囲んでいるのは 代入 + printするため。通常は必要ない。
(df <- dplyr::tbl_df(iris)) # Source: local data frame [150 x 5] # # Sepal.Length Sepal.Width Petal.Length Petal.Width # 1 5.1 3.5 1.4 0.2 # 2 4.9 3.0 1.4 0.2 # 3 4.7 3.2 1.3 0.2 # .. ... ... ... ... (dt <- dplyr::tbl_dt(data.table::data.table(iris))) # Source: local data table [150 x 5] # # Sepal.Length Sepal.Width Petal.Length Petal.Width # 1 5.1 3.5 1.4 0.2 # 2 4.9 3.0 1.4 0.2 # 3 4.7 3.2 1.3 0.2 # .. ... ... ... ...
以降のサンプルで、 df
は data.frame
, dt
は data.table
をあらわす。また、返ってくるデータ形式に特に差異がない場合の出力は適宜 省略する。
補足: data.frame
では 列選択時に結果が 一列になった場合は vector
, 複数列の場合は data.frame
を返すという謎の挙動をする。tbl_df
はこれを抑制し、常に data.frame
を返してくれる (v0.3.0以降)。
# 素の data.frame # 結果が複数列の場合は data.frame iris[, c(FALSE, FALSE, FALSE, TRUE, TRUE)] # Petal.Width Species # 1 0.2 setosa # 2 0.2 setosa # 3 0.2 setosa # 結果が一列の場合に vectorになる iris[, c(FALSE, FALSE, FALSE, FALSE, TRUE)] # [1] setosa setosa setosa setosa setosa setosa setosa setosa # ... # Levels: setosa versicolor virginica # tbl_df なら 結果が一列でも data.frame df[, c(FALSE, FALSE, FALSE, FALSE, TRUE)] # Source: local data frame [150 x 1] # # Species # 1 setosa # 2 setosa # 3 setosa # .. ... # data.tableは一貫して data.table data.table(iris)[, c(FALSE, FALSE, FALSE, FALSE, TRUE), with = FALSE] # 略 (data.table) dt[, c(FALSE, FALSE, FALSE, FALSE, TRUE), with = FALSE] # 略 (data.table)
補足2 一方、data.table
は [, ]
記法で 列名を変数名で指定した場合は vector
、文字列で指定した場合は data.table
と挙動が違うので注意。
dt[, Species] # [1] setosa setosa setosa setosa setosa setosa setosa setosa # ... # Levels: setosa versicolor virginica dt[, 'Species', with = FALSE] # Source: local data table [150 x 1] # # Species # 1 setosa # 2 setosa # 3 setosa # . ...
なんにせよ、一列を選択する処理では常に戻り値の型を意識しないと つい以下のような処理を書いてしまう。
# OK is.factor(dt[, Species]) # [1] TRUE # NG! is.factor(dt[, 'Species', with = FALSE]) # [1] FALSE
列操作
列名操作
参照は names
, colnames
が共通の動作をするのでどちらでもよい。
names(df) # [1] "Sepal.Length" "Sepal.Width" "Petal.Length" "Petal.Width" "Species" names(dt) # 略 colnames(df) # 略 colnames(dt) # 略
列名変更は dplyr::rename
(v0.3.0以降)。
dplyr::rename(df, newcol = Species) # Source: local data frame [150 x 5] # # Sepal.Length Sepal.Width Petal.Length Petal.Width newcol # 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 # .. ... ... ... ... ... dplyr::rename(dt, newcol = Species) # 略
列名を文字列で渡したい場合は dplyr::rename_
。rename_
は rename
の Standard evaluation 版。(select_
, group_by_
など、様々な dplyr
関数にv0.3.0で追加された)で、渡した文字列を関数/列名として評価するもの。
dplyr::rename_(df, 'newcol' = 'Species') # 略 dplyr::rename_(dt, 'newcol' = 'Species') # 略
すべての列名をまとめて変更したい場合は上の記法で個々に指定してもよいが、 dplyr::rename_(.dots)
を使うと便利。dots
に渡したリストは引数として展開された後、Standard Evaluationされる。この記法で可変長の文字列引数を dplyr
へ渡せる ( Python でいうと **kwargs
記法のような感じ )。
# .dotsに引数として渡すリスト setNames(names(df), c('col1', 'col2', 'col3', 'col4', 'col5')) # col1 col2 col3 col4 col5 # "Sepal.Length" "Sepal.Width" "Petal.Length" "Petal.Width" "Species" dplyr::rename_(df, .dots = setNames(names(df), c('col1', 'col2', 'col3', 'col4', 'col5'))) # Source: local data frame [150 x 5] # # col1 col2 col3 col4 col5 # 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 # .. ... ... ... ... ... dplyr::rename_(dt, .dots = setNames(names(dt), c('col1', 'col2', 'col3', 'col4', 'col5'))) # 略
補足 dplyr::rename
は data.table
に対して裏側で高速化された data.table::setnames
を使ってくれている。setnames
は破壊的だが rename
は非破壊的。
names(dt) # "Sepal.Length" "Sepal.Width" "Petal.Length" "Petal.Width" "Species" names(dplyr::rename(dt, newcol = Species)) # [1] "Sepal.Length" "Sepal.Width" "Petal.Length" "Petal.Width" "newcol" # rename適用しても元データは変更されない names(dt) # [1] "Sepal.Length" "Sepal.Width" "Petal.Length" "Petal.Width" "Species" # setnamesでは変更される data.table::setnames(dt, c('col1', 'col2', 'col3', 'col4', 'col5')) names(dt) # [1] "col1" "col2" "col3" "col4" "col5"
※ここで列名変更した場合、以降のスクリプトの前に列名を元に戻すこと。
変数名を用いて、列をベクトルとして選択する
dplyr::select
もしくは $
を使う。select
は戻り値が data.frame
もしくは data.table
になるので、結果を vector
としてほしい場合は列番号指定が必要なことに注意。
dplyr::select(df, Species) # Source: local data frame [150 x 1] # # Species # 1 setosa # 2 setosa # 3 setosa # .. ... dplyr::select(df, Species)[[1]] # [1] setosa setosa setosa setosa setosa setosa setosa setosa # ... # Levels: setosa versicolor virginica df$Species # [1] setosa setosa setosa setosa setosa setosa setosa setosa # ... # Levels: setosa versicolor virginica dt$Species # 略
補足 data.table
では[ , ]
記法の際に変数名で列選択できるが、data.frame
では使えない。
df[, Species] # Error in `[.tbl_df`(df, , Species) : object 'Species' not found dt[, Species] # [1] setosa setosa setosa setosa setosa setosa setosa setosa # ... # Levels: setosa versicolor virginica
補足2 data.frame[, ]
で変数名から列選択したいときは substitute %>% deparse
すればできる。ただし戻り値は data.frame
になるので、vector
がほしければ列指定する。
df[, deparse(substitute(Species))][[1]] # [1] setosa setosa setosa setosa setosa setosa setosa setosa # ... # Levels: setosa versicolor virginica
変数名を用いて、列を data.frame/data.table として選択する
dplyr::select
を使う。
dplyr::select(df, Petal.Length, Petal.Width) # Source: local data frame [150 x 2] # # Petal.Length Petal.Width # 1 1.4 0.2 # 2 1.4 0.2 # 3 1.3 0.2 # .. ... ... dplyr::select(dt, Petal.Length, Petal.Width) # 略
文字列を用いて、列をベクトルとして選択する
dplyr::select_
もしくは [[
。
dplyr::select_(df, 'Species')[[1]] # [1] setosa setosa setosa setosa setosa setosa setosa setosa # ... # Levels: setosa versicolor virginica df[['Species']] # 略 dt[['Species']] # 略
また、多少長くなってもよければ通常のselect
も使える (次セクション参照)。
dplyr::select(df, one_of(c('Species')))[[1]] # 略
文字列vectorを用いて、列をdata.frame/data.tableとして選択する
dplyr::select
+ one_of
。one_of
に渡しているものはデータと関係ない文字列vector
なので、select_
ではなくselect
でよい。
※以下の例の場合は stats_with
でもOK.
dplyr::select(df, one_of(c('Petal.Length', 'Petal.Width'))) # Source: local data frame [150 x 2] # # Petal.Length Petal.Width # 1 1.4 0.2 # 2 1.4 0.2 # 3 1.3 0.2 # .. ... ... dplyr::select(dt, one_of(c('Petal.Length', 'Petal.Width'))) # 略 dplyr::select(df, starts_with('Petal')) # 略
また、dplyr::select_(.dots)
を使って以下のようにも書ける。
dplyr::select_(df, .dots = c('Petal.Width', 'Petal.Length')) # 略 dplyr::select_(dt, .dots = c('Petal.Width', 'Petal.Length')) # 略
補足 data.frame
と data.table
それぞれの書式で書く場合は、data.table
で with = FALSE
を指定しないと そのまま文字列vector
として評価されてしまう。
# df[, c('Petal.Width', 'Species')] # Source: local data frame [150 x 2] # # Petal.Width Species # 1 0.2 setosa # 2 0.2 setosa # 3 0.2 setosa # .. ... ... # NG! dt[, c('Petal.Length', 'Petal.Width')] # [1] "Petal.Length" "Petal.Width" dt[, c('Petal.Length', 'Petal.Width'), with = FALSE] # Source: local data table [150 x 2] # # Petal.Width Species # 1 0.2 setosa # 2 0.2 setosa # 3 0.2 setosa # .. ... ...
真偽値vectorを用いて、列をdata.frame/data.tableとして選択する
dplyr::select
+ one_of
。ただし真偽値 vector
はそのままでは select
に渡せないため、names
をかませて列名を取る。
dplyr::select(df, one_of(names(df)[c(FALSE, FALSE, TRUE, TRUE, FALSE)])) # Source: local data frame [150 x 2] # # Petal.Length Petal.Width # 1 1.4 0.2 # 2 1.4 0.2 # 3 1.3 0.2 # .. ... ... dplyr::select(dt, one_of(names(dt)[c(FALSE, FALSE, TRUE, TRUE, FALSE)])) # 略
補足 通常の記法を使う際の with = FALSE
は文字列vector
の場合と同様。
df[, c(FALSE, FALSE, TRUE, TRUE, FALSE)] # 略 dt[, c(FALSE, FALSE, TRUE, TRUE, FALSE), with = FALSE] # 略
列の属性/値が特定の条件に該当する列を、data.frame/data.tableとして選択する
型が数値の列のみ取り出したい、なんてときには dplyr::select
+ one_of
+ sapply
。
dplyr::select(df, one_of(names(df)[sapply(df, is.numeric)])) # Source: local data frame [150 x 4] # # Sepal.Length Sepal.Width Petal.Length Petal.Width # 1 5.1 3.5 1.4 0.2 # 2 4.9 3.0 1.4 0.2 # 3 4.7 3.2 1.3 0.2 # .. ... ... ... ... dplyr::select(dt, one_of(names(dt)[sapply(dt, is.numeric)])) # 略
補足 通常の記法を使う際h(略)
df[, sapply(df, is.numeric)] # 略 dt[, sapply(df, is.numeric), with = FALSE] # 略
行操作
値が特定の条件を満たす行を抽出する
dplyr::filter
。
dplyr::filter(df, Species == 'virginica') # Source: local data frame [50 x 5] # # Sepal.Length Sepal.Width Petal.Length Petal.Width Species # 1 6.3 3.3 6.0 2.5 virginica # 2 5.8 2.7 5.1 1.9 virginica # 3 7.1 3.0 5.9 2.1 virginica # .. ... ... ... ... ... dplyr::filter(dt, Species == 'virginica') # 略
列名、式を文字列として渡す場合は dplyr::filter_
。
dplyr::filter_(df, "Species == 'virginica'") # 略 dplyr::filter_(dt, "Species == 'virginica'") # 略
補足 通常の記法では data.frame
で条件式後のカンマを忘れると悲惨なことになる。列選択との場合と違って、ここのカンマは忘れやすいと思う、、自分だけ? data.table
ではカンマ有無どちらでも同じ挙動。
df[df$Species == 'virginica', ] # Source: local data frame [50 x 5] # # Sepal.Length Sepal.Width Petal.Length Petal.Width Species # 1 6.3 3.3 6.0 2.5 virginica # 2 5.8 2.7 5.1 1.9 virginica # 3 7.1 3.0 5.9 2.1 virginica # .. ... ... ... ... ... # NG! df[df$Species == 'virginica'] # Source: local data frame [150 x 50] dt[dt$Species == 'virginica', ] # Source: local data table [50 x 5] # # Sepal.Length Sepal.Width Petal.Length Petal.Width Species # 1 6.3 3.3 6.0 2.5 virginica # 2 5.8 2.7 5.1 1.9 virginica # 3 7.1 3.0 5.9 2.1 virginica # .. ... ... ... ... ... dt[dt$Species == 'virginica'] # Source: local data table [50 x 5] # # Sepal.Length Sepal.Width Petal.Length Petal.Width Species # 1 6.3 3.3 6.0 2.5 virginica # 2 5.8 2.7 5.1 1.9 virginica # 3 7.1 3.0 5.9 2.1 virginica # .. ... ... ... ... ...
特定の行番号を抽出する
n行目を抽出する処理には dplyr::slice
。
# dplyr::slice(df, 2:4) # Source: local data frame [3 x 5] # # Sepal.Length Sepal.Width Petal.Length Petal.Width Species # 1 4.9 3.0 1.4 0.2 setosa # 2 4.7 3.2 1.3 0.2 setosa # 3 4.6 3.1 1.5 0.2 setosa dplyr::slice(dt, 2:4) # 略
ランダムサンプリングしたい場合は、それ用の関数 dplyr::sample_n
で直接抽出できる。標準関数でやる場合のようにsample
でインデックスをランダム生成してスライシングする必要はない。
dplyr::sample_n(df, 5) # Source: local data frame [5 x 5] # # Sepal.Length Sepal.Width Petal.Length Petal.Width Species # 1 4.8 3.4 1.6 0.2 setosa # 2 5.0 2.3 3.3 1.0 versicolor # 3 4.9 2.4 3.3 1.0 versicolor # 4 4.8 3.4 1.9 0.2 setosa # 5 6.2 3.4 5.4 2.3 virginica dplyr::sample_n(dt, 5) # Source: local data table [5 x 5] # # Sepal.Length Sepal.Width Petal.Length Petal.Width Species # 1 5.9 3.2 4.8 1.8 versicolor # 2 6.1 3.0 4.6 1.4 versicolor # 3 6.9 3.2 5.7 2.3 virginica # 4 5.5 2.3 4.0 1.3 versicolor # 5 4.8 3.4 1.6 0.2 setosa
代入
dplyr::mutate
。計算結果を新しい列 "Petal.Mult" として追加するとき、
dplyr::mutate(df, Petal.Mult = Petal.Width * Petal.Length) # Source: local data frame [150 x 6] # # Sepal.Length Sepal.Width Petal.Length Petal.Width Species Petal.Mult # 1 5.1 3.5 1.4 0.2 setosa 0.28 # 2 4.9 3.0 1.4 0.2 setosa 0.28 # 3 4.7 3.2 1.3 0.2 setosa 0.26 # .. ... ... ... ... ... ... dplyr::mutate(dt, Petal.Mult = Petal.Width * Petal.Length) # 略
列の値、式が文字列の場合は dplyr::mutate_
。
dplyr::mutate_(df, 'Petal.Mult' = 'Petal.Width * Petal.Length') # 略 dplyr::mutate_(dt, 'Petal.Mult' = 'Petal.Width * Petal.Length') # 略
補足 data.table
の代入式はかっこいいが、列名、式を文字列で扱う場合の挙動が恐ろしすぎるので自分はあまり使っていない。
x = 'Petal.Mult' y = 'Petal.Witdh * Petal.Length' # NG! (data.table(iris)[, x := y]) # Sepal.Length Sepal.Width Petal.Length Petal.Width Species x # 1: 5.1 3.5 1.4 0.2 setosa Petal.Witdh * Petal.Length # 2: 4.9 3.0 1.4 0.2 setosa Petal.Witdh * Petal.Length # 3: 4.7 3.2 1.3 0.2 setosa Petal.Witdh * Petal.Length # .. ... ... ... ... ... ... # これまでのように with = FALSE しても NG! (data.table(iris)[, x := y, with = FALSE]) # Sepal.Length Sepal.Width Petal.Length Petal.Width Species Petal.Mult # 1: 5.1 3.5 1.4 0.2 setosa Petal.Witdh * Petal.Length # 2: 4.9 3.0 1.4 0.2 setosa Petal.Witdh * Petal.Length # 3: 4.7 3.2 1.3 0.2 setosa Petal.Witdh * Petal.Length # .. ... ... ... ... ... ... # OK (data.table(iris)[, x := get('Petal.Width') * get('Petal.Length'), with = FALSE]) # Sepal.Length Sepal.Width Petal.Length Petal.Width Species Petal.Mult # 1: 5.1 3.5 1.4 0.2 setosa 0.28 # 2: 4.9 3.0 1.4 0.2 setosa 0.28 # 3: 4.7 3.2 1.3 0.2 setosa 0.26 # .. ... ... ... ... ... ...
まとめ
上述のような基本操作もdplyr
によって data.frame
, data.table
共通の処理に置き換えられるようになった。特に Standard Evaluation関数(アンダースコア付の関数)によって変数名を文字列として扱うことで、かなり柔軟な処理が書けるようになっている。ということで dplyr
使っておけばいい。
Pythonでdata.go.jpからデータを取得する
データカタログサイト data.go.jp が本稼働したので、そこからデータを pandasのデータフレームとして取得するモジュールを書いた。
data.go.jp に限らず data.go...系は CKAN で構築されていることが多いのだが、PyPI 上には使いやすそうなクライアントが現状見当たらない。
インストール
pip install pyopendata
ドキュメント
http://pyopendata.readthedocs.org/en/latest/
データの取得
サンプルとして 鉱工業指数の原指数を業種別/月次 で取ってくる。data.go.jp上のURLのうち、"meti_20140901_0895"がパッケージのID, "aad25837-7e83-4881-9372-1839ecb9b5eb"がリソースのIDになる。リソースのIDがわかればファイルが一意に特定される (ハズ)。
上記リンク先でデータがプレビュー/ダウンロードできるが、"生産", "出荷", "在庫", "在庫率" 4シートからなるExcelになっている。また、ヘッダは3行目から始まっているので冒頭2行は読み飛ばす必要がある。
※対象のファイルは pandasの read_excel, read_csvでパースできるものでないとだめ。
# おまじない from __future__ import unicode_literals import pyopendata as pyod import pandas as pd pd.options.display.mpl_style = 'default' import matplotlib.pyplot as plt plt.ioff() # DataStoreの初期化 store = pyod.CKANStore('http://www.data.go.jp/data') # 取得するリソースIDを指定 resource = store.get('aad25837-7e83-4881-9372-1839ecb9b5eb') # シート名: 不要カラムの辞書 sheets = {'生産': '付加生産ウエイト', '出荷': '出荷ウエイト', '在庫': '在庫ウエイト'} for sheet in sheets: # sheet名を指定して開く。最初の二行は読み飛ばす df = resource.read(sheetname=sheet, skiprows=[0, 1]) # 不要なカラムを削除 df = df.drop(['品目番号', sheets[sheet]], axis=1) # index設定 df = df.set_index('品目名称') # 転置して、時間が行として並ぶようにする df = df.T # 数値に変換できるものを変換 df = df.convert_objects(convert_numeric=True) # 適当な列をフィルタ df = df[['製造工業', '電気機械工業', '機械工具', '乗用車']] # プロット ax = df.plot() ax.set_title(sheet) plt.show()
こんな感じで順番にプロットされる。