groupby 是「拆分、计算、合并」三步,只写第一步什么都不会发生。 它返回一个 GroupBy 对象,后面必须接 mean、sum、agg 这类动作才有结果。 分组结果的行标签是分组键,不是 0、1、2,想拉平就 reset_index。
前五篇都在「整张表上算一个数」。真实分析里更常见的是「按类别分别算」。 本表按班级分两组,一班五个人、二班五个人。
一、先分组,它什么都不给你看
df.groupby('班级') 只做「拆」这一步,返回一个 GroupBy 对象。
直接打印它,看到的是一句 <pandas.core.groupby...>,没有数据。
分组结果里有两组:一班、二班。这个信息在 .groups 里能看到。
只写 groupby 等于什么都没干。必须再接一个动作。
二、接一个均值
| 班级 | 语文 | 数学 | 英语 |
|---|---|---|---|
| 一班 | 86.0 | 83.2 | 85.8 |
| 二班 | 79.4 | 82.8 | 78.0 |
一行的差别就出来了:一班语文比二班高 6.6 分,英语高 7.8 分。 数学两个班只差 0.4 分——整张表的均值把这三个差别全抹平了。 这就是分组的价值:它把「总体差不多」拆成「哪一块差在哪」。
行标签是班级,列是科目,这是一张「班级 × 科目」的交叉表。
三、一次要多个统计量
| 班级 | count | sum | mean | max |
|---|---|---|---|---|
| 一班 | 5 | 1275 | 255.0 | 277 |
| 二班 | 5 | 1201 | 240.2 | 274 |
df.groupby('班级')['总分'].agg(['count', 'sum', 'mean', 'max'])。
行是班级,列是四个函数名。一次跑完四个统计,比循环快也好读。
count 给的是「有几条记录」,没有缺失时和 size 一样;有缺失时 count 只数非空的。
这个区别在数据脏的时候会要命:以为有 5 条,其实只有 3 条有效。
四、分组键换成两列
df.groupby(['班级', '姓名']) 是两级分组,行标签变成两层。
姓名每个都不一样,所以每组只有一行——这种分组没有意义。
多级分组要有意义,键得有重复。班级有重复,姓名没有。
合理的多级键是「班级 + 性别」「省份 + 年份」这种,每级都有重复值。
五、动手 Demo:groupby:按类聚起来算
先只看分组、不接动作,体会「什么都没发生」。 再接 mean、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],
})
DF["总分"] = DF["语文"] + DF["数学"] + DF["英语"]
G = DF.groupby("班级")
print("分组对象 %s" % type(G).__name__)
print("分了 %d 组:%s" % (len(G), list(G.groups.keys())))
print(G[ ["语文", "数学", "英语"]].mean().round(2).to_string())
A = G["总分"].agg(["count", "sum", "mean", "max"])
print(A.round(2).to_string())
print("两列分组后 %d 组" % len(DF.groupby(["班级", "姓名"])))
实跑输出:
--------------------------------------------------------------------
groupby:按类聚起来算
--------------------------------------------------------------------
① 先分组
--------------------------------------------------------------------
groupby 只是「分组」,不写后面的计算,它什么都不返回给你看。
它返回的是一个 GroupBy 对象,必须再接一个动作才有结果。
分了 2 组:一班、二班。
! groupby 是「拆分、计算、合并」三步,只写第一步等于什么都没干。
② 接一个均值
--------------------------------------------------------------------
| 班级 | 语文 | 数学 | 英语 |
|---|---:|---:|---:|
| 一班 | 86.0 | 83.2 | 85.8 |
| 二班 | 79.4 | 82.8 | 78.0 |
两个班三科均值的对照,一行就出来了。一班语文 86.00,二班 79.40。
行标签是班级,列是科目——这是一张「班级 × 科目」的交叉表。
! 分组之后结果的行标签是分组键,不是 0、1、2。想拉平就 reset_index。
③ 换个函数
--------------------------------------------------------------------
| 班级 | count | sum | mean | max |
|---|---:|---:|---:|---:|
| 一班 | 5 | 1275 | 255.0 | 277 |
| 二班 | 5 | 1201 | 240.2 | 274 |
一次给 count、sum、mean、max 四个数,行是班级,列是四个函数名。
这里 count 给的是「有几条记录」,和 size 一样(没有缺失时)。
! agg 传一个函数名列表,等于一次跑好几个统计——比循环快,也比循环好读。
④ 分组键换成两列
--------------------------------------------------------------------
| 班级 | 姓名 | 总分 |
|---|---:|---:|
| 一班 | 刘洋 | 252 |
| 一班 | 张伟 | 265 |
| 一班 | 李娜 | 223 |
| 一班 | 王芳 | 277 |
传列表就是多级分组,行标签变成两层。
姓名每个都不一样,所以这一组每组只有一行——多级分组要有意义,键得有重复。
! 多级分组之后想按其中一层算,用 xs 或者先把行标签 reset 成一列再筛。
type(G).__name__ 打出来是 DataFrameGroupBy——它是个中间对象,不是表。
分组后行数会变。按班级分两组就剩 2 行,这是「合并」这一步做的。
30 秒实验
- 把 groupby 后面的 mean() 去掉,直接 print(G),看打出来是什么。
- 把分组键换成「姓名」,看结果有几行、有没有意义。
- 想一想:按班级分组求总分之和,两个班加起来和整表的总和一样吗?
常见误区
- 只写 groupby 就以为算完了:什么都不会输出。
- 忘了 groupby 后面要挑列:数值列都算一遍,还混进没意义的列。
- 以为 count 就是行数:它只数非空,有缺失时偏小。
- 用姓名做分组键:每组一行,等于给每行加了个标签。
自测三问(点开对答案)
1. groupby 的三步是什么? —— 拆分(按键分组)、计算(每组跑一个函数)、合并(把结果拼回一张表)。 2. 为什么 groupby 之后什么都不显示? —— 它只完成第一步,返回的是 GroupBy 对象,必须再接 mean、sum、agg 才有结果。 3. count 和 size 有什么区别? —— size 数行数,count 数非空值。没有缺失时两者一样,有缺失时 count 偏小。分组会用了,但只能接现成的函数。下一篇讲怎么把自定义函数塞进去。
- ☐ (本篇完,下一篇 4.7 分组之后:迭代、agg 与自定义函数)