迭代给的是(组名, 子表),子表就是一个完整的 DataFrame,想干什么都行。 agg 传列表是「同一列跑多个函数」,传字典是「不同列配不同函数」。 自定义函数只要能接一个 Series 返回一个数,就能直接塞进 agg。
上一篇只会接 mean、sum 这些现成的。这一篇把 agg 的三种写法和迭代都讲透。 迭代慢但灵活,agg 快但要求规整。选哪个看「每组做的事一不一样」。
一、迭代每一组
for k, g in df.groupby('班级') 每轮拿到组名和一张子表。
一班 5 人、语文均值 86.00;二班 5 人、语文均值 79.40。
子表是完整的 DataFrame,想算什么都行,想画图、想存文件都行。
迭代慢。能用 agg 就别迭代,只有「每组要做不同的动作」时才值得循环。 行数一多,迭代的代价是几十倍的差距,不是百分之几。
二、不同列配不同函数:字典 agg
| 班级 | 语文 | 数学 | 英语 |
|---|---|---|---|
| 一班 | 86.0 | 95 | 79 |
| 二班 | 79.4 | 91 | 60 |
agg({'语文': 'mean', '数学': 'max', '英语': 'min'}),三列三种算法。
键是列名,值是函数名。看不出来的是:这三列的语义已经不一样了。
一列是均值、一列是最高、一列是最低——表头得写清楚,不然读者会横向比较。
这一句是分组统计的主力写法。它把「哪列配哪个函数」写得明明白白。
三、塞一个自定义函数
定义一个极差函数:def spread(s): return s.max() - s.min()。
直接塞进 agg:df.groupby('班级')[ ['语文','数学'] ].agg(spread)。
| 班级 | 语文 | 数学 |
|---|---|---|
| 一班 | 17 | 27 |
| 二班 | 27 | 26 |
一班语文极差 17 分,二班 27 分——二班分得更散,虽然均值只差 6.6。 自定义函数拿到的是「一列数」,也就是一个 Series。按 Series 的写法写就行。 它必须返回一个数。返回一整列会报错,因为 agg 要的是「一组压成一个值」。
四、三种写法对照
| 写法 | 含义 | 例子 |
|---|---|---|
| agg('mean') | 所有列一个函数 | agg('mean') |
| agg(['min','max']) | 同一列多个函数 | agg(['min','max']) |
| agg({列: 函数}) | 不同列不同函数 | agg({'语文':'mean'}) |
| agg(自定义) | 自己写的聚合 | agg(spread) |
四种可以混:agg({'语文': ['mean', spread]}),一列上跑一个现成的加一个自定义的。
结果会变成多级列名,上面一层是列名、下面一层是函数名。想拉平用 droplevel。
五、动手 Demo:分组之后:迭代、agg 与自定义函数
先迭代一遍看清每组长什么样,再用字典 agg 和自定义函数各算一次。 最后把三种 agg 写法摆在一起比。
import pandas as pd
DF = pd.DataFrame({
"姓名": ["张伟", "王芳", "李娜", "刘洋", "陈静",
"杨帆", "赵敏", "黄磊", "周涛", "吴霞"],
"班级": ["一班"] * 5 + ["二班"] * 5,
"语文": [88, 92, 75, 85, 90, 68, 95, 72, 83, 79],
"数学": [92, 95, 68, 88, 73, 82, 91, 65, 86, 90],
"英语": [85, 90, 80, 79, 95, 71, 88, 60, 84, 87],
})
for k, g in DF.groupby("班级"):
print("%s:%d 人,语文均值 %.2f" % (k, len(g), g["语文"].mean()))
def spread(s):
return s.max() - s.min()
M = DF.groupby("班级").agg({"语文": "mean", "数学": "max", "英语": "min"})
print(M.round(2).to_string())
print(DF.groupby("班级")[ ["语文", "数学"]].agg(spread).to_string())
print(DF.groupby("班级")["语文"].agg(["mean", spread]).round(2).to_string())
实跑输出:
--------------------------------------------------------------------
分组之后:迭代、agg 与自定义函数
--------------------------------------------------------------------
① 迭代每一组
--------------------------------------------------------------------
一班:5 人,语文均值 86.00。
二班:5 人,语文均值 79.40。
迭代给的是(组名, 子表)两样东西,子表就是一个完整的 DataFrame。
想对每组单独做复杂的事(画图、存文件),就在这个循环里写。
! 迭代慢,能用 agg 就别迭代。只有「每组要做不同的动作」时才值得循环。
② 不同列配不同函数
--------------------------------------------------------------------
| 班级 | 语文 | 数学 | 英语 |
|---|---:|---:|---:|
| 一班 | 86.0 | 95 | 79 |
| 二班 | 79.4 | 91 | 60 |
传一个字典,键是列名、值是函数名,一次算出三样不同的东西。
一班数学最高 95,二班英语最低 60。
! 字典 agg 是分组统计的主力写法。它把「哪列配哪个函数」写得明明白白。
③ 塞一个自定义函数
--------------------------------------------------------------------
自定义一个极差函数,直接塞进 agg:
| 班级 | 语文 | 数学 |
|---|---:|---:|
| 一班 | 17 | 27 |
| 二班 | 27 | 26 |
一班语文极差 17,二班 27。二班分得更散。
! 自定义函数拿到的是「一列数」,也就是一个 Series,按 Series 的写法写就行。
④ 三个易错点
--------------------------------------------------------------------
| 现象 | 原因 |
|---|---|
| 分组后少了一组 | 那列是空值,groupby 默认丢掉(dropna=False 可留) |
| 结果行标签乱了 | 分组键成了标签,reset_index 拉平 |
| 自定义函数报错 | 函数要接 Series 返回单值,别返回整列 |
这三条几乎每次做分组都会撞上一条,记住了能省不少时间。
! 分组统计的数一定要和总量对得上:各组人数加起来要等于总行数。
最后一个把现成函数和自定义函数放在一个列表里,一列上跑两个——这是 agg 最灵活的地方。 迭代里能做的事,agg 里未必能做;agg 能做的事,迭代一定也能做,只是慢。
30 秒实验
- 把 spread 改成返回 s.mean() + 1,看结果变化。
- 给 agg 传 {'语文': ['mean', 'max']},看列名变成几层。
- 想一想:为什么 agg 要求函数「返回一个数」而不是「返回一列」?
常见误区
- 自定义函数返回整列:agg 报错,因为一组要压成一个值。
- 字典 agg 的键写错列名:报 KeyError,或者悄悄少一列。
- 迭代里改原表:改的是子表的副本,原表不动。
- 多级列名不拉平就导出:Excel 里表头变成两行,下游对不上。
自测三问(点开对答案)
1. 迭代 groupby 拿到的是什么? —— 两样东西:组名,和这一组对应的子表(完整的 DataFrame)。 2. agg 传列表和传字典有什么区别? —— 列表是「同一批列都跑这几个函数」,字典是「每列配自己的函数」。 3. 自定义聚合函数有什么要求? —— 接一个 Series,返回一个标量。返回整列或返回 None 都会出错。分组键一直是表里的一列。下一篇讲键不在表里怎么办:用字典和 Series 指认。
- ☐ (本篇完,下一篇 4.8 用字典和 Series 分组)