
本文详解如何通过自定义函数工厂配合 groupby().agg() 一次性计算多个字段的任意分位数,并生成语义清晰的列名(如 bell_q10),避免循环拼接,兼顾可读性、简洁性与计算效率。
本文详解如何通过自定义函数工厂配合 `groupby().agg()` 一次性计算多个字段的任意分位数,并生成语义清晰的列名(如 `bell_q10`),避免循环拼接,兼顾可读性、简洁性与计算效率。
在 Pandas 数据分析中,常需对分组数据同时计算多种统计量(如均值、中位数、特定分位数)。虽然 Series.quantile() 和 GroupBy.quantile() 可直接获取分位数,但它们返回的是多级索引 Series,难以与常规聚合(如 "mean"、"median")统一集成到一次 agg() 调用中——这正是用户遇到的核心痛点:希望用声明式语法(如 ("bell", "q90"))实现“一调用、多结果、好命名”。
解决方案:函数工厂(Function Factory)
关键在于将参数化的 quantile(q) 封装为无参函数,使其能被 agg() 正确识别为聚合器:
def quantile(q=0.5, **kwargs):
def f(series):
return series.quantile(q, **kwargs)
return f该函数返回一个闭包 f,它接收 Series 并调用 series.quantile(q),完全符合 agg() 对自定义聚合器的签名要求(单参数 Series → 标量或 Series)。
完整示例:混合聚合 + 命名分位数
import pandas as pd
import numpy as np
rng = np.random.default_rng(seed=18860504)
df = pd.DataFrame({
"dummy": 1,
"bell": rng.normal(loc=0, scale=1, size=100),
"fish": rng.poisson(lam=10, size=100),
"cabin": rng.lognormal(mean=0, sigma=1.0, size=100),
})
# 定义分位点(如 q10, q50, q90)
quants = [0.1, 0.5, 0.9]
# 使用函数工厂构建聚合字典
agg_dict = {
# 基础统计量
"bell_med": ("bell", "median"),
"bell_mean": ("bell", "mean"),
"fish_med": ("fish", "median"),
"fish_mean": ("fish", "mean"),
# 分位数(动态命名)
**{f"{col}_q{int(q*100):02d}": (col, quantile(q))
for q in quants
for col in ["bell", "fish"]}
}
result = df.groupby("dummy").agg(**agg_dict)
print(result)输出示例(列名清晰、结果紧凑):
bell_med bell_mean fish_med fish_mean bell_q10 bell_q50 bell_q90 fish_q10 fish_q50 fish_q90 dummy 1 -0.063454 -0.058557 10.0 9.92 -1.553682 -0.063454 1.452172 7.0 10.0 14.0
注意事项与最佳实践
- ✅ 性能优势:
agg()内部会对每个分组只遍历一次数据,比多次groupby().quantile()更高效; - ⚠️ 命名冲突:确保生成的列名(如
"bell_q10")不与内置方法名(如"quantile")重复; - ? 扩展性:可轻松扩展至其他参数化统计(如
std(ddof=0)、rolling(3).mean()),只需调整函数工厂; - ? 兼容性:该方案完全兼容 Pandas 1.4+,且支持
agg()的所有输入格式(元组、字典、列表); - ? 进阶技巧:若需保留原始分位数索引(如
q10,q50),可改用lambda s: s.quantile(quants),但此时需额外stack()或unstack()处理层级,牺牲了列命名的直观性。
综上,函数工厂是连接“参数化聚合”与“声明式 agg()”的优雅桥梁——它既保持了代码的可读性与可维护性,又充分发挥了 Pandas 向量化计算的性能潜力。

















