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的分组聚合操作在特征处理过程中比较常见, 会在原始的数据集合中将各种分组聚合的结果作为训练用的特征, 这一块遇到更多的处理方式的时候将会继续更新.

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值