概述

当内置聚合、transform、filter 无法满足需求时,可以使用 apply() 对每个组执行任意函数;pipe() 则用于管道式地向分组对象传入函数,便于构建可复用流程。


6.1 apply()

签名

GroupBy.apply(func, *args, include_groups=True, **kwargs)

参数默认值说明
func—作用在每个子组上的函数,可返回标量、Series 或 DataFrame
include_groupsTruepandas 2.2+,是否将分组列包含在传递给函数的子组中
*args / **kwargs—传给 func 的额外参数

特性

  • 灵活性最高:返回标量 → 类似聚合;返回 Series/DataFrame → 自动拼接;
  • 结果索引会叠加分组键(受 group_keys 参数控制);
  • ⚠️ apply() 内部会对结果做「对齐」处理,性能通常低于 agg() / transform()。

示例

import pandas as pd
import numpy as np
 
df = pd.DataFrame({
    "部门": ["A", "A", "B", "B", "C"],
    "员工": ["张三", "李四", "王五", "赵六", "孙七"],
    "薪资": [8000, 9500, 12000, 11000, 9000],
    "年龄": [30, 25, 40, 35, 28],
})
 
# 返回标量:等效于聚合
df.groupby("部门").apply(lambda x: x["薪资"].sum())
 
# 返回 Series:每组生成一个序列
df.groupby("部门").apply(lambda x: x["薪资"] - x["薪资"].mean())
 
# 返回 DataFrame:每组操作后自动拼接
df.groupby("部门").apply(lambda x: x.sort_values("薪资", ascending=False))
 
# 返回多行 DataFrame:每个组展开
df.groupby("部门").apply(lambda x: pd.DataFrame({
    "统计量": ["人数", "平均薪资"],
    "数值": [len(x), x["薪资"].mean()],
}))
 
# 传入额外参数
def 按阈值筛选(g, 阈值):
    return g[g["薪资"] >= 阈值]
 
df.groupby("部门").apply(按阈值筛选, 阈值=9000)

group_keys 的作用

# group_keys=True(默认):结果索引带有分组键层级
df.groupby("部门", group_keys=True).apply(lambda x: x.head(1))
 
# group_keys=False:不添加分组键层级,索引紧凑
df.groupby("部门", group_keys=False).apply(lambda x: x.head(1))

6.2 pipe()

pipe() 将 GroupBy 对象本身传给外部函数,用于函数式管道组合操作。

签名

GroupBy.pipe(func, *args, **kwargs)

# 定义一个接收 GroupBy 对象的函数
def 汇总并排序(gb, 列名):
    return gb[列名].agg(["sum", "mean"]).sort_values("sum", ascending=False)
 
df.groupby("部门").pipe(汇总并排序, "薪资")
 
# 多步管道组合
def 计算均值(gb, 列名):
    return gb[列名].mean()
 
def 转换为百分比(s):
    return s / s.sum() * 100
 
result = (
    df.groupby("部门")
    .pipe(计算均值, "薪资")
    .pipe(转换为百分比)
)
result

pipe() 与 apply() 的区别

  • apply(func):func 的入参是每个子组(DataFrame/Series);
  • pipe(func):func 的入参是整个 GroupBy 对象,一般用于串联外部处理函数。

常见管道模式

# 标准写法
df.groupby("部门").pipe(lambda gb: gb["薪资"].agg(["sum", "mean"]))
 
# 与 lambda 组合
df.groupby("部门").pipe(lambda gb: gb.size().sort_values(ascending=False))
 
# 自定义函数返回 DataFrame,继续处理
def 添加标记(gb, 标记值="★"):
    s = gb["薪资"].sum()
    return s.to_frame("总薪资").assign(标记=标记值)
 
df.groupby("部门").pipe(添加标记, "★")

相关笔记