pandas 數據分組聚和
分組與聚合是特徵工程中比較常見的一種處理方式, 比如以用戶ID作爲分組統計其在某一個時段內的活躍天數等等. pandas 對DataFrame 和 Series 都提供了groupby 函數實現各種各樣的分組以及聚合操作, 本文對pandas的分組與聚合做一個小結.
DataFrame/Series的分組操作
首先看groupby 方法的參數, 羅列出其中使用最頻繁的參數
by : mapping, function, label, or list of labels
Used to determine the groups for the groupby.
If ``by`` is a function, it's called on each value of the object's
index. If a dict or Series is passed, the Series or dict VALUES
will be used to determine the groups (the Series' values are first
aligned; see ``.align()`` method). If an ndarray is passed, the
values are used as-is determine the groups. A label or list of
labels may be passed to group by the columns in ``self``. Notice
that a tuple is interpreted a (single) key.
axis : {
0 or 'index', 1 or 'columns'}, default 0
Split along rows (0) or columns (1).
level : int, level name, or sequence of such, default None
If the axis is a MultiIndex (hierarchical), group by a particular
level or levels.
as_index : bool, default True
For aggregated output, return object with group labels as the
index. Only relevant for DataFrame input. as_index=False is
effectively "SQL-style" grouped output.
by: 指定分組的key. 這裏的取值可以是一個映射(mapping), 一個 函數(function) 或者是一個label(一列名稱) 或者是 名稱的 list(多個分組條件). 小字部分關鍵信息,如果傳遞的是一個function 那麼 function將會作用於每一行的索引, 函數對每一行索引的求值結果作爲分組的參照.
axis: 比較常規了指定在什麼方向上做分組, 一般都是行索引方向(axis=0) 做分組操作
level: 如果是按照索引進行分組, 這個Level指定索引的級別(多級索引) 或者索引名稱.
as_index: 是否將分組參考的Label 作爲分組後聚合的結果的索引. 默認情況下分組聚合操作後, 分組參考的labels 會作爲索引的, 這也是這個值默認爲True 如果設置爲False 那麼 分組參考的labels 會成爲數據中的一列. 但是這個僅僅對聚合處理之後結果是DataFrame有效. 如果分組聚合之後的類型是Series( 比如分組後調用size()函數 )那麼這個as_index 是無效的.
返回類型:
DataFrameGroupBy or SeriesGroupBy
Depends on the calling object and returns groupby object that
contains information about the groups.
返回數據類型完整路徑pandas.core.groupby.generic.DataFrameGroupBy
or pandas.core.groupby.generic.SeriesGroupBy
可以在shell中使用dir查看所有分組之後的可執行聚合方法.
>>> dir(pandas.core.groupby.generic.DataFrameGroupBy)
['__bytes__', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattr__', '__getattribute__', '__getitem__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__unicode__', '__weakref__', '_accessors', '_add_numeric_operations', '_agg_examples_doc', '_agg_see_also_doc', '_aggregate', '_aggregate_generic', '_aggregate_item_by_item', '_aggregate_multiple_funcs', '_apply_filter', '_apply_to_column_groupbys', '_apply_whitelist', '_assure_grouper', '_block_agg_axis', '_bool_agg', '_builtin_table', '_choose_path', '_concat_objects', '_constructor', '_cumcount_array', '_cython_agg_blocks', '_cython_agg_general', '_cython_table', '_cython_transform', '_decide_output_index', '_def_str', '_define_paths', '_deprecations', '_dir_additions', '_dir_deletions', '_fill', '_get_cythonized_result', '_get_data_to_aggregate', '_get_index', '_get_indices', '_gotitem', '_group_selection', '_insert_inaxis_grouper_inplace', '_internal_names', '_internal_names_set', '_is_builtin_func', '_is_cython_func', '_iterate_column_groupbys', '_iterate_slices', '_make_wrapper', '_obj_with_exclusions', '_post_process_cython_aggregate', '_python_agg_general', '_python_apply_general', '_reindex_output', '_reset_cache', '_reset_group_selection', '_selected_obj', '_selection', '_selection_list', '_selection_name', '_set_group_selection', '_set_result_index_ordered', '_shallow_copy', '_transform_fast', '_transform_general', '_transform_item_by_item', '_transform_should_cast', '_try_aggregate_string_function', '_try_cast', '_wrap_agged_blocks', '_wrap_aggregated_output', '_wrap_applied_output', '_wrap_generic_output', '_wrap_transformed_output', 'agg', 'aggregate', 'all', 'any', 'apply', 'backfill', 'bfill', 'boxplot', 'corr', 'corrwith', 'count', 'cov', 'cumcount', 'cummax', 'cummin', 'cumprod', 'cumsum', 'describe', 'diff', 'dtypes', 'expanding', 'ffill', 'fillna', 'filter', 'first', 'get_group', 'groups', 'head', 'hist', 'idxmax', 'idxmin', 'indices', 'last', 'mad', 'max', 'mean', 'median', 'min', 'ndim', 'ngroup', 'ngroups', 'nth', 'nunique', 'ohlc', 'pad', 'pct_change', 'pipe', 'plot', 'prod', 'quantile', 'rank', 'resample', 'rolling', 'sem', 'shift', 'size', 'skew', 'std', 'sum', 'tail', 'take', 'transform', 'tshift', 'var']
>>>
構建基本的DataFrame
ict_obj = {
'key1' : ['a', 'b', 'a', 'b',
'a', 'b', 'a', 'a'],
'key2' : ['one', 'one', 'two', 'three',
'two', 'two', 'one', 'three'],
'data1': np.random.randn(8),
'data2': np.random.randn(8)}
df_obj = pd.DataFrame(dict_obj)
key1 key2 data1 data2
0 a one -0.275125 1.233618
1 b one 0.471614 0.028697
2 a two 0.505572 -1.060270
3 b three -1.694967 -0.232360
4 a two -0.668416 0.120577
5 b two -1.455430 0.457114
6 a one -0.052052 -1.229687
7 a three 0.971619 -0.358885
groupby 的結果是不能直接打印的, 直接打印結果如下:
print(df_obj.groupby('key1'))
<pandas.core.groupby.generic.DataFrameGroupBy object at 0x125f8f240>
一般操作都是分組後執行相關的聚合函數, 如果想要直接查看每一個具體的分組,可以迭代groupby的結果
for key, group in df_obj.groupby('key1'):
print((key, group))
('a', key1 key2 data1 data2
0 a one -0.275125 1.233618
2 a two 0.505572 -1.060270
4 a two -0.668416 0.120577
6 a one -0.052052 -1.229687
7 a three 0.971619 -0.358885)
('b', key1 key2 data1 data2
1 b one 0.471614 0.028697
3 b three -1.694967 -0.232360
5 b two -1.455430 0.457114)
首先看分組聚合操作後返回的數據類型
print(df_obj.groupby(df_obj['key1']).mean()) # DataFrame 聚合
print(df_obj['data1'].groupby(df_obj['key1']).mean()) # Series 聚合
DataFrame
data1 data2
key1
a 0.096320 -0.258929
b -0.892928 0.084484
Series
key1
a 0.096320
b -0.892928
Name: data1, dtype: float64
DataFrame 使用mean 聚合結果是DataFrame類型 Series mean 聚合結果是Series類型. mean 特別之處在於只對數值型列進行計算, 第一種情況下 key2不是數值型數據, 所以結果不會再聚合結果裏面.
在看as_index的影響. 如果想把分組聚合後的分組的key作爲結果一列數據保存, 以便將此結果和其他DataFrame做鏈接操作(類似於SQL的表連接操作). 這時候as_index參數就起到作用了.
print(df_obj.groupby('key1', as_index=False).mean())
print(df_obj.groupby('key1', as_index=False).size())
key1 data1 data2
0 a 0.096320 -0.258929
1 b -0.892928 0.084484
key1
a 5
b 3
mean 操作 as_index=False 結果裏面 key1(分組列) 作爲一列存在, 而 size 操作 同樣是as_index=False 結果裏面key1卻成了Index 而不是一列數據. 因爲size() 操作返回的是Series 無論此處as_index 取什麼值, 最終結果裏面都不會包含key1這個數據列. 如果想要分組的lable 存在在最終聚合的結果裏面通用處理方法就是分組(as_index 使用默認的True)聚合之後使用reset_index 一定可以實現.
print(df_obj.groupby('key1', as_index=False).size().reset_index())
key1 0
0 a 5
1 b 3
所以通用的將分組的key不作爲index 作爲最終分組聚合結果中的一列可以採用
XXX.groupby().reset_index()
的通用處理實現模式.
by 可以給定一個function作爲分組依據, function作用在每一行的索引值上面, 以索引值計算結果作爲分組依據, 比如:
df_obj3 = pd.DataFrame(np.random.randint(1, 10, (5,5)),
columns=['a', 'b', 'c', 'd', 'e'],
index=['AA', 'BBB', 'CC', 'D', 'EE'])
a b c d e
AA 9 4 4 4 9
BBB 4 5 5 9 3
CC 1 1 2 1 6
D 9 7 3 9 2
EE 2 3 8 2 1
print(df_obj3.groupby(len).size())
1 1
2 3
3 1
dtype: int64
len函數作用於索引上, 計算索引長度, 以索引長度作爲分組的依據. 索引長度爲1(D) 只有一行, 長度爲2(AA, CC, EE) 兩行, 索引長度爲3(BBB) 只有一行. 所以纔有如上結果.
level 取值. 可以是索引級別也可以是索引的名稱.
比如構建列級別的索引
columns = pd.MultiIndex.from_arrays([['Python', 'Java', 'Python', 'Java', 'Python'],
['A', 'A', 'B', 'C', 'B']], names=['language', 'index'])
df_obj4 = pd.DataFrame(np.random.randint(1, 10, (5, 5)), columns=columns)
language Python Java Python Java Python
index A A B C B
0 4 4 1 5 8
1 6 6 7 8 6
2 6 2 5 9 5
3 6 2 7 4 4
4 1 2 4 5 1
在language 這一層做列級別分組 然後分組後每組中一行數據求和
print(df_obj4.groupby(level='language', axis=1).sum())
python 是第一列 第三列 和 第五列 三列每一行數據求和 就是[13, 19, 16, 17, 6] 驗證沒問題.
language Java Python
0 9 13
1 14 19
2 11 16
3 6 17
4 7 6
同樣 level 可以給索引級別(o, 1, 2等)
print(df_obj4.groupby(level=1, axis=1).sum())
level=1 則是 index 列分組, A 是第一和第二列數據的租 每一行求和結果爲 [8,12,8,8,3]
index A B C
0 8 9 5
1 12 13 8
2 8 10 9
3 8 11 4
4 3 5 5
分組差不多就是上面的內容, 其實主要是將分組聚合後的key 保留在數據列做後續merge操作較多.
當然也可以不進行此操作, 因爲本身pandas提供了 merge功能支持列和索引的聯合操作. 只不過放在列上進行更像SQL的表連接操作而已.
DataFrame/Series 分組後聚合操作
分組之後當然是聚合操作了. 最常規的聚合mean max min sum size count
等此處不再贅述. 主要是說一下 agg函數, 接收自定義的function 做聚合操作. 函數比較簡單的時候可以使用Lambda 表達式替代自定義函數.
def func(df):
return df.max() - df.min()
print(df_obj.groupby('key1').agg(func))
print(df_obj.groupby('key1').agg(lambda df : df.max() - df.min()))
data1 data2
key1
a 1.640036 2.463305
b 2.166580 0.689474
小結
基本上pandas的分組聚合操作在特徵處理過程中比較常見, 會在原始的數據集合中將各種分組聚合的結果作爲訓練用的特徵, 這一塊遇到更多的處理方式的時候將會繼續更新.