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