pandas數據分組聚合

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的分組聚合操作在特徵處理過程中比較常見, 會在原始的數據集合中將各種分組聚合的結果作爲訓練用的特徵, 這一塊遇到更多的處理方式的時候將會繼續更新.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章