pandasのagg(), aggregate()の使い方
pandas.DataFrame, Seriesのagg()およびaggregate()メソッドを使うと、行・列に一度に複数の処理を適用して集約できる。agg()はaggregate()のエイリアスで、どちらを使っても同じ。
各列の主要な要約統計量(平均や標準偏差など)を一度に取得したい場合はdescribe()メソッドがある。いちいちagg()でリストを指定するより簡単。
また、agg()は、groupby(), resample(), rolling()などが返すオブジェクトのメソッドとしても提供されている。基本的な使い方・考え方は本記事の説明と同じ。具体例は以下の記事を参照。
- 関連記事: pandasのgroupby()でグルーピングし統計量を算出
- 関連記事: pandasで時系列データをリサンプリングするresample, asfreq
- 関連記事: pandasで窓関数を適用するrollingを使って移動平均などを算出
本記事のサンプルコードのpandasおよびNumPyのバージョンは以下の通り。バージョンによって仕様が異なる可能性があるので注意。
import pandas as pd
import numpy as np
print(pd.__version__)
# 2.1.2
print(np.__version__)
# 1.26.1
agg()とaggregate()は同一
冒頭に書いたように、agg()はaggregate()のエイリアス。どちらを使っても同じ。
print(pd.DataFrame.agg is pd.DataFrame.aggregate)
# True
print(pd.Series.agg is pd.Series.aggregate)
# True
以降のサンプルコードではagg()を使う。
agg()の基本的な使い方
pandas.DataFrameの場合
以下のDataFrameを例とする。
df = pd.DataFrame({'A': [0, 1, 2], 'B': [3, 4, 5]})
print(df)
# A B
# 0 0 3
# 1 1 4
# 2 2 5
agg()の第一引数に、適用したい関数・メソッド名の文字列や呼び出し可能オブジェクト、またはそのリストを指定する。ここでは文字列を使う。詳細は後述。
リストを指定するとDataFrame、文字列や呼び出し可能オブジェクトを単独で指定するとSeriesが返される。要素数が1個でもリストの場合はDataFrame。
print(df.agg(['sum', 'mean', 'min', 'max']))
# A B
# sum 3.0 12.0
# mean 1.0 4.0
# min 0.0 3.0
# max 2.0 5.0
print(type(df.agg(['sum', 'mean', 'min', 'max'])))
# <class 'pandas.core.frame.DataFrame'>
print(df.agg(['sum']))
# A B
# sum 3 12
print(type(df.agg(['sum'])))
# <class 'pandas.core.frame.DataFrame'>
print(df.agg('sum'))
# A 3
# B 12
# dtype: int64
print(type(df.agg('sum')))
# <class 'pandas.core.series.Series'>
キーkeyを列名、値valueを適用する処理とした辞書dictを指定すると、各列に異なる処理を適用できる。
print(df.agg({'A': ['sum', 'min', 'max'], 'B': ['mean', 'min', 'max']}))
# A B
# sum 3.0 NaN
# min 0.0 3.0
# max 2.0 5.0
# mean NaN 4.0
適用する処理をリストではなく単独で指定するとSeriesが返される。いずれかの列にリストを指定するとDataFrame。
print(df.agg({'A': 'sum', 'B': 'mean'}))
# A 3.0
# B 4.0
# dtype: float64
print(df.agg({'A': ['sum'], 'B': 'mean'}))
# A B
# sum 3.0 NaN
# mean NaN 4.0
print(df.agg({'A': ['min', 'max'], 'B': 'mean'}))
# A B
# min 0.0 NaN
# max 2.0 NaN
# mean NaN 4.0
デフォルトは列ごとだが、引数axisを1または'columns'とすると行ごとに処理される。
print(df.agg(['sum', 'mean', 'min', 'max'], axis=1))
# sum mean min max
# 0 3.0 1.5 0.0 3.0
# 1 5.0 2.5 1.0 4.0
# 2 7.0 3.5 2.0 5.0
pandas.Seriesの場合
以下のSeriesを例とする。
s = pd.Series([0, 1, 2])
print(s)
# 0 0
# 1 1
# 2 2
# dtype: int64
agg()の第一引数にリストを指定するとSeries、単独で指定するとスカラー値が返される。要素数1個でもリストならSeries。
print(s.agg(['sum', 'mean', 'min', 'max']))
# sum 3.0
# mean 1.0
# min 0.0
# max 2.0
# dtype: float64
print(type(s.agg(['sum', 'mean', 'min', 'max'])))
# <class 'pandas.core.series.Series'>
print(s.agg(['sum']))
# sum 3
# dtype: int64
print(type(s.agg(['sum'])))
# <class 'pandas.core.series.Series'>
print(s.agg('sum'))
# 3
print(type(s.agg('sum')))
# <class 'numpy.int64'>
リストで指定した場合は結果のラベル名は処理の名前になるが、辞書で指定するとキーkeyがラベル名となる。
print(s.agg({'Total': 'sum', 'Average': 'mean', 'Min': 'min', 'Max': 'max'}))
# Total 3.0
# Average 1.0
# Min 0.0
# Max 2.0
# dtype: float64
辞書の値valueにリストを指定することはできない。
# print(s.agg({'NewLabel_1': ['sum', 'max'], 'NewLabel_2': ['mean', 'min']}))
# SpecificationError: nested renamer is not supported
agg()の第一引数に指定できる処理(関数・メソッド)
関数・メソッド名の文字列
agg()の第一引数に指定された文字列は_apply_str()という関数でチェックされる。以前は_try_aggregate_string_function()という名前だった。
def _apply_str(self, obj, func: str, *args, **kwargs):
"""
if arg is a string, then try to operate on it:
- try to find a function (or attribute) on obj
- try to find a numpy function
- raise
"""
obj(ここではSeriesまたはDataFrame)のメソッド・属性、および、NumPyの関数と一致する文字列が有効となる。
例えば'count'はSeriesおよびDataFrameのメソッドでNumPyの関数には存在せず、'amax'はNumPyの関数でSeriesおよびDataFrameのメソッドには存在しないが、どちらも文字列で指定可能。
df = pd.DataFrame({'A': [0, 1, 2], 'B': [3, 4, 5]})
print(df)
# A B
# 0 0 3
# 1 1 4
# 2 2 5
print(df.agg(['count', 'amax']))
# A B
# count 3 3
# amax 2 5
print(df['A'].count())
# 3
# print(np.count(df['A']))
# AttributeError: module 'numpy' has no attribute 'count'
print(np.amax(df['A']))
# 2
# print(df['A'].amax())
# AttributeError: 'Series' object has no attribute 'amax'
どちらにも当てはまらない文字列はエラーとなる。
# print(df.agg(['xxx']))
# AttributeError: 'xxx' is not a valid function for 'Series' object
# print(df.agg('xxx'))
# AttributeError: 'xxx' is not a valid function for 'DataFrame' object
上のエラーメッセージから分かるように、リストで指定した場合はSeries、文字列単独で指定した場合はDataFrameのメソッド・属性が使われる。
また、上記の_apply_str()のソースコードを見ると分かるように、NumPyの関数が有効になるのはobjが__array__属性を持っている場合のみ。
DataFrame, Seriesは__array__属性を持つが、groupby(), resample(), rolling()などが返すオブジェクトは__array__属性を持たない。
print(hasattr(pd.DataFrame, '__array__'))
# True
print(hasattr(pd.core.groupby.GroupBy, '__array__'))
# False
したがって、groupby(), resample(), rolling()などが返すオブジェクトのagg()メソッドでは、NumPy関数の名前の文字列は認識されない。呼び出し可能オブジェクト(np.xxx)の形で指定することは可能。
なお、これはpandas2.1.2での仕様。バージョンが異なると変わる場合もあるので注意。
呼び出し可能オブジェクト
agg()の第一引数には、defで定義した関数やラムダ式(無名関数)などの呼び出し可能オブジェクトも指定できる。
df = pd.DataFrame({'A': [0, 1, 2], 'B': [3, 4, 5]})
print(df)
# A B
# 0 0 3
# 1 1 4
# 2 2 5
def my_func(x):
return x.min() + x.max()
print(df.agg([my_func, lambda x: x.min() - x.max()]))
# A B
# my_func 2 8
# <lambda> -2 -2
関数やメソッドに引数を指定
agg()に指定したキーワード引数は適用する関数やメソッドに渡される。
df = pd.DataFrame({'A': [0, 1, 2], 'B': [3, 4, 5]})
print(df)
# A B
# 0 0 3
# 1 1 4
# 2 2 5
print(df.agg('std'))
# A 1.0
# B 1.0
# dtype: float64
print(df.agg('std', ddof=0))
# A 0.816497
# B 0.816497
# dtype: float64
print(df.agg(['std'], ddof=0))
# A B
# std 0.816497 0.816497
複数の関数やメソッドを指定した場合は、そのすべてにキーワード引数が渡される。受け取れない場合はエラーとなる。
# print(df.agg(['max', 'std'], ddof=0))
# TypeError: max() got an unexpected keyword argument 'ddof'
個別に引数を指定したい場合はラムダ式を利用する。
print(df.agg(['max', lambda x: x.std(ddof=0)]))
# A B
# max 2.000000 5.000000
# <lambda> 0.816497 0.816497
対応していないデータ型dtypeに対する処理
文字列を要素とする列を含むDataFrameを例とする。
df_str = pd.DataFrame({'A': [0, 1, 2], 'B': [3, 4, 5], 'C': ['X', 'Y', 'Z']})
print(df_str)
# A B C
# 0 0 3 X
# 1 1 4 Y
# 2 2 5 Z
例えば、文字列を要素とするSeriesからmean()を呼ぶとエラーになるため、agg()に指定してもエラーになる。
# df_str['C'].mean()
# TypeError: Could not convert XYZ to numeric
# print(df_str.agg(['mean']))
# TypeError: Could not convert string 'XYZ' to numeric
なお、これはpandas2.1.2での仕様。pandas1.0.4ではエラーにならずにNaNになっていた。
DataFrameのmean()では引数numeric_onlyが実装されているが、Seriesのmean()ではnumeric_onlyが実装されていないので、リストで指定した場合(Seriesとして処理される場合)は、numeric_onlyは使えない。
print(df_str.mean(numeric_only=True))
# A 1.0
# B 4.0
# dtype: float64
print(df_str.agg('mean', numeric_only=True))
# A 1.0
# B 4.0
# dtype: float64
# df_str['C'].mean(numeric_only=True)
# TypeError: Series.mean does not allow numeric_only=True with non-numeric dtypes.
# print(df_str.agg(['mean'], numeric_only=True))
# TypeError: Series.mean does not allow numeric_only=True with non-numeric dtypes.
数値の列のみを対象としたい場合は、select_dtypes()のあとでagg()を呼ぶ。
print(df_str.select_dtypes(include='number').agg(['sum', 'mean']))
# A B
# sum 3.0 12.0
# mean 1.0 4.0