Pandas使用教程 - 数据分组与聚合 (groupby)
除了使用内置的聚合函数外,我们还可以传入自定义函数来对每个组的数据进行处理。假设我们想计算每个部门工资的极差(最大值减去最小值),可以自定义一个 lambda 表达式或函数来实现。我们也可以定义一个函数,并传入agg()方法。salary_range_func = df.groupby("部门")["工资"].agg(calc_range)print("各部门工资极差(自定义函数):")输出与上例
目录
基础篇11:数据分组与聚合 (groupby)
在数据分析过程中,数据预处理往往包含对数据的分组和聚合操作。Pandas 中的 groupby() 方法是实现这一功能的核心工具。本文将详细介绍 Pandas 中分组与聚合的基本原理、常见用法、函数应用以及如何利用自定义函数实现更复杂的聚合需求。通过本章内容,你不仅能掌握分组聚合的使用方法,还能理解其中的“拆分—应用—合并(Split-Apply-Combine)”思想,为后续更高级的数据处理打下坚实的基础。
1. 引言
在实际数据处理过程中,我们经常需要对数据进行分类统计。例如,统计不同部门的销售总额、计算各类别的平均成绩或分析不同时间段内的指标变化。利用 Pandas 的 groupby() 方法,我们可以轻松实现这些需求。
从数学角度看,假设数据集中每个样本 x i j x_{ij} xij 属于某个组 i i i,那么聚合操作可以表示为
g i = f ( { x i j } j = 1 n i ) g_i = f\Big(\{ x_{ij} \}_{j=1}^{n_i}\Big) gi=f({xij}j=1ni)
其中, f f f 表示聚合函数,如求和、均值、最大值等。本文将详细介绍如何利用 Pandas 对数据进行分组,并利用各种聚合函数得到我们所需的统计结果。
2. Pandas 中的 GroupBy 概念
2.1 GroupBy 的基本思想
Pandas 的 groupby() 方法可以看作是将一个 DataFrame 根据指定的键拆分成多个子 DataFrame,然后对每个子 DataFrame 分别应用某个函数,最后将结果合并起来。这个过程通常被称为“拆分—应用—合并(Split-Apply-Combine)”策略:
- 拆分(Split):根据一个或多个键将数据拆分成若干组,每一组包含满足相同条件的数据子集。
- 应用(Apply):对每个子集应用一个函数 f f f,可以是内置的聚合函数(如
sum、mean、count等),也可以是自定义的函数。 - 合并(Combine):将所有组的结果合并成一个新的 DataFrame 或 Series。
例如,假设我们有一个销售数据表,其中包含“地区”和“销售额”等信息。如果我们希望统计每个地区的总销售额,可以使用 groupby 操作将数据按地区拆分,然后对每个组求和,最终合并出一个按地区统计的结果。
2.2 数学公式表示
对于一个分组操作,我们可以定义:
- 设数据集为 D = { x 1 , x 2 , … , x N } D=\{x_1, x_2, \dots, x_N\} D={x1,x2,…,xN},其中每个样本 x i x_i xi 都带有一个分组标签 g ( x i ) g(x_i) g(xi)。
- 将数据集拆分为若干组 D k = { x i ∣ g ( x i ) = k } D_k = \{x_i \mid g(x_i) = k\} Dk={xi∣g(xi)=k},其中 k k k 为不同的组标识。
- 对每一组 D k D_k Dk,应用聚合函数 f f f,计算结果为
y k = f ( D k ) y_k = f(D_k) yk=f(Dk) - 最终将所有结果合并成集合 Y = { y k } Y = \{y_k\} Y={yk},这就是分组聚合操作的输出。
例如,若我们要求各组的平均值,则有
y k = 1 ∣ D k ∣ ∑ x ∈ D k x y_k = \frac{1}{|D_k|} \sum_{x \in D_k} x yk=∣Dk∣1x∈Dk∑x
3. 基本用法与代码示例
接下来,我们通过几个简单的例子,介绍如何使用 Pandas 的 groupby() 方法进行数据分组和聚合操作。
3.1 创建示例数据
首先,我们构造一个简单的 DataFrame,包含姓名、部门、工资和工作年限等信息:
import pandas as pd
data = {
"姓名": ["张三", "李四", "王五", "赵六", "孙七", "周八", "吴九", "郑十"],
"部门": ["销售", "销售", "技术", "技术", "技术", "人事", "人事", "销售"],
"工资": [8000, 8500, 12000, 11500, 11000, 7000, 7200, 9000],
"工作年限": [3, 4, 5, 3, 4, 2, 3, 5]
}
df = pd.DataFrame(data)
print(df)
输出结果如下:
姓名 部门 工资 工作年限
0 张三 销售 8000 3
1 李四 销售 8500 4
2 王五 技术 12000 5
3 赵六 技术 11500 3
4 孙七 技术 11000 4
5 周八 人事 7000 2
6 吴九 人事 7200 3
7 郑十 销售 9000 5
3.2 按部门分组并计算聚合指标
3.2.1 计算各部门的平均工资
我们可以通过以下代码实现:
# 按部门分组后计算平均工资
avg_salary = df.groupby("部门")["工资"].mean()
print("各部门平均工资:")
print(avg_salary)
输出结果类似于:
各部门平均工资:
部门
人事 7100.0
销售 8500.0
技术 11500.0
Name: 工资, dtype: float64
这里,df.groupby("部门") 将 DataFrame 按“部门”列进行分组,后续通过 ["工资"].mean() 对每个组中“工资”这一列求平均值。
3.2.2 同时计算多个聚合指标
可以使用 agg() 方法同时对多个列应用多个聚合函数,例如计算每个部门的平均工资和总工资:
aggregated = df.groupby("部门").agg({
"工资": ["mean", "sum"],
"工作年限": "mean"
})
print("各部门聚合统计结果:")
print(aggregated)
输出结果可能为:
工资 工作年限
mean sum mean
部门
人事 7100.0 14200 2.5
销售 8500.0 25500 4.0
技术 11500.0 34500 4.0
在上述代码中,我们为“工资”列同时应用了 mean 和 sum 两个函数,为“工作年限”应用了 mean 函数。
4. 多重分组
在实际情况中,我们常常需要根据多个列进行分组。例如,我们有一个销售数据表,既要按地区分组,又要按产品类别分组。
4.1 示例数据
假设我们有如下数据:
data_sales = {
"地区": ["华东", "华东", "华北", "华北", "华南", "华南", "华东", "华南"],
"产品": ["手机", "笔记本", "手机", "笔记本", "手机", "笔记本", "手机", "笔记本"],
"销售额": [12000, 25000, 15000, 30000, 10000, 20000, 13000, 22000]
}
df_sales = pd.DataFrame(data_sales)
print(df_sales)
输出结果:
地区 产品 销售额
0 华东 手机 12000
1 华东 笔记本 25000
2 华北 手机 15000
3 华北 笔记本 30000
4 华南 手机 10000
5 华南 笔记本 20000
6 华东 手机 13000
7 华南 笔记本 22000
4.2 按“地区”和“产品”分组
我们可以同时按照“地区”和“产品”两列进行分组,并计算每组的销售总额:
grouped_sales = df_sales.groupby(["地区", "产品"])["销售额"].sum()
print("各地区、各产品销售总额:")
print(grouped_sales)
输出结果:
各地区、各产品销售总额:
地区 产品
华东 笔记本 25000
手机 25000
华北 笔记本 30000
手机 15000
华南 笔记本 42000
手机 10000
Name: 销售额, dtype: int64
这样,我们可以直观地看到每个地区中各产品的销售情况。
5. 自定义聚合函数
除了使用内置的聚合函数外,我们还可以传入自定义函数来对每个组的数据进行处理。假设我们想计算每个部门工资的极差(最大值减去最小值),可以自定义一个 lambda 表达式或函数来实现。
5.1 使用 lambda 表达式
salary_range = df.groupby("部门")["工资"].agg(lambda x: x.max() - x.min())
print("各部门工资极差:")
print(salary_range)
输出结果类似于:
各部门工资极差:
部门
人事 200
销售 1000
技术 500
Name: 工资, dtype: int64
5.2 定义自定义函数
我们也可以定义一个函数,并传入 agg() 方法。例如:
def calc_range(series):
return series.max() - series.min()
salary_range_func = df.groupby("部门")["工资"].agg(calc_range)
print("各部门工资极差(自定义函数):")
print(salary_range_func)
输出与上例相同。
6. 分组后数据的变换与过滤
除了聚合操作外,Pandas 的 groupby 还提供了 transform 和 filter 方法,用于对分组后的数据进行变换或筛选。
6.1 transform 方法
transform 方法返回一个与原数据形状相同的对象,常用于数据标准化或归一化操作。举例来说,我们可以对每个部门的工资做标准化处理,使得每个组内数据转换为 (x - 均值) / 标准差 的形式。
# 对工资进行标准化处理
standardized_salary = df.groupby("部门")["工资"].transform(lambda x: (x - x.mean()) / x.std())
df["标准化工资"] = standardized_salary
print("添加标准化工资后的 DataFrame:")
print(df)
此时,每一行的“标准化工资”均反映了该员工工资相对于所在部门工资分布的标准差单位。
6.2 filter 方法
filter 方法可以根据各组的特征筛选出符合条件的组。例如,我们希望筛选出平均工资高于 9000 的部门:
high_salary_dept = df.groupby("部门").filter(lambda group: group["工资"].mean() > 9000)
print("平均工资高于9000的部门数据:")
print(high_salary_dept)
只有满足条件的部门(组)才会出现在结果中。
7. 利用 pipe() 实现链式调用
在实际项目中,我们往往希望将多个操作串联在一起,从而使代码更加清晰。Pandas 提供了 pipe() 方法,允许我们将分组操作与后续的函数调用结合起来。
例如,我们希望在对每个部门分组后,计算该部门所有员工工资的总额占公司总工资的百分比,可以使用如下方式:
def calc_pct(group):
total = group["工资"].sum()
company_total = df["工资"].sum()
return total / company_total * 100
dept_pct = df.groupby("部门").pipe(lambda g: g.apply(calc_pct))
print("各部门工资总额占比:")
print(dept_pct["工资"])
这种链式调用使得代码逻辑更加清晰,便于阅读和维护。
8. Split-Apply-Combine 的流程图示意
为了更直观地理解 groupby 的拆分—应用—合并过程,我们可以使用 Mermaid 语法绘制一个简单的示意图。
flowchart LR
A[原始 DataFrame]
B[拆分:根据指定键将数据分为若干组]
C[应用:对每个组应用聚合或变换函数]
D[合并:将各组结果合并成最终输出]
A --> B
B --> C
C --> D
上述流程图展示了 groupby 操作的基本步骤:首先从原始 DataFrame 拆分出多个组,然后对每个组执行某种函数操作,最后合并成一个新的 DataFrame 或 Series。
9. 实战案例:学生成绩数据分析
下面通过一个实际案例来演示如何利用 groupby 对数据进行分组与聚合操作。假设我们有一份学生成绩数据,包含学生姓名、科目和分数,我们希望统计每个科目的最高分、最低分和平均分。
9.1 构造数据
import pandas as pd
data_scores = {
"学生": ["张三", "李四", "王五", "赵六", "孙七", "周八", "吴九", "郑十"],
"科目": ["数学", "数学", "英语", "英语", "数学", "英语", "数学", "英语"],
"分数": [88, 92, 75, 80, 85, 78, 95, 82]
}
df_scores = pd.DataFrame(data_scores)
print("学生成绩数据:")
print(df_scores)
输出结果:
学生 科目 分数
0 张三 数学 88
1 李四 数学 92
2 王五 英语 75
3 赵六 英语 80
4 孙七 数学 85
5 周八 英语 78
6 吴九 数学 95
7 郑十 英语 82
9.2 按科目分组并聚合
我们希望统计每个科目的最高分、最低分和平均分,可以这样做:
scores_agg = df_scores.groupby("科目")["分数"].agg(
最高分 = "max",
最低分 = "min",
平均分 = "mean"
)
print("各科目成绩统计:")
print(scores_agg)
输出结果可能为:
最高分 最低分 平均分
科目
英语 82 75 78.75
数学 95 85 90.00
可以看到,每个科目的分数分布情况一目了然。
10. 进阶操作:多重分组与复杂聚合
在一些复杂场景下,我们可能需要对数据进行多重分组,并对不同列使用不同的聚合函数。假设我们有一个销售数据集,其中不仅包含产品销售额,还包含销售数量。我们希望统计每个区域、每个产品的总销售额、平均销售额以及销售数量总和。
10.1 构造销售数据
data_complex = {
"区域": ["华东", "华东", "华北", "华北", "华南", "华南", "华东", "华南"],
"产品": ["A", "B", "A", "B", "A", "B", "A", "B"],
"销售额": [12000, 25000, 15000, 30000, 10000, 20000, 13000, 22000],
"销售量": [100, 150, 80, 120, 90, 130, 110, 140]
}
df_complex = pd.DataFrame(data_complex)
print("销售数据:")
print(df_complex)
输出结果:
区域 产品 销售额 销售量
0 华东 A 12000 100
1 华东 B 25000 150
2 华北 A 15000 80
3 华北 B 30000 120
4 华南 A 10000 90
5 华南 B 20000 130
6 华东 A 13000 110
7 华南 B 22000 140
10.2 多重分组聚合
我们可以同时按照“区域”和“产品”两列进行分组,并对“销售额”与“销售量”分别使用不同的聚合函数:
agg_result = df_complex.groupby(["区域", "产品"]).agg({
"销售额": ["sum", "mean"],
"销售量": "sum"
})
print("多重分组聚合结果:")
print(agg_result)
输出结果可能为:
销售额 销售量
sum mean sum
区域 产品
华东 A 25000 12500.0 210
B 25000 25000.0 150
华北 A 15000 15000.0 80
B 30000 30000.0 120
华南 A 10000 10000.0 90
B 42000 21000.0 270
在这里,我们通过传入字典,将不同的列映射到不同的聚合函数。注意,聚合后的 DataFrame 会产生多级列索引,如果需要可以进一步重命名或“扁平化”列名。
例如,可以使用以下方式扁平化多级列索引:
# 扁平化列索引
agg_result.columns = ['_'.join(col).strip() for col in agg_result.columns.values]
agg_result = agg_result.reset_index()
print("扁平化后的聚合结果:")
print(agg_result)
输出结果:
区域 产品 销售额_sum 销售额_mean 销售量_sum
0 华东 A 25000 12500.0 210
1 华东 B 25000 25000.0 150
2 华北 A 15000 15000.0 80
3 华北 B 30000 30000.0 120
4 华南 A 10000 10000.0 90
5 华南 B 42000 21000.0 270
11. 性能优化与注意事项
在使用 groupby 操作时,需要注意以下几点问题:
-
分组键的数据类型
使用分类数据(category)作为分组键可以显著降低内存占用和加快分组速度。
例如:df["部门"] = df["部门"].astype("category") -
避免使用过多的 .apply()
虽然 .apply() 提供了灵活性,但它往往会导致性能下降。应尽量使用内置的聚合函数或 vectorized 操作。
如:# 尽量用 agg() 或 transform() 替代 apply() df.groupby("部门")["工资"].agg("mean") -
合理使用 as_index 参数
默认情况下,groupby 会将分组键设置为结果的索引。如果后续需要对结果进行进一步处理,可以设置as_index=False。
例如:df.groupby("部门", as_index=False)["工资"].mean() -
链式调用与 pipe()
利用 pipe() 方法可以将多个操作串联在一起,使代码更简洁且易于调试。
如前文所示,结合 groupby 与 pipe() 进行数据转换。 -
检查缺失值
在分组前,检查分组键是否存在缺失值(NaN),因为默认情况下这些值会被忽略。必要时可先进行填充或剔除操作。 -
小批量处理
对于超大数据集,可考虑使用分块读取(chunksize参数)或结合 Dask、PySpark 等工具实现并行计算。
12. 实战案例总结
通过以上各部分内容,我们详细学习了 Pandas 中的 groupby 操作,从最基础的单列分组、聚合函数的使用,到多列分组、多个聚合函数的应用,再到自定义聚合函数、transform 和 filter 方法的使用,以及链式调用的高级技巧。下面我们对整个流程做一个总结:
-
拆分(Split):
利用df.groupby(分组键)将 DataFrame 拆分为若干子集。
数学上可表示为: D k = { x i ∣ g ( x i ) = k } D_k = \{ x_i \mid g(x_i) = k \} Dk={xi∣g(xi)=k} -
应用(Apply):
对每个子集应用聚合函数 f f f,如求和、均值、计数等。
表示为: y k = f ( D k ) y_k = f(D_k) yk=f(Dk) -
合并(Combine):
将所有组的结果合并成最终的输出对象。
最终输出为: Y = { y k } Y = \{ y_k \} Y={yk}
通过这些操作,我们可以快速从原始数据中提取出有用的信息,从而支持进一步的数据分析、可视化和建模工作。
13. 总结与展望
Pandas 的 groupby 操作为数据分析提供了强大的分组和聚合功能。无论是对销售数据、学生成绩还是其他业务数据,我们都可以利用 groupby 快速实现分组统计、数据标准化以及自定义计算。掌握 split-apply-combine 思想,有助于我们更高效地处理海量数据和复杂数据结构。
在今后的学习中,可以进一步探索以下内容:
- 如何利用多重索引管理复杂分组后的结果,并进行扁平化处理;
- 与时间序列数据结合,使用 resample() 方法进行时间分组;
- 利用 transform() 方法进行数据归一化、标准化等操作;
- 探索 groupby 的性能优化技巧,如使用 categorical 类型、避免过多使用 apply() 等;
- 将分组结果与数据可视化结合,利用 Matplotlib 或 Seaborn 展示分组统计结果。
通过不断实践和探索,你将能更熟练地利用 Pandas 的 groupby 操作,从而在数据预处理和特征工程阶段大大提高工作效率。
14. 结语
本文详细介绍了 Pandas 中数据分组与聚合的基本概念、常用方法和实战案例。希望你能够通过这些示例掌握 groupby 的使用技巧,并将其应用于实际数据分析中。如果在使用过程中遇到问题,不妨参考 Pandas 官方文档或查阅更多相关资料。
同时,建议大家多实践、多总结经验,只有不断动手操作,才能真正体会到 groupby 操作在大数据分析中的威力。
无论你是数据分析初学者,还是希望优化数据处理流程的高级用户,掌握 groupby 操作都是提升数据处理效率的重要技能。希望本章节内容能为你的数据科学之路增添一份信心和动力!
以上就是关于“基础篇11:数据分组与聚合 (groupby)”的完整博客内容。通过本文的学习,你应能理解并熟练使用 Pandas 的 groupby 方法,实现各类分组统计和自定义聚合操作,为进一步的数据分析和机器学习模型构建打下坚实的基础。
更多推荐



所有评论(0)