本课导读
前五课都在"整理"数据,这课开始"拷问"数据:各城市卖了多少?每类产品平均单价?谁是销冠?——这类"按 X 分组看 Y"的问题是一切业务分析的原型,pandas 的答案叫 groupby。你其实早就手写过它(U0.4 的"遍历 + 字典计数"、U1.3 项目的分组归档),今天见它的终极形态:一行出结果。预计 60 分钟。
从手写分组到 groupby
先看你的老手艺,再看工业版——同一个问题:"各城市销售额合计":
Python
import pandas as pd
raw = """order_id,product,city,amount
1001,键盘,上海,299
1002,鼠标,北京,178
1003,显示器,上海,1599
1004,键盘,深圳,897
1005,鼠标,北京,89
1006,耳机,上海,918"""
with open("orders.csv", "w", encoding="utf-8") as f:
f.write(raw)
df = pd.read_csv("orders.csv")
# 老手艺(U0.4 的遍历 + 字典收集):
totals = {}
for _, row in df.iterrows():
totals[row["city"]] = totals.get(row["city"], 0) + row["amount"]
print(totals)
print("―" * 30)
# 工业版:一行
print(df.groupby("city")["amount"].sum())
读法拆解 df.groupby("city")["amount"].sum(),三段式:
groupby("city")——按城市把行分堆(上海一堆、北京一堆…)
["amount"]——每堆里只关心金额列
.sum()——对每堆执行聚合(求和)
提示
类比记忆:groupby 是把一副扑克按花色分堆,再对每堆做同一件事(数张数、加点数、找最大)。口诀读作"按什么分、看什么列、算什么数"——任何 groupby 语句都能这么读出声。
聚合函数随便换:.sum() 合计、.mean() 平均、.count() 计数、.max()/.min() 极值。换着问几个问题:
Python
import pandas as pd
raw = """order_id,product,city,amount
1001,键盘,上海,299
1002,鼠标,北京,178
1003,显示器,上海,1599
1004,键盘,深圳,897
1005,鼠标,北京,89
1006,耳机,上海,918"""
with open("orders.csv", "w", encoding="utf-8") as f:
f.write(raw)
df = pd.read_csv("orders.csv")
print(df.groupby("product")["amount"].mean()) # 各产品平均客单
print("―" * 30)
print(df.groupby("city")["order_id"].count()) # 各城市订单量
print("―" * 30)
# 结果是 Series,接着用老功夫:排序找冠军
print(df.groupby("city")["amount"].sum().sort_values(ascending=False))
注意最后一招:groupby 的结果还是 Series/DataFrame,排序、head、筛选照常连招——流水线不断。
一次算多个指标:agg
老板从来不只问一个数:"各城市的总额、平均、单数都给我"——agg 一次交齐:
Python
import pandas as pd
raw = """order_id,product,city,amount
1001,键盘,上海,299
1002,鼠标,北京,178
1003,显示器,上海,1599
1004,键盘,深圳,897
1005,鼠标,北京,89
1006,耳机,上海,918"""
with open("orders.csv", "w", encoding="utf-8") as f:
f.write(raw)
df = pd.read_csv("orders.csv")
report = df.groupby("city")["amount"].agg(["sum", "mean", "count"])
print(report)
print("―" * 30)
# 列名太程序员?改成人话再交差:
report.columns = ["总额", "平均单价", "订单数"]
print(report.round(1))
agg(["sum", "mean", "count"])——把聚合函数名列成清单,一次全算。产出直接就是一张报表(round(1) 顺手控制小数位)。
多列分组:更细的切面
"每个城市每种产品卖了多少"——按两个维度分堆,groupby 收个列表就行:
Python
import pandas as pd
raw = """order_id,product,city,amount
1001,键盘,上海,299
1002,鼠标,北京,178
1003,显示器,上海,1599
1004,键盘,深圳,897
1005,鼠标,北京,89
1006,键盘,上海,598"""
with open("orders.csv", "w", encoding="utf-8") as f:
f.write(raw)
df = pd.read_csv("orders.csv")
by_two = df.groupby(["city", "product"])["amount"].sum()
print(by_two)
print("―" * 30)
# 层级看着费劲?reset_index() 拍平成普通表格
print(by_two.reset_index())
两个实用点:分组列表 ["city", "product"] 顺序即层级(先按城市、城市内再按产品);结果的"缩进式"行标签叫多级索引,看着费劲就 reset_index() 拍平成普通表——拍平之后又是熟悉的 DataFrame,接着筛选排序都随意。
value_counts 再登场:最快的"分组计数"
上节课的显微镜其实是 groupby 的特化快捷键——"按这列分组、数每组行数、降序排好":
Python
import pandas as pd
raw = """order_id,product,city
1001,键盘,上海
1002,鼠标,北京
1003,显示器,上海
1004,键盘,上海
1005,鼠标,北京"""
with open("orders.csv", "w", encoding="utf-8") as f:
f.write(raw)
df = pd.read_csv("orders.csv")
print(df["city"].value_counts()) # 等价于 groupby("city").size() 再降序
print(df["city"].value_counts(normalize=True).round(2)) # 加 normalize:占比!
只是数次数就用 value_counts(还能一键出占比);要算金额等其他列才请 groupby——快捷键和全功能各有主场。
✍️ 练习
练习 1:口播翻译
把三句业务提问翻译成 groupby(用"按什么分、看什么列、算什么数"口诀):① 各部门平均工资 ② 各部门人数 ③ 各部门最高工资,并降序排列。
Python
import pandas as pd
raw = """name,dept,salary
小雨,设计,12000
阿杰,运营,9500
丽丽,设计,13000
老王,销售,11000
小陈,运营,8800"""
with open("staff.csv", "w", encoding="utf-8") as f:
f.write(raw)
df = pd.read_csv("staff.csv")
# 你的代码写在下面
✅ 参考答案
import pandas as pd
raw = """name,dept,salary
小雨,设计,12000
阿杰,运营,9500
丽丽,设计,13000
老王,销售,11000
小陈,运营,8800"""
with open("staff.csv", "w", encoding="utf-8") as f:
f.write(raw)
df = pd.read_csv("staff.csv")
print(df.groupby("dept")["salary"].mean())
print(df.groupby("dept")["name"].count())
print(df.groupby("dept")["salary"].max().sort_values(ascending=False))
练习 2:一张报表交差
运营周会要一张表:各产品的销售总额、平均值、订单数,列名用中文,按总额降序。
Python
import pandas as pd
raw = """order_id,product,amount
1001,键盘,299
1002,鼠标,178
1003,显示器,1599
1004,键盘,897
1005,鼠标,89
1006,键盘,598"""
with open("orders.csv", "w", encoding="utf-8") as f:
f.write(raw)
df = pd.read_csv("orders.csv")
# 你的代码写在下面
💡 提示
agg 三件套 → columns 改中文 → sort_values("总额", ascending=False)。
✅ 参考答案
import pandas as pd
raw = """order_id,product,amount
1001,键盘,299
1002,鼠标,178
1003,显示器,1599
1004,键盘,897
1005,鼠标,89
1006,键盘,598"""
with open("orders.csv", "w", encoding="utf-8") as f:
f.write(raw)
df = pd.read_csv("orders.csv")
report = df.groupby("product")["amount"].agg(["sum", "mean", "count"])
report.columns = ["总额", "平均", "订单数"]
print(report.sort_values("总额", ascending=False).round(1))
练习 3:先洗后问(挑战)
真实节奏:这份数据带脏(城市空格、金额脏值)。先用上节课的流水线洗净,再回答"各城市有效销售总额,谁第一?"——洗和问必须在同一段代码里完成。
Python
import pandas as pd
raw = """order_id,city,amount
1001, 上海,299
1002,北京 ,178
1003,上海,1599
1004,深圳,补录中
1005, 北京,89
1006,上海,918"""
with open("dirty_orders.csv", "w", encoding="utf-8") as f:
f.write(raw)
df = pd.read_csv("dirty_orders.csv")
# 你的代码写在下面
✅ 参考答案
import pandas as pd
raw = """order_id,city,amount
1001, 上海,299
1002,北京 ,178
1003,上海,1599
1004,深圳,补录中
1005, 北京,89
1006,上海,918"""
with open("dirty_orders.csv", "w", encoding="utf-8") as f:
f.write(raw)
df = pd.read_csv("dirty_orders.csv")
df["city"] = df["city"].str.strip()
df["amount"] = pd.to_numeric(df["amount"], errors="coerce")
df = df.dropna(subset=["amount"])
result = df.groupby("city")["amount"].sum().sort_values(ascending=False)
print(result)
print(f"销冠城市:{result.index[0]}")
不洗直接 groupby 的话:上海会分裂成两组、"补录中"直接让求和报错——清洗永远在分析前面,这是下一课项目和真实工作的铁律。
📝 随堂测验
1. df.groupby("city")["amount"].sum() 的三段式读法是?
2. “各城市的总额、平均、订单数一次算齐”,用什么?
3. groupby(["city", "product"]) 结果的缩进式行标签看着费劲,怎么拍平?
4. 只想数“每个城市各有几行”,最快的写法是?
5. 对带脏数据的表直接 groupby 会发生什么?
本课小结
- groupby 口诀:按什么分、看什么列、算什么数——
df.groupby("列")["列"].sum()
- 多指标
agg([...]) 出报表;多列分组出层级,reset_index() 拍平;结果可继续排序连招
- 只数次数用
value_counts()(normalize 出占比);算账用 groupby
- 铁律:清洗在分析前面——脏数据会被分组统计忠实放大
下一课把多张表"合体":订单表连上客户表——merge 与 concat,数据界的接亲家。