Pandas分组聚合实战:从agg到transform的深度选择指南

当数据分析师面对分组聚合任务时,groupby().mean()往往是第一个跳入脑海的解决方案。但真实业务场景中,这种简单粗暴的处理方式常常导致信息丢失或计算逻辑受限。本文将带你突破单一聚合的思维定式,通过学生成绩分析的完整案例,深入剖析agg、apply和transform三大方法的本质差异与适用边界。

1. 为什么我们需要超越简单的groupby().mean()?

假设你手头有一份包含50名学生、5门课程成绩的数据集,业务方提出了三个需求:计算每个学生的平均分、找出每门课程的前10%高分学生、为每位学生标注其相对于班级平均水平的偏离程度。如果仅用groupby().mean(),你只能完成第一个需求——这正是大多数数据分析师面临的现实困境。

分组聚合的核心价值在于分组维度保留计算逻辑扩展的平衡。我们来看一个典型的学生成绩DataFrame示例:

import pandas as pd
import numpy as np

np.random.seed(42)
subjects = ['Math', 'Physics', 'Chemistry', 'Biology', 'Literature']
students = [f'S{str(i).zfill(2)}' for i in range(1, 51)]
data = {
    'Student': np.repeat(students, len(subjects)),
    'Subject': subjects * len(students),
    'Score': np.random.randint(50, 100, size=len(students)*len(subjects))
}
df = pd.DataFrame(data)

传统做法直接计算各科平均分:

df.groupby('Subject')['Score'].mean()

这种操作存在三个明显局限:

  1. 单维度输出:无法同时获取多种统计量(如标准差、分位数)
  2. 结构僵化:结果只能是缩减维度的聚合表
  3. 逻辑单一:难以实现条件聚合或跨列计算

2. agg:多维聚合的瑞士军刀

agg(aggregate的缩写)是处理复杂聚合需求的利器,它支持三种强大的参数模式:

2.1 多函数并行计算

df.groupby('Student')['Score'].agg(['mean', 'max', 'min', 'std'])

输出结果将包含四列统计量,这种操作在金融风控领域尤为常见,比如同时计算客户的交易频率、最大单笔金额和金额波动率。

2.2 列差异化聚合

当DataFrame包含多个数值列时,可以用字典指定各列的不同聚合方式:

# 假设新增一列'Attendance'表示出勤率
df['Attendance'] = np.random.uniform(0.7, 1.0, size=len(df))
df.groupby('Student').agg({
    'Score': ['mean', 'count'],
    'Attendance': 'median'
})

2.3 自定义聚合函数

agg支持传入自定义函数实现特殊计算逻辑。例如计算每位学生的成绩稳定性(最高分与最低分差):

def score_range(x):
    return x.max() - x.min()

df.groupby('Student')['Score'].agg(['mean', score_range])

注意:agg中的自定义函数应当返回标量值。若需要返回Series或DataFrame,应考虑使用apply。

3. apply:分组自由的终极形态

apply的强大之处在于其处理每个分组时,可以访问完整的子DataFrame,实现任意复杂度的计算。以下是三个典型应用场景:

3.1 条件过滤后聚合

找出每位学生得分最高的科目:

def top_subject(group):
    return group.nlargest(1, 'Score')

df.groupby('Student').apply(top_subject)

3.2 跨列计算

计算每位学生的"理科优势指数"(数理化平均分减去文科平均分):

def science_advantage(group):
    science = group[group['Subject'].isin(['Math','Physics','Chemistry'])]['Score'].mean()
    arts = group[group['Subject'] == 'Literature']['Score'].mean()
    return science - arts

df.groupby('Student').apply(science_advantage).sort_values(ascending=False)

3.3 分组可视化

直接在分组操作中生成可视化(需在Jupyter环境中运行):

def plot_scores(group):
    group.plot(x='Subject', y='Score', kind='bar', title=f"{group.name}'s Scores")
    return group.describe()

df.groupby('Student').apply(plot_scores)

性能提示:apply虽然灵活,但速度通常慢于agg。在数据量超过百万行时,应优先考虑向量化操作或Dask等并行计算方案。

4. transform:保持原貌的隐式聚合

transform的独特价值在于它返回与原始数据相同形状的结果,非常适合以下场景:

4.1 数据标准化

计算每位学生的Z-score(成绩与班级平均的标准差距离):

df['Z_score'] = df.groupby('Subject')['Score'].transform(
    lambda x: (x - x.mean())/x.std()
)

4.2 缺失值填充

用各科目中位数填充缺失值:

# 人为制造缺失值
df.loc[::10, 'Score'] = np.nan
df['Score_filled'] = df.groupby('Subject')['Score'].transform(
    lambda x: x.fillna(x.median())
)

4.3 排名计算

计算每门课程的成绩百分位:

df['Percentile'] = df.groupby('Subject')['Score'].transform(
    lambda x: x.rank(pct=True)
)

transform与agg的关键区别可以用SQL来类比:

  • transform ≈ 窗口函数(OVER PARTITION BY)
  • agg ≈ GROUP BY聚合

5. 方法选择决策树

面对具体业务问题时,可参考以下决策流程:

  1. 是否需要保持原始行数

    • 是 → 选择transform
    • 否 → 进入下一步
  2. 是否需要访问多列数据或复杂逻辑

    • 是 → 选择apply
    • 否 → 进入下一步
  3. 是否需要多种聚合函数或列差异化处理

    • 是 → 选择agg
    • 否 → 使用简单聚合函数(如mean)

实际项目中,这三种方法经常组合使用。例如先用transform计算分组标准化值,再用agg生成汇总统计,最后用apply实现业务规则判断。

Logo

码道开发者社区,聚焦华为云码道 CodeArts 代码智能体,沉淀 Agent、Skill、鸿蒙开发实战内容,供开发者查阅资料、交流技术、分享工程实践

更多推荐