分组基础:groupby 与 split-apply-combine
本教程共 54 篇 · 第 37 篇 · 更新于 2026-08-11 · 约 6 分钟阅读
本节目标:理解 split-apply-combine 分组思想,学会创建分组对象、查看分组、遍历分组,并跑通第一个分组聚合。
数据里常有一类问题:“每个部门的平均薪资是多少""每个产品卖了多少件”。这类问题都是同一个套路:把数据按某个标准切成几堆,分别算,再把结果拼起来。pandas 把整套套路封装成了 groupby。
分组思想:split-apply-combine
groupby 这个名字来自 SQL 的 GROUP BY。整个过程分三步:
- split(拆分):按分组键把数据切成若干小组
- apply(应用):对每个小组独立执行函数
- combine(合并):把结果组合成新的数据结构
import pandas as pd
import numpy as np
df = pd.DataFrame({
"部门": ["技术", "销售", "技术", "运营", "销售", "技术"],
"姓名": ["张三", "李四", "王五", "赵六", "钱七", "孙八"],
"薪资": [12000, 15000, 11000, 18000, 14000, 13000],
})
单列分组
groupby 传一个列名,按这一列的值分组:
grouped = df.groupby("部门")
type(grouped) # pandas.core.groupby.DataFrameGroupBy
这一步什么都没算。grouped 只是一个”分组计划”,真正计算要等聚合方法出现。分组对象上可以挂 .sum()、.mean()、.size() 等方法,比如按部门算平均薪资:
df.groupby("部门")[["薪资"]].mean()
结果以”部门”为索引,每个部门一行。分组键默认会排序,结果按键排好。
Note创建 GroupBy 对象是惰性的——它只校验分组键是否合法,不会真正拆分数据。同一个分组对象可以反复调用多次聚合,不必每次重新分组。
多列分组
需求变成”每个部门每年的平均薪资”,就把多个列名放进列表:
df["年份"] = [2023, 2023, 2024, 2024, 2023, 2024]
df.groupby(["部门", "年份"])[["薪资"]].sum()
结果索引变成两层(部门、年份),这就是 MultiIndex,§22 讲过。想要平的 DataFrame,在 groupby 时加 as_index=False:
df.groupby(["部门", "年份"], as_index=False)[["薪资"]].sum()
分组键就变回普通列了,后续做其他处理更方便。
分组键还可以是 Series
分组键不一定是列名。传一个与行数等长的 Series 或列表,效果一样:
df.groupby(df["部门"])[["薪资"]].mean() # 等价于 df.groupby("部门")
官方文档里 df.groupby("部门") 就是这写法的语法糖。灵活之处在于:可以用一列经过计算后的结果当分组键,也可以把一个 Series 同时用于多个 DataFrame 的分组。
多列分组时,get_group 要传元组:
df.groupby(["部门", "年份"]).get_group(("技术", 2023))
分组对象怎么查看
分组对象本身看不到数据,但有几个属性帮你看清结构:
grouped.groups # 字典:分组名 -> 组内行索引
len(grouped) # 分组个数
grouped.ngroups # 分组个数,和 len 一样
groups 返回一个字典,键是分组名(如 “技术”),值是该组所有行的索引。注意值是索引标签,不是行号。
单独取出某一组的数据,用 get_group:
grouped.get_group("技术")
取回的是完整的小 DataFrame,包含组名列。
遍历分组
分组对象可以像字典一样迭代,每次拿到 (分组名, 组数据):
for name, group in grouped:
print(name)
print(group)
多列分组时,name 是一个元组,比如 (“技术”, 2023)。遍历适合调试和”每个组做不同的事”的场景,但性能一般,能用聚合方法解决的别用循环。
列选择
分组后只想算某一列,用方括号选列,和 DataFrame 选列一样:
grouped["薪资"].sum() # 返回 Series
grouped[["薪资"]].sum() # 返回 DataFrame
先选列再聚合,只计算需要的列,比全表聚合后再取列更快。
聚合初体验
最常见的三个聚合:
df.groupby("部门")[["薪资"]].sum() # 求和
df.groupby("部门")[["薪资"]].mean() # 平均
df.groupby("部门").size() # 每组的行数
size 返回每组的记录条数,包含缺失值;count 只数非缺失值。看”这个组有多少人”用 size 就对了。
分组结果默认按键排序,想按数据里出现的顺序排,传 sort=False:
df.groupby("部门", sort=False)[["薪资"]].sum()
Tipsize 和 count 的区别:size 数所有行,count 忽略 NaN。想统计每组样本量用 size,想统计有效数据量用 count。
Warning分组键里有缺失值时,默认整个组被丢弃(
dropna=True)。想保留缺失值组,写df.groupby("部门", dropna=False)。
分组键不止列名
groupby 的键可以是:
- 列名或索引层级名(字符串)
- 长度与行数相同的列表或数组
- 与索引等长的 Series
- 按索引标签计算的函数
比如按索引长度分组、按年份分组(§40 展开),入门阶段先知道”能这么用”就行。
分组对象还能干什么
分组对象上挂了一整套方法,按用途分四类,后面三章逐个展开:
- 聚合:sum、mean、agg——每组压成一行
- 变换:cumsum、transform——保持行数,逐组计算
- 过滤:filter、head、tail、nth——按条件删组
- 灵活应用:apply——结果形状自由
小结
分组对象是整套分组分析的地基:惰性创建、groups 查看、get_group 取组、迭代遍历、选列聚合。下一步(§38)把”聚合”玩出花。