Python pandas の算術演算 / 集約関数 / 統計関数まとめ
概要
恒例の pandas
記事。今回は 基本的な算術演算についてまとめた。このあたりの挙動は numpy
と一緒で直感的だと思うが、知っていないとハマるポイントがいくつかあるので。
準備
サンプルは DataFrame
のみ。だが内容は Series
でも同じ ( 行/列 2次元のデータに関するに記載は除く)。
import numpy as np import pandas as pd df = pd.DataFrame({'C1': [1, 1, 1], 'C2': [1, 1, 1], 'C3': [1, 1, 1]}) df # C1 C2 C3 # 0 1 1 1 # 1 1 1 1 # 2 1 1 1
四則演算
まずは基本的な例。
# スカラーの加算はデータ全体に対して適用 ( ブロードキャスト ) される df + 1 # C1 C2 C3 # 0 2 2 2 # 1 2 2 2 # 2 2 2 2 # np.array の加算は 各行に対する加算 df + np.array([1, 2, 3]) # C1 C2 C3 # 0 2 3 4 # 1 2 3 4 # 2 2 3 4 # 要素の長さが違う場合は ValueError df + np.array([1, 2]) # ValueError: Wrong number of items passed 2, place
とはいえ、演算をデータ全体へブロードキャストさせたいことはマレだと思う。特定の列 あるいは 特定セルに対して演算を行う場合は、以下の記事でまとめたデータ選択記法を使って計算結果を代入する。
補足 データへの演算自体は非破壊的な処理だが、代入によって元のデータが変更されることに注意。
# 1列目に [1, 2, 3] を足す df['C1'] = df['C1'] + np.array([1, 2, 3]) df # C1 C2 C3 # 0 2 1 1 # 1 3 1 1 # 2 4 1 1 # 3行目 / 3列目の値に 5 を足す df.iloc[2, 2] += 5 df # C1 C2 C3 # 0 2 1 1 # 1 3 1 1 # 2 4 1 6 # 複数列に対する演算も OK # (裏側では DataFrame 選択 -> 計算 -> 代入 をしているだけなので) df[['C1', 'C2']] -= 5 df # C1 C2 C3 # 0 -3 -4 1 # 1 -2 -4 1 # 2 -1 -4 6
算術演算メソッド
演算の挙動をもうすこし制御したい場合は、DataFrame.add
。DataFrame
や Series
は、add
のほかにも Python 標準モジュールの operators
と対応する算術演算メソッド / 論理演算メソッドを持つ。利用できるメソッドの一覧はこちら。オプションはどれも重要なので順番に。
列に対するブロードキャスト (axis=0
)
上記のような1列の加算ではなく、列全体に対してブロードキャストをしたい場合は axis=0
を指定。
axis=0
もしくはaxis='index'
で列に対する演算axis=1
もしくはaxis='columns'
で行に対する演算 (デフォルト)
# データは元に戻す df = pd.DataFrame({'C1': [1, 1, 1], 'C2': [1, 1, 1], 'C3': [1, 1, 1]}) df # C1 C2 C3 # 0 1 1 1 # 1 1 1 1 # 2 1 1 1 df.add(np.array([1, 2, 3]), axis=0) # C1 C2 C3 # 0 2 2 2 # 1 3 3 3 # 2 4 4 4 df.add(np.array([1, 2, 3]), axis='index') # 略
欠測値 ( NaN
) 要素へのパディング (fill_value
)
データに 欠測値 ( NaN
) が含まれるとき、その要素への演算の結果も NaN
になる。これは numpy
の挙動と同じ。
# numpy の挙動 np.nan + 1 # nan # NaN を含む DataFrame を定義する df_nan = pd.DataFrame({'C1': [1, np.nan, np.nan], 'C2': [np.nan, 1, np.nan], 'C3': [np.nan, np.nan, 1]}) df_nan # C1 C2 C3 # 0 1 NaN NaN # 1 NaN 1 NaN # 2 NaN NaN 1 # NaN を含むセルの演算結果は NaN df.add(df_nan) # C1 C2 C3 # 0 2 NaN NaN # 1 NaN 2 NaN # 2 NaN NaN 2
これに気づかず四則演算していると、なんか合計があわないな?ということになってしまう。この挙動を変えたい場合は fill_value
。演算の実行前に fill_value
で指定した値がパディングされる。ただし、演算対象の要素が 両方とも NaN
の場合は NaN
のまま。
df.add(df_nan, fill_value=0) # C1 C2 C3 # 0 2 1 1 # 1 1 2 1 # 2 1 1 2 # データの順序が変わっても有効 ( fill_value は演算対象 両方のデータに適用される) df_nan.add(df, fill_value=0) # C1 C2 C3 # 0 2 1 1 # 1 1 2 1 # 2 1 1 2 # 要素が 両方とも NaN の場合はパディングされない df_nan.add(df_nan, fill_value=0) # C1 C2 C3 # 0 2 NaN NaN # 1 NaN 2 NaN # 2 NaN NaN 2 # DataFrame について、対象データが array, Series の場合の処理は未実装 (NotImplementedError) なので注意 df.add(np.array([1, np.nan, 1]), fill_value=0) # NotImplementedError: fill_value 0 not supported
最後のオプション、 level
は index
が複数の階層 (レベル) 持つ場合 ( MultiIndex
を持つ場合 ) の制御を行う。このオプションの挙動は近日公開予定 (?) の MultiIndex
編で。
行列積
*
で DataFrame
同士の積をとった場合は、各要素同士の積になる。
df2 = pd.DataFrame({'C1': [2, 3], 'C2': [4, 5]}) df2 # C1 C2 # 0 2 4 # 1 3 5 df3 = pd.DataFrame({'C1': [1, -2], 'C2': [-3, 4]}) df3 # C1 C2 # 0 1 -3 # 1 -2 4 df2 * df3 # C1 C2 # 0 2 -12 # 1 -6 20
行列の積をとりたい場合は DataFrame.dot
。ただし、行列の積をとるためには元データの columns
と 引数の index
のラベルが一致している必要がある (説明 次節)。そのため、上のように columns
/ index
が一致しないデータから直接 行列の積をとることはできない。そんなときは DataFrame.values
プロパティで引数側の内部データを numpy.array
として取り出せばよい。
# NG! df2.dot(df3) # ValueError: matrices are not aligned # values プロパティで内部データを numpy.array として取り出せる df3.values # array([[ 1, -3], # [-2, 4]], dtype=int64) # OK! df2.dot(df3.values) # 0 1 # 0 -6 10 # 1 -7 11
引数側を転置すれば、元データの columns
と 引数の index
のラベルが一致する。このようなデータはそのまま DataFrame.dot
で積をとれる。
df3.T # 0 1 # C1 1 -2 # C2 -3 4 df2.dot(df3.T) # 0 1 # 0 -10 12 # 1 -12 14
補足 そもそも Python には行列積の演算子がない。が、現在 PEP-465 で行列積として @
演算子が提案されている。
ラベルによる演算の制御
ここまでは index
, columns
が一致するデータのみを対象にしてきた。実際には演算を行うデータの index
, columns
が異なることもある。その場合の挙動には少し注意が必要。
Series
, DataFrame
同士の演算は、 index
, columns
のラベルが一致する要素同士で行われる。例えば以下のように index
と columns
がずれているとき、対応しない要素は NaN
となる。
df4 = pd.DataFrame({'C1': [1, 1, 1], 'C2': [1, 1, 1], 'C3': [1, 1, 1]}) df4 # C1 C2 C3 # 0 1 1 1 # 1 1 1 1 # 2 1 1 1 df5 = pd.DataFrame({'C2': [1, 1, 1], 'C3': [1, 1, 1], 'C4': [1, 1, 1]}, index=[1, 2, 3]) df5 # C2 C3 C4 # 1 1 1 1 # 2 1 1 1 # 3 1 1 1 df4 + df5 # C1 C2 C3 C4 # 0 NaN NaN NaN NaN # 1 NaN 2 2 NaN # 2 NaN 2 2 NaN # 3 NaN NaN NaN NaN
どう直せばよいかは状況による。index
, columns
のずれ自体は OK で、 NaN
でのパディングが気に入らない場合は fill_value
。
df4.add(df5, fill_value=0) # C1 C2 C3 C4 # 0 1 1 1 NaN # 1 1 2 2 1 # 2 1 2 2 1 # 3 NaN 1 1 1
index
, columns
に関係なく、対応する位置の要素同士で演算したい場合は DataFrame.values
を使って取得した numpy.array
のみを渡せばよい。
df4 + df5.values # C1 C2 C3 # 0 2 2 2 # 1 2 2 2 # 2 2 2 2
もしくは、以下のようにして データの index
もしくは columns
を揃えてから演算する。
df4
のindex
をdf5
にそろえる場合は、df4.index
に代入。df5
のindex
をdf4
にそろえる場合は、df5.index
に代入。もしくは、DataFrame.reset_index(drop=True)
でindex
を 0 から振りなおし
# df4 の index を df5 にそろえる df4.index = [1, 2, 3] df4 # C1 C2 C3 # 1 1 1 1 # 2 1 1 1 # 3 1 1 1 # df5 の index を df4 にそろえる df5.reset_index(drop=True) # C2 C3 C4 # 0 1 1 1 # 1 1 1 1 # 2 1 1 1 # reset_index デフォルトでは、元の index をカラムとして残す df5.reset_index() # index C2 C3 C4 # 0 1 1 1 1 # 1 2 1 1 1 # 2 3 1 1 1
集約関数
Series
, DataFrame
は 合計を取得する sum
, 平均を計算する mean
などの一連の集約関数に対応するメソッドを持つ。一覧は こちら。
df6 = pd.DataFrame({'C1': ['A', 'B', 'C'], 'C2': [1, 2, 3], 'C3': [4, 5, 6]}) df6 # C1 C2 C3 # 0 A 1 4 # 1 B 2 5 # 2 C 3 6
集約系の関数は既定では列方向に対して適用される。基本的には 数値型のみが集約対象になる。が、一部の関数では数値以外の型に対してもがんばってくれるものもある。
# mean は数値型のみ集約 df6.mean() # C2 2 # C3 5 # dtype: float64 # sum は 文字列も集約 df6.sum() # C1 ABC # C2 6 # C3 15 # dtype: object # 数値以外が不要な場合は numeric_only=True df6.sum(numeric_only=True) # C2 6 # C3 15 # dtype: int64 # 行方向へ適用したい場合は axis = 1 # このとき、数値型以外は除外して関数適用される df6.sum(axis=1) # 0 5 # 1 7 # 2 9 # dtype: int64 # 明示的に含めようとするとエラー df6.sum(axis=1, numeric_only=False) # TypeError: cannot concatenate 'str' and 'long' objects
複数列のデータをまとめて関数適用したい、という場合もたまーにある。pandas
には直接対応するメソッドはないが、以下のようにすればできる。
# values.flatten() で 2列分の値を ひとつの numpy.array として取得 df6[['C2', 'C3']].values.flatten() # array([1, 4, 2, 5, 3, 6], dtype=int64) # 集約関数適用 np.mean(df6[['C2', 'C3']].values.flatten()) # 3.5
補足 numpy.array.flatten()
は1次元化した array
のコピーを返す。
統計関数
また、分散を計算する var
, 標準偏差を計算する std
などの統計関数に対応するメソッドもある。一覧は上と同じく こちら。関数の適用方向など、挙動は集約関数と一緒。
1点 覚えておくべき箇所は、pandas
では分散 / 標準偏差について不偏推定量の計算がデフォルトになっている。これは numpy
の挙動 ( 標本統計量を返す ) とは異なる。この挙動は pandas
, numpy
ともに ddof
オプションで制御できる。
pandas
: 不偏推定量の計算 (ddof=True
) がデフォルト。numpy
: 標本統計量の計算 (ddof=False
) がデフォルト。
補足 ddof
= Delta Degrees of Freedom
df7 = pd.DataFrame({'C1': [1, 2, 3, 4], 'C2': [4, 5, 6, 7], 'C3': [2, 3, 3, 2]}) # C1 C2 C3 # 0 1 4 2 # 1 2 5 3 # 2 3 6 3 # 3 4 7 2 # 不偏分散 df7.var() # C1 1.666667 # C2 1.666667 # C3 0.333333 # dtype: float64 # 標本分散 df7.var(ddof=False) # C1 1.25 # C2 1.25 # C3 0.25 # dtype: float64 # 標本分散 (numpy) np.var(df7) # C1 1.25 # C2 1.25 # C3 0.25 # dtype: float64 # 不偏標準偏差 df7.std() # C1 1.290994 # C2 1.290994 # C3 0.577350 # dtype: float64 # 標本標準偏差 df7.std(ddof=False) # C1 1.118034 # C2 1.118034 # C3 0.500000 # dtype: float64 # 標本標準偏差 (numpy) np.std(df7) # C1 1.118034 # C2 1.118034 # C3 0.500000 # dtype: float64
基本統計量の表示 ( describe
)
最後。基本統計量をまとめて計算したい場合は DataFrame.describe()
。
df7.describe() # C1 C2 C3 # count 4.000000 4.000000 4.00000 # mean 2.500000 5.500000 2.50000 # std 1.290994 1.290994 0.57735 # min 1.000000 4.000000 2.00000 # 25% 1.750000 4.750000 2.00000 # 50% 2.500000 5.500000 2.50000 # 75% 3.250000 6.250000 3.00000 # max 4.000000 7.000000 3.00000
まとめ
pandas
での算術演算、集約関数、統計関数の挙動をまとめた。ポイントは、
DataFrame
への演算は適用される方向に注意。演算方向を指定する場合、列方向ならaxis=0
, 行方向はaxis=1
。- 演算したい要素に
NaN
が含まれる場合、必要に応じてfill_value
。 - 演算したい要素同士のラベルは一致させること。
Pythonによるデータ分析入門 ―NumPy、pandasを使ったデータ処理
- 作者: Wes McKinney,小林儀匡,鈴木宏尚,瀬戸山雅人,滝口開資,野上大介
- 出版社/メーカー: オライリージャパン
- 発売日: 2013/12/26
- メディア: 大型本
- この商品を含むブログ (9件) を見る