Pandas GroupBy 详解:深入理解与实践
pandas
是 Python 数据分析的核心库之一,而 groupby()
则是 Pandas 中一个强大而灵活的工具。它帮助我们轻松地对数据进行分组和聚合,以便从数据中提取有价值的信息。本文将详细解释 groupby()
的使用细节,以及其他相关函数如何配合 groupby()
完成各种复杂的分析任务。
GroupBy 工作流程
使用 groupby()
进行数据分组的过程可以概括为 “分割-应用-组合” 的三步流程:
- 分割(Split):按指定的键(列)将数据分成不同的组。
- 应用(Apply):对每个组应用聚合函数、变换函数或其他自定义函数。
- 组合(Combine):将每组操作的结果合并成一个新的数据结构。
这种工作流程使得我们可以灵活地对每个组的数据进行操作并获取汇总信息。
基础示例
我们先通过一个简单的数据集来演示分组操作:
import pandas as pd
# 创建数据集
data = {
'城市': ['北京', '上海', '广州', '北京', '广州', '上海', '北京'],
'销售额': [100, 200, 150, 80, 120, 250, 90],
'月份': ['一月', '一月', '一月', '二月', '二月', '二月', '三月']
}
df = pd.DataFrame(data)
print(df)
输出结果:
城市 销售额 月份
0 北京 100 一月
1 上海 200 一月
2 广州 150 一月
3 北京 80 二月
4 广州 120 二月
5 上海 250 二月
6 北京 90 三月
使用 GroupBy 进行分组聚合
使用 groupby()
方法对数据按城市进行分组,然后使用 sum()
计算每个城市的总销售额:
grouped = df.groupby('城市')['销售额'].sum()
print(grouped)
输出结果:
城市
北京 270
广州 270
上海 450
Name: 销售额, dtype: int64
在这个例子中,我们首先使用 groupby('城市')
按照 城市
列对数据进行分组,接着使用 ['销售额'].sum()
对每个城市的 销售额
进行求和。
GroupBy 的细节:层级索引与多重索引
使用 groupby()
时,得到的结果往往包含多重索引。尤其在对多个列进行分组时,多重索引的结构会更加复杂。例如:
grouped_multi = df.groupby(['城市', '月份'])['销售额'].sum()
print(grouped_multi)
输出结果:
城市 月份
北京 一月 100
二月 80
三月 90
广州 一月 150
二月 120
上海 一月 200
二月 250
Name: 销售额, dtype: int64
这里得到的是一个具有多重索引的 Series
,我们可以通过 reset_index()
方法将其转换回 DataFrame 格式:
reset_df = grouped_multi.reset_index()
print(reset_df)
输出结果:
城市 月份 销售额
0 北京 一月 100
1 北京 二月 80
2 北京 三月 90
3 广州 一月 150
4 广州 二月 120
5 上海 一月 200
6 上海 二月 250
使用 transform 进行分组后的变换
transform()
方法可以在分组后对数据进行转换,并返回一个与原数据相同大小的结果。例如,我们想计算每个城市的平均销售额,并将结果作为新列添加到原数据中:
df['平均销售额'] = df.groupby('城市')['销售额'].transform('mean')
print(df)
输出结果:
城市 销售额 月份 平均销售额
0 北京 100 一月 90.0
1 上海 200 一月 225.0
2 广州 150 一月 135.0
3 北京 80 二月 90.0
4 广州 120 二月 135.0
5 上海 250 二月 225.0
6 北京 90 三月 90.0
使用 apply 进行自定义分组处理
apply()
方法允许我们对每个组执行自定义的操作,具有极高的灵活性。例如,我们对每个城市的销售额进行标准化处理:
def standardize(group):
return (group - group.mean()) / group.std()
df['标准化销售额'] = df.groupby('城市')['销售额'].apply(standardize)
print(df)
输出结果:
城市 销售额 月份 平均销售额 标准化销售额
0 北京 100 一月 90.0 0.730297
1 上海 200 一月 225.0 -0.707107
2 广州 150 一月 135.0 0.707107
3 北京 80 二月 90.0 -1.095445
4 广州 120 二月 135.0 -0.707107
5 上海 250 二月 225.0 0.707107
6 北京 90 三月 90.0 0.365148
在这个例子中,我们对每个组使用 standardize()
函数来进行标准化处理,从而生成新的列。
分组后的过滤操作
有时候我们只需要保留符合特定条件的组,可以使用 filter()
方法。例如,我们只保留总销售额大于 300 的城市:
grouped_filter = df.groupby('城市').filter(lambda x: x['销售额'].sum() > 300)
print(grouped_filter)
输出结果:
城市 销售额 月份 平均销售额 标准化销售额
1 上海 200 一月 225.0 -0.707107
2 广州 150 一月 135.0 0.707107
4 广州 120 二月 135.0 -0.707107
5 上海 250 二月 225.0 0.707107
通过 filter()
方法,我们只保留了销售额总和大于 300 的城市,删除了北京的数据。
分组聚合中的常用函数
groupby()
经常与各种聚合函数结合使用,以下是一些常见的聚合函数:
- sum():求和
- mean():均值
- count():计数
- max() 和 min():最大值和最小值
- median():中位数
- std() 和 var():标准差和方差
我们可以同时对多个列进行聚合操作,例如:
multi_agg = df.groupby('城市').agg({'销售额': ['sum', 'mean'], '月份': 'count'})
print(multi_agg)
输出结果:
销售额 月份
sum mean count
城市
北京 270 90.0 3
广州 270 135.0 2
上海 450 225.0 2
在这个例子中,我们对 销售额
列进行了 sum
和 mean
两种聚合操作,对 月份
列进行了 count
操作。
使用 apply 的注意事项与常见报错
在使用 apply()
进行分组操作时,有时会遇到与索引相关的错误。例如,如果我们尝试对每个城市的销售额进行标准化处理,并将结果赋值回原 DataFrame,如下所示:
def standardize(group):
return (group - group.mean()) / group.std()
df['标准化销售额'] = df.groupby('城市')['销售额'].apply(standardize)
执行上面的代码时,可能会遇到如下错误:
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
...
TypeError: incompatible index of inserted column with frame index
这个错误通常是由于 apply()
之后返回的结果的索引与原 DataFrame 的索引不匹配引起的。当我们使用 apply()
进行分组操作时,返回的结果是按组处理的 Series
,其索引可能与原始 DataFrame 不一致,导致赋值操作失败。
解决方案
为了解决这个问题,我们有两种方法:
- 使用
transform()
代替apply()
。transform()
方法会返回与原始数据相同大小的结果,并且保持原 DataFrame 的索引结构一致,非常适合用于新增列的情况。
df['标准化销售额'] = df.groupby('城市')['销售额'].transform(standardize)
print(df)
- 如果必须使用
apply()
,可以在返回的结果中重置索引,然后再赋值给原 DataFrame。例如:
standardized_sales = df.groupby('城市')['销售额'].apply(standardize).reset_index(level=0, drop=True)
df['标准化销售额'] = standardized_sales
print(df)
通过这种方式,我们确保 apply()
返回的结果与原 DataFrame 的索引匹配,从而避免类型不兼容的错误。
总结
本文详细介绍了 Pandas 中 groupby()
的使用细节,从基础的分组聚合操作到高级的分组变换、过滤和自定义操作。groupby()
是数据分析中非常重要的工具,它能够让我们灵活地对数据进行分组和处理。通过结合 transform()
、apply()
和 filter()
等方法,我们可以极大地扩展分组操作的功能,以满足复杂的数据分析需求。