データ操作まとめ(R dplyrとPython pandasで)
irisデータセットを使ってRとPythonのデータフレームの操作方法の比較
準備
R
> library(tidyverse) > dim(iris) 150 5 > iris %>% head() A data.frame: 6 × 5 Sepal.Length Sepal.Width Petal.Length Petal.Width Species <dbl> <dbl> <dbl> <dbl> <fct> 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 4 4.6 3.1 1.5 0.2 setosa 5 5.0 3.6 1.4 0.2 setosa 6 5.4 3.9 1.7 0.4 setosa
Python
> from sklearn.datasets import load_iris > import pandas as pd > import numpy as np > iris0 = load_iris() > target_list = [{0:'setosa', 1:'versicolor', 2:'virginica'}[key] for key in iris0.target] > target_list[:3] ['setosa', 'setosa', 'setosa'] > iris = pd.concat([pd.DataFrame(iris0.data), ... pd.DataFrame(np.array(target_list).reshape(-1,1))], ... axis=1, ... ignore_index=True ... ) > iris.columns = ['sepal_length', 'sepal_width', 'petal_length', 'petal_width', 'species'] > print(iris.head()) sepal_length sepal_width petal_length petal_width species 0 5.1 3.5 1.4 0.2 setosa 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 4 5.0 3.6 1.4 0.2 setosa
(列の)選択
R
- 列番号を指定すると指定した列のみのdataframeが返ってくる
- seriesにならないところがポイント
> iris[1] %>% head() A data.frame: 6 × 1 Sepal.Length <dbl> 1 5.1 2 4.9 3 4.7 4 4.6 5 5.0 6 5.4
- 列番号を複数指定すると指定した列らで構成されたdataframeが返ってくる
- 列番号が重複していると、結果も重複する
> iris[c(1,2,1)] %>% head() A data.frame: 6 × 3 Sepal.Length Sepal.Width Sepal.Length.1 <dbl> <dbl> <dbl> 1 5.1 3.5 5.1 2 4.9 3.0 4.9 3 4.7 3.2 4.7 4 4.6 3.1 4.6 5 5.0 3.6 5.0 6 5.4 3.9 5.4
- select(列名)での選択が可能(NSE)
> iris %>% select(Sepal.Length) %>% head() A data.frame: 6 × 1 Sepal.Length <dbl> 1 5.1 2 4.9 3 4.7 4 4.6 5 5.0 6 5.4
- selectを使う場合は重複は無視される
> iris %>% select(Sepal.Length, Sepal.Length) %>% head() A data.frame: 6 × 1 Sepal.Length <dbl> 1 5.1 2 4.9 3 4.7 4 4.6 5 5.0 6 5.4
- 限定列挙する場合
> iris %>% select(Sepal.Length, Sepal.Width) %>% head() A data.frame: 6 × 2 Sepal.Length Sepal.Width <dbl> <dbl> 1 5.1 3.5 2 4.9 3.0 3 4.7 3.2 4 4.6 3.1 5 5.0 3.6 6 5.4 3.9
> iris %>% select(one_of(c('Sepal.Width', 'Petal.Length'))) %>% head() A data.frame: 6 × 2 Sepal.Width Petal.Length <dbl> <dbl> 1 3.5 1.4 2 3.0 1.4 3 3.2 1.3 4 3.1 1.5 5 3.6 1.4 6 3.9 1.7
- 条件に合致する列名を一括して選択する
starts_with
ends_with
contains
one_of
matches
等
> iris %>% select(ends_with('Width')) %>% head() A data.frame: 6 × 2 Sepal.Width Petal.Width <dbl> <dbl> 1 3.5 0.2 2 3.0 0.2 3 3.2 0.2 4 3.1 0.2 5 3.6 0.2 6 3.9 0.4
starts_with
の実態は、bool型リスト(ベクトル?)なので、論理演算が可能
> iris %>% select(ends_with('Width') & starts_with('Petal')) %>% head() A data.frame: 6 × 1 Petal.Width <dbl> 1 0.2 2 0.2 3 0.2 4 0.2 5 0.2 6 0.4
> iris %>% select(ends_with('Width') | starts_with('Petal')) %>% head() A data.frame: 6 × 3 Sepal.Width Petal.Width Petal.Length <dbl> <dbl> <dbl> 1 3.5 0.2 1.4 2 3.0 0.2 1.4 3 3.2 0.2 1.3 4 3.1 0.2 1.5 5 3.6 0.2 1.4 6 3.9 0.4 1.7
Python
- 列番号を指定すると指定した列のSeriesが返ってくる
> iris.iloc[:5,1] sepal_width 0 3.5 1 3.0 2 3.2 3 3.1 4 3.6
- DataFrameが欲しい場合は列番号をリストにする
> iris.iloc[:5,[1]]
- 列番号を複数指定すると指定した列らで構成されたDataFrameが返ってくる
- 列番号が重複していると、結果も重複する
> iris.iloc[:5, [1,2,1]] sepal_width petal_length sepal_width 0 3.5 1.4 3.5 1 3.0 1.4 3.0 2 3.2 1.3 3.2 3 3.1 1.5 3.1 4 3.6 1.4 3.6
- 列名で選択する場合は
.iloc
不要
> iris['sepal_length'] 0 5.1 1 4.9 2 4.7 3 4.6 4 5.0 ... 145 6.7 146 6.3 147 6.5 148 6.2 149 5.9 Name: sepal_length, Length: 150, dtype: float64
- あるいは
.loc
を使う
> iris.loc[:5,['sepal_length']] sepal_length 0 5.1 1 4.9 2 4.7 3 4.6 4 5.0 5 5.4
- 列名が重複していると、結果も重複する
> iris.loc[:5,['sepal_length', 'sepal_length']] sepal_length sepal_length 0 5.1 5.1 1 4.9 4.9 2 4.7 4.7 3 4.6 4.6 4 5.0 5.0 5 5.4 5.4
- 列名の限定列挙
> iris.loc[:5,['sepal_length', 'sepal_width']] sepal_length sepal_width 0 5.1 3.5 1 4.9 3.0 2 4.7 3.2 3 4.6 3.1 4 5.0 3.6 5 5.4 3.9
- 条件に合致する列名を一括して選択する場合はstrアクセサを使う
startswith
endswith
contains
等
> iris.loc[:5, lambda x: x.columns.str.endswith('width')] sepal_width petal_width 0 3.5 0.2 1 3.0 0.2 2 3.2 0.2 3 3.1 0.2 4 3.6 0.2 5 3.9 0.4
startswith
の実態は配列でありビット演算子&,|,^,~
による要素ごとの論理演算(AND, OR, XOR, NOT)が可能- ただし、
startswith
endswith
の引数にはタプルを指定でき、OR条件とすることが可能
- ただし、
> iris.loc[:5, lambda x: x.columns.str.endswith('width') & x.columns.str.startswith('petal')] petal_width 0 0.2 1 0.2 2 0.2 3 0.2 4 0.2 5 0.4
抽出(filter)
R
filter(条件式)
で条件に合致する行が抽出できる
> iris %>% filter(Sepal.Width < 3.5) %>% head() A data.frame: 6 × 5 Sepal.Length Sepal.Width Petal.Length Petal.Width Species <dbl> <dbl> <dbl> <dbl> <fct> 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 4 4.6 3.4 1.4 0.3 setosa 5 5.0 3.4 1.5 0.2 setosa 6 4.4 2.9 1.4 0.2 setosa
- 複数の列を使用する条件式も可能
> iris %>% filter(Sepal.Width < Sepal.Length*.8) %>% head() A data.frame: 6 × 5 Sepal.Length Sepal.Width Petal.Length Petal.Width Species <dbl> <dbl> <dbl> <dbl> <fct> 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 4 4.6 3.1 1.5 0.2 setosa 5 5.0 3.6 1.4 0.2 setosa 6 5.4 3.9 1.7 0.4 setosa
- リスト中のいずれかの値と一致する行を抽出する例
> iris %>% filter(Species %in% c('virginica')) %>% head() A data.frame: 6 × 5 Sepal.Length Sepal.Width Petal.Length Petal.Width Species <dbl> <dbl> <dbl> <dbl> <fct> 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 4 6.3 2.9 5.6 1.8 virginica 5 6.5 3.0 5.8 2.2 virginica 6 7.6 3.0 6.6 2.1 virginica
stringr::str_detect
を使えば多彩な文字列条件を適用できる
> iris %>% filter(str_detect(Species, 'v')) %>% group_by(Species) %>% count() A grouped_df: 2 × 2 Species n <fct> <int> versicolor 50 virginica 50
- 選択同様、論理演算が可能
> iris %>% filter(str_detect(Species, 'v') & str_detect(Species, 'ca')) %>% group_by(Species) %>% count() A grouped_df: 1 × 2 Species n <fct> <int> virginica 50
Python
- 抽出する場合は、
query
メソッドを使うか、
> iris.query("sepal_width < 3.5").iloc[:6]
- loc + λ式を使う。
- 個人的にはこっちのほうが条件をダイナミックに変えやすいため好み
> iris.loc[lambda x: x['sepal_width'] < 3.5].iloc[:6] 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 6 4.6 3.4 1.4 0.3 setosa 7 5.0 3.4 1.5 0.2 setosa 8 4.4 2.9 1.4 0.2 setosa
- 複数の列を使用する条件式も可能
> iris.loc[lambda x: x['sepal_width'] < x['sepal_length']*.8].iloc[:6] sepal_length sepal_width petal_length petal_width species 0 5.1 3.5 1.4 0.2 setosa 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 4 5.0 3.6 1.4 0.2 setosa 5 5.4 3.9 1.7 0.4 setosa
- リスト中のいずれかの値と一致する行を抽出する例
> iris.loc[lambda x: x['species'].isin(['virginica',''])].iloc[:6] sepal_length sepal_width petal_length petal_width species 100 6.3 3.3 6.0 2.5 virginica 101 5.8 2.7 5.1 1.9 virginica 102 7.1 3.0 5.9 2.1 virginica 103 6.3 2.9 5.6 1.8 virginica 104 6.5 3.0 5.8 2.2 virginica 105 7.6 3.0 6.6 2.1 virginica
- strアクセサを使えば多彩な文字列条件を適用できる
- 下記は'v'を含むspeciesを抽出する例
> iris.loc[lambda x: x['species'].str.contains('v')].groupby('species').size() species versicolor 50 virginica 50 dtype: int64
- 選択同様、ビット演算子が利用可能
> iris.loc[lambda x: x['species'].str.contains('v') & x['species'].str.contains('ca')].groupby('species').size() species virginica 50 dtype: int64
- ORの場合は正規表現を使うほうがスマート
> iris.loc[lambda x: x['species'].str.contains('v|ca')].groupby('species').size() species versicolor 50 virginica 50 dtype: int64
カテゴリ化
- 数値をカテゴリ化して集計するケース
R
mutate
を使って新しいカラムを作成して使う
> iris %>% mutate(cat = Sepal.Width < 3.5) %>% group_by(cat) %>% count() A grouped_df: 2 × 2 cat n <lgl> <int> FALSE 25 TRUE 125
- カテゴリ毎にコード(A, B, C... 等)を振りたいときは
if_else
を入れ子にして新しいカラムを作成する
> iris %>% mutate(cat = if_else(Sepal.Width < 3.5, 'A', if_else(Sepal.Width < 4.0, 'B', 'C'))) %>% group_by(cat) %>% count() A grouped_df: 3 × 2 cat n <chr> <int> A 125 B 21 C 4
if_else
の中で複数のカラム(Sepal.WidthとSepal.Length)を使用することができる
> iris %>% mutate(cat = if_else(Sepal.Width < 3.5 & Petal.Length > 5.5, 'A', if_else(Sepal.Width < 4.0, 'B', 'C'))) %>% group_by(cat) %>% count() A grouped_df: 3 × 2 cat n <chr> <int> A 22 B 124 C 4
Python
assign
を使って新しいカラムを作成して使う
> iris.assign(cat = lambda x: x['sepal_width'] < 3.5).groupby('cat').size() cat False 25 True 125 dtype: int64
> iris.assign(cat = lambda x: ['A' if i < 3.5 else 'B' if i < 4.0 else 'C' for i in x['sepal_width']]).groupby('cat').size() cat A 125 B 21 C 4 dtype: int64
- 複数のカラム(Sepal.WidthとSepal.Length)を使う場合は、zipを用いる
> iris.assign(cat = lambda x: ['A' if i < 3.5 and j > 5.5 else 'B' if i < 4.0 else 'C' for i,j in zip(x['sepal_width'], x['petal_length'])]).groupby('cat').size() cat A 22 B 124 C 4 dtype: int64
集計
R
dplyr 1.0.0
から集計時のungroup
がデフォルトとなった- ただし、
.groups
を指定しないとwarningが表示される
- ただし、
> iris %>% + group_by(Species) %>% + summarize(n = n(), .groups='drop') A tibble: 3 × 2 Species n <fct> <int> setosa 50 versicolor 50 virginica 50
- 複数の集計をまとめて行う場合
> iris %>% + group_by(Species) %>% + summarize(n = n(), + sum_sl = sum(Sepal.Length, na.rm=TRUE), + .groups='drop') A tibble: 3 × 3 Species n sum_sl <fct> <int> <dbl> setosa 50 250.3 versicolor 50 296.8 virginica 50 329.4
- 集計対象のカラムを条件で一括指定する場合、
summarize_at
を使用する
> iris %>% + group_by(Species) %>% + summarize_at(.vars=vars(ends_with('Width')), .funs=sum) A tibble: 3 × 3 Species Sepal.Width Petal.Width <fct> <dbl> <dbl> setosa 171.4 12.3 versicolor 138.5 66.3 virginica 148.7 101.3
dplyr 1.0.0
からは、across
を用いて一括指定することもできる
> iris %>% + group_by(Species) %>% + summarize(across(ends_with('Width'), ~sum(.x)), .groups='drop') A tibble: 3 × 3 Species Sepal.Width Petal.Width <fct> <dbl> <dbl> setosa 171.4 12.3 versicolor 138.5 66.3 virginica 148.7 101.3
across
の中でも論理演算は有効
> iris %>% + group_by(Species) %>% + summarize(across(ends_with('Width') | starts_with('Petal'), ~sum(.x)), .groups='drop') A tibble: 3 × 4 Species Sepal.Width Petal.Width Petal.Length <fct> <dbl> <dbl> <dbl> setosa 171.4 12.3 73.1 versicolor 138.5 66.3 213.0 virginica 148.7 101.3 277.6
- 論理演算としてNot
!
を使うケース
> iris %>% + group_by(Species) %>% + summarize(across(!ends_with('Width'), ~sum(.x)), .groups='drop') A tibble: 3 × 3 Species Sepal.Length Petal.Length <fct> <dbl> <dbl> setosa 250.3 73.1 versicolor 296.8 213.0 virginica 329.4 277.6
> iris %>% + group_by(Species) %>% + summarize(across(ends_with('Width') | starts_with('Petal'), list(~sum(.x), ~mean(.x))), .groups='drop') A tibble: 3 × 7 Species Sepal.Width_1 Sepal.Width_2 Petal.Width_1 Petal.Width_2 Petal.Length_1 Petal.Length_2 <fct> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> setosa 171.4 3.428 12.3 0.246 73.1 1.462 versicolor 138.5 2.770 66.3 1.326 213.0 4.260 virginica 148.7 2.974 101.3 2.026 277.6 5.552
- 適用する関数を名前付きリストで指定すれば集計結果のカラム名の接尾辞として使用される
> iris %>% + group_by(Species) %>% + summarize(across(ends_with('Width') | starts_with('Petal'), list(sum=~sum(.x), avg=~mean(.x))), .groups='drop') A tibble: 3 × 7 Species Sepal.Width_sum Sepal.Width_avg Petal.Width_sum Petal.Width_avg Petal.Length_sum Petal.Length_avg <fct> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> setosa 171.4 3.428 12.3 0.246 73.1 1.462 versicolor 138.5 2.770 66.3 1.326 213.0 4.260 virginica 148.7 2.974 101.3 2.026 277.6 5.552
across
とは別に集計を指定することも可能- これにより、一括でsumを求めつつ、レコードカウントも行うことが簡単になった
> iris %>% + group_by(Species) %>% + summarize(across(ends_with('Width') | starts_with('Petal'), list(sum=~sum(.x), avg=~mean(.x))), + n=n(), + .groups='drop') A tibble: 3 × 8 Species Sepal.Width_sum Sepal.Width_avg Petal.Width_sum Petal.Width_avg Petal.Length_sum Petal.Length_avg n <fct> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <int> setosa 171.4 3.428 12.3 0.246 73.1 1.462 50 versicolor 138.5 2.770 66.3 1.326 213.0 4.260 50 virginica 148.7 2.974 101.3 2.026 277.6 5.552 50 , avg=mean))
dplyr <1.0.0
でなければsummarize_at
を使うことは少なくなるだろう
> iris %>% + group_by(Species) %>% + summarize_at(vars(ends_with('Width') | starts_with('Petal')), list(sum=sum, avg=mean)) A tibble: 3 × 7 Species Sepal.Width_sum Petal.Width_sum Petal.Length_sum Sepal.Width_avg Petal.Width_avg Petal.Length_avg <fct> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> setosa 171.4 12.3 73.1 3.428 0.246 1.462 versicolor 138.5 66.3 213.0 2.770 1.326 4.260 virginica 148.7 101.3 277.6 2.974 2.026 5.552
Python
- 基本は
groupby
+集計用メソッド- 集計用メソッド:
size
min
max
count
mean
median
std
など
- 集計用メソッド:
> iris.groupby(by='species').size() species setosa 50 versicolor 50 virginica 50 dtype: int64
- 複数の集計を一括して行う場合は
groupby
+agg
> iris.groupby(by='species').agg({'sepal_width': ['count','sum'], 'sepal_length': 'sum'}) sepal_width sepal_length count sum sum species setosa 50 171.4 250.3 versicolor 50 138.5 296.8 virginica 50 148.7 329.4
groupby
の後に.loc
を使うことができないため、集計対象のカラムをstrアクセサで一括指定することができない- 無理にやるのであれば下記のようにトリッキーになる。
> iris.loc[:, lambda x: x.columns.str.endswith('width') | (x.columns == 'species')].groupby(by='species').sum() sepal_width petal_width species setosa 171.4 12.3 versicolor 138.5 66.3 virginica 148.7 101.3
agg
の中で集計関数を条件分岐させるテクニックもあるようだが、これもやはりトリッキーであろう
> iris.groupby(by='species').agg(lambda x: x.sum() if x.name.endswith('width') else x.min()) sepal_length sepal_width petal_length petal_width species setosa 4.3 171.4 1.0 12.3 versicolor 4.9 138.5 3.0 66.3 virginica 4.9 148.7 4.5 101.3
- 結局、集計関数をまとめて適用し、後で必要なカラムだけ取り出すのが無難な方法と思う
> df_01 = iris.groupby(by='species').agg(['count','sum']) df_01 sepal_length sepal_width petal_length petal_width count sum count sum count sum count sum species setosa 50 250.3 50 171.4 50 73.1 50 12.3 versicolor 50 296.8 50 138.5 50 213.0 50 66.3 virginica 50 329.4 50 148.7 50 277.6 50 101.3 > df_01[[('sepal_length','count'), ('sepal_width','sum')]] sepal_length sepal_width count sum species setosa 50 171.4 versicolor 50 138.5 virginica 50 148.7
- 特定の集計結果だけ取り出すならば
xs
メソッドが使える
> df_01.xs('sum', axis=1, level=1) 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
> df_01.loc[:, lambda x: x.columns.get_level_values(0).str.startswith('sepal') & x.columns.get_level_values(1).str.startswith('sum')] sepal_length sepal_width sum sum species setosa 250.3 171.4 versicolor 296.8 138.5 virginica 329.4 148.7
集計値を新規カラムとして追加
- グループごとの合計値を全レコードに追加したい場合、ランキングのカラムを追加したい場合など
R
group_by %>% mutate
が簡単
> iris %>% + group_by(Species) %>% + mutate(sum_sepal_width = sum(Sepal.Width), + rnk = rank(Sepal.Width), + rnk2 = min_rank(Sepal.Width), + rnk3 = dense_rank(Sepal.Width), + rnk4 = rank(desc(Sepal.Width)), + ) %>% head() A grouped_df: 6 × 10 Sepal.Length Sepal.Width Petal.Length Petal.Width Species sum_sepal_width rnk rnk2 rnk3 rnk4 <dbl> <dbl> <dbl> <dbl> <fct> <dbl> <dbl> <int> <int> <dbl> 5.1 3.5 1.4 0.2 setosa 171.4 31.5 29 8 19.5 4.9 3.0 1.4 0.2 setosa 171.4 5.5 3 3 45.5 4.7 3.2 1.3 0.2 setosa 171.4 15.0 13 5 36.0 4.6 3.1 1.5 0.2 setosa 171.4 10.5 9 4 40.5 5.0 3.6 1.4 0.2 setosa 171.4 36.0 35 9 15.0 5.4 3.9 1.7 0.4 setosa 171.4 45.5 45 12 5.5
Python
assign
メソッドを使う- ただし、グループごとの合計値を全レコードに追加したい(つまり、集計(集約)関数の結果)場合、
transform
をかませる必要がある
> iris.assign( ... sum_sepal_width = lambda x: x.groupby('species')['sepal_width'].transform('sum') ... ).iloc[:6] sepal_length sepal_width petal_length petal_width species sum_sepal_width 0 5.1 3.5 1.4 0.2 setosa 171.4 1 4.9 3.0 1.4 0.2 setosa 171.4 2 4.7 3.2 1.3 0.2 setosa 171.4 3 4.6 3.1 1.5 0.2 setosa 171.4 4 5.0 3.6 1.4 0.2 setosa 171.4 5 5.4 3.9 1.7 0.4 setosa 171.4
- ランキングなど、Window関数の結果ならば、λ式の中でそのまま使用できる
> iris.assign( ... sum_sepal_width = lambda x: x.groupby('species')['sepal_width'].transform('sum'), ... rnk = lambda x: x.groupby('species')['sepal_width'].rank(), ... rnk2 = lambda x: x.groupby('species')['sepal_width'].rank(method='min'), ... rnk3 = lambda x: x.groupby('species')['sepal_width'].rank(method='dense'), ... rnk4 = lambda x: x.groupby('species')['sepal_width'].rank(ascending=False), ... ).iloc[:6] sepal_length sepal_width petal_length petal_width species sum_sepal_width rnk rnk2 rnk3 rnk4 0 5.1 3.5 1.4 0.2 setosa 171.4 31.5 29.0 8.0 19.5 1 4.9 3.0 1.4 0.2 setosa 171.4 5.5 3.0 3.0 45.5 2 4.7 3.2 1.3 0.2 setosa 171.4 15.0 13.0 5.0 36.0 3 4.6 3.1 1.5 0.2 setosa 171.4 10.5 9.0 4.0 40.5 4 5.0 3.6 1.4 0.2 setosa 171.4 36.0 35.0 9.0 15.0 5 5.4 3.9 1.7 0.4 setosa 171.4 45.5 45.0 12.0 5.5