拆分、施法、合并:Pandas groupby 的全景实战与工程最佳实践
你以为自己在用 groupby 汇总数据,实际上你在设计一条“管道”:把复杂数据按语义拆开、在局部施以规则、再把结果合上。这就是 split-apply-combine。
开篇引入
Python 从“优雅的脚本语言”成长为数据时代的生产力引擎,离不开 Pandas 在数据整形与分析上的强大表达力。无论是电商日常报表、风控特征聚合,还是实验日志分析,groupby
都是高频工具。它背后的思想非常简单却无处不在:把数据按键划分为子集(Split),对每个子集应用函数(Apply),最终再拼回一个结构化结果(Combine)。理解这个过程,你会写出更直观、更高效、更可靠的数据管道。
- 为什么写这篇文章:多年工程实践里,我见过太多“能跑但慢”“结果对但不可维护”的 groupby。本文从原理到实战,从 API 到性能,带你把
groupby
用得漂亮、稳健。 - 你将获得:
groupby
的心智模型、语法全景、聚合/变换/过滤的区别、复杂场景的模板、性能与内存优化策略、常见坑与排错清单。
基础概念:什么是 split-apply-combine
- Split(拆分):按一个或多个键,把 DataFrame 分割成若干“组”。键可以是列名、索引层、函数(按规则分箱)、字典/Series(自定义映射),甚至是多个来源的组合。
- Apply(施法):对每个组应用函数。函数可以是聚合(reduce 到标量)、变换(保留原行数)、过滤(丢弃不满足条件的组),或任意自定义。
- Combine(合并):把各组的结果拼接为新的 Series 或 DataFrame,并在索引上保留组键的语义(单层或 MultiIndex)。
心智模型:把 groupby(keys)
看成“把 keys 作为索引”,在“每个键的切片”上执行函数,再把切片结果叠回去。
入门例子:一眼看懂聚合、变换、过滤
import pandas as pd
import numpy as np
df = pd.DataFrame({
"city": ["Tokyo", "Tokyo", "Osaka", "Osaka", "Nagoya"],
"cat": ["A", "B", "A", "B", "A"],
"qty": [2, 3, 5, 1, 4],
"price":[10, 20, 30, 40, 50],
"user": [101,102,201,202,301]
})
g = df.groupby(["city", "cat"], as_index=True)
# 1) 聚合(Aggregation):每组变一个标量
agg_df = g.agg(
orders=("user", "nunique"),
qty_sum=("qty", "sum"),
price_mean=("price", "mean"),
)
# 结果:行数=组数,索引为 (city, cat)
# 2) 变换(Transform):每行都保留,按组广播结果
df["qty_zscore"] = g["qty"].transform(lambda s: (s - s.mean()) / (s.std(ddof=0) + 1e-8))
# 结果:与原 df 同行数,常用于特征标准化
# 3) 过滤(Filter):保留满足条件的组(整组保留或整组丢弃)
df_big_groups = g.filter(lambda sub: sub["user"].nunique() >= 2)
# 结果:丢掉小组的所有行
选择哪一种,取决于你想要“行数变不变”和“输出聚合到什么层级”。
语法全景:从 keys 到 agg 的丰富表达
groupby 的 keys(分组键)
- 列名或列名列表:
df.groupby("city") df.groupby(["city", "cat"])
- 索引层或层名:
df2 = df.set_index(["city", "cat"]) df2.groupby(level="city")
- 映射/函数:
# 映射:把城市归到大区 region_map = { "Tokyo": "East", "Osaka": "West", "Nagoya"</