5-2 聚合函数的诊断指标


文档摘要

5-2 聚合:诊断指标的三种输出形态 本节摘要:同样一次分组,agg把每组压成一个数、transform保持原表行数给每行标注组内信息、filter按组级指标整组放行或拦截。本节把三种输出形态一次讲透,并用命名聚合产出可直接进报告的列名。 上一节分组机制建好了,本节讲应用段:会诊报告要的不是"每组的和",而是"每组的和、均值、笔数、与组均值的偏离"——后一种需求就得换输出形态。 agg:一组长出多个诊断指标 列表与字典的 agg 会产生多层列名,报告里不好引用。命名聚合(pd.NamedAgg)一步到位: transform:保长输出,给每行标注组内信息 需求:找出"高于所在门店平均单"的交易。

5-2 聚合:诊断指标的三种输出形态

本节摘要:同样一次分组,agg把每组压成一个数、transform保持原表行数给每行标注组内信息、filter按组级指标整组放行或拦截。本节把三种输出形态一次讲透,并用命名聚合产出可直接进报告的列名。

上一节分组机制建好了,本节讲应用段:会诊报告要的不是"每组的和",而是"每组的和、均值、笔数、与组均值的偏离"——后一种需求就得换输出形态。

agg:一组长出多个诊断指标

import pandas as pd df = pd.DataFrame({ '门店': ['A', 'A', 'B', 'B', 'C'], '金额': [1280, 960, 1732, 880, 2100], }) # 一个键一个指标:单函数直接写 print(df.groupby('门店')['金额'].mean().round(1)) # 门店 # A 1120.0 # B 1306.0 # C 2100.0 # 一组多指标:agg接函数列表 print(df.groupby('门店')['金额'].agg(['sum', 'mean', 'count'])) # sum mean count # 门店 # A 2240 1120.0 2 # B 2612 1306.0 2 # C 2100 2100.0 1 # 不同列要不同指标:字典写法 print(df.groupby('门店').agg({'金额': ['sum', 'max']})) # 金额 # sum max # 门店 # A 2240 1280 # B 2612 1732 # C 2100 2100

列表与字典的 agg 会产生多层列名,报告里不好引用。命名聚合(pd.NamedAgg)一步到位:

report = df.groupby('门店').agg( 总金额=('金额', 'sum'), 均单=('金额', 'mean'), 笔数=('金额', 'count'), ) print(report) # 总金额 均单 笔数 # 门店 # A 2240 1120.0 2 # B 2612 1306.0 2 # C 2100 2100.0 1 print(report['均单'].idxmax()) # 'C' —— 但C只有一笔,下文filter会收拾它

transform:保长输出,给每行标注组内信息

需求:找出"高于所在门店平均单"的交易。错误思路是拿全局均值比(A店的1280会被B店的均值冤枉),正确思路是组内比较:

# transform返回与原表等长的序列:每行得到它所在组的均值 df['店均单'] = df.groupby('门店')['金额'].transform('mean') print(df) # 门店 金额 店均单 # 0 A 1280 1120.0 # 1 A 960 1120.0 # 2 B 1732 1306.0 # 3 B 880 1306.0 # 4 C 2100 2100.0 # 组内比较:一行布尔筛选搞定 print(df[df['金额'] > df['店均单']]) # 门店 金额 店均单 # 0 A 1280 1120.0 # 2 B 1732 1306.0

💡 关键直觉:agg越分越少、transform保持行数。判断用哪个只需要问一句:结果要贴回原来的每一行吗?要,就transform。

filter:按组级指标整组裁决

# 样本太少的组不可信:只有一笔的C店不该进报告 big_enough = df.groupby('门店').filter(lambda g: len(g) >= 2) print(big_enough['门店'].unique()) # ['A' 'B'] C整组被拦下 # 组均值不达标的整组淘汰 print(df.groupby('门店').filter(lambda g: g['金额'].mean() > 1100)['门店'].unique()) # ['A' 'B']

filter是"先汇总判断、再按行返回"的打包写法。手动实现要先算组指标、再isin、再布尔筛,四步并一步,且语义读起来就是一句话:留下满足条件的组。

三种输出形态对照

agg transform filter 输出形态对照

器械 输出形态 典型场景 结果行数
agg 每组一行 汇总报告 组数
transform 与原表等长 组内标注、组内比较 原行数
filter 原表的子集 按组级标准筛组 通过组的行数

05-5-fig01-5

本节要点回顾

  • agg接列表与字典出多指标,命名聚合自定义列名免多层索引
  • transform保长,组内比较先transform再布尔筛,两步完成
  • filter整组裁决,小样本组与不达标组整组拦截
  • 全局均值会冤枉好数据,组内比较才是公平口径
  • 三种出口的选择口诀:报告agg、标注transform、筛组filter

追问:自定义聚合函数怎么写

内置指标覆盖不了的需求(比如"每组前两大客户的合计")用自定义函数进agg。写法上只要记住契约:拿到一张子表,返回一个标量

def top2_share(g): """组内前两大单占组内总额的比例:客户集中度指标""" top2 = g.nlargest(2).sum() return round(top2 / g.sum(), 3) print(df.groupby('门店')['金额'].agg(top2_share)) # 门店 # A 0.994 # 两单几乎就是全部,集中度极高 # B 0.928 # C 1.000 # 只有一单,比例自然封顶

注意C店的1.0——单笔组里集中度指标退化,这类"指标在小样本上的假信号"正是上一节filter要把小样本组拦出去的原因。自定义聚合虽好,代价是失去向量化加速,十万行以上明显变慢,能用内置就别自定义。

一个完整的会诊指标卡

把本节三件器械拼成一张管理层口径的指标卡:

card = df.groupby('门店').agg( 总额=('金额', 'sum'), 均单=('金额', 'mean'), 笔数=('金额', 'count'), ).assign( 单笔贡献=lambda x: (x['总额'] / x['笔数']).round(1), 总额占比=lambda x: (x['总额'] / x['总额'].sum() * 100).round(1), ) print(card) # 总额 均单 笔数 单笔贡献 总额占比 # 门店 # A 2240 1120.0 2 1120.0 36.1 # B 2612 1306.0 2 1306.0 42.1 # C 2100 2100.0 1 2100.0 33.8

命名聚合出基础列、assign补派生列,一气呵成。这张卡就是下一节要排版的原始素材。

下一节把会诊结果排版成正式报告:透视表与交叉表。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U