news 2026/10/21 19:39:38

Pandas groupby实战:分组聚合、transform与性能优化全攻略

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Pandas groupby实战:分组聚合、transform与性能优化全攻略

搞数据分析的人,没有谁没被groupby救过命。我刚入行那会儿,为了算一个“按城市分组的平均销售额”,手动写循环加条件判断,吭哧吭哧搞了十几行代码,逻辑绕得自己都看不懂。后来换成groupby,三行搞定,速度还快了一个量级。可以说,groupby是 pandas 里最值得花时间吃透的方法之一。

这篇文章不是官方文档的中文翻译,而是把我在实际项目中用过的groupby技巧完整梳理一遍,从分组、聚合、转换、过滤到性能优化,每一段都有可直接运行的代码、适用场景和踩坑提醒。不管你是刚接触 pandas 的初学者,还是用了一两年但没系统整理过groupby用法的人,这篇都能帮你把这块短板补上。建议直接收藏,用的时候翻出来对照。

1. 先搞懂 groupby 到底在干什么

很多教程上来就丢各种聚合函数,却不说底层逻辑。我建议你在敲任何代码之前,先花五分钟把groupby的执行模型搞清楚,后面所有操作都不会再疑惑。

groupby的核心思想是三个步骤,业内叫split-apply-combine,也就是“拆分-应用-合并”:

  1. Split 拆分:按照指定的键,把 DataFrame 拆成若干个分组,每个分组是一份子数据。
  2. Apply 应用:对每个分组独立执行操作,比如求和、求均值、自定义函数、标准化等。
  3. Combine 合并:把每个分组的计算结果汇总成一个新的 DataFrame 或 Series。

看一个最简单的例子:

import pandas as pd df = pd.DataFrame({ '城市': ['北京', '上海', '北京', '上海', '北京', '上海'], '销售额': [100, 200, 150, 180, 120, 210], '成本': [60, 110, 80, 90, 70, 100] }) df.groupby('城市').mean()

执行结果:

销售额 成本 城市 北京 123.333333 70.0 上海 196.666667 100.0

程序做的事情是:先按“城市”列把 6 行数据拆成“北京组”3 行、“上海组”3 行,再对每一组分别调用mean()求均值,最后把两端结果拼成一张新表。

这里有一个新手最容易困惑的点:df.groupby('城市')这一行运行之后,你直接打印它,看到的不是一个分组后的 DataFrame,而是类似<pandas.core.groupby.groupby.DataFrameGroupBy object at 0x...>这样的内存地址。这是因为groupby是惰性求值的——它只记录了分组配置,还没有真正计算。只有当你调用.mean()、.sum()、.agg()、.apply()这类方法时,拆分、计算、合并才会真正触发。

理解这个特性很重要,因为它解释了为什么你可以在groupby之后链式调用各种方法,也解释了为什么你在调试时要习惯用.groups属性去看分组情况。

groupby对象上有几个高频使用的“检查类”属性:

# 查看每个分组包含哪些原始索引 df.groupby('城市').groups # 查看每个分组的行数 df.groupby('城市').size() # 查看分组总数 df.groupby('城市').ngroups # 提取某个分组的子 DataFrame df.groupby('城市').get_group('北京')

其中size()我几乎天天用,它能快速告诉你每个组的样本量,这在查数据分布是否均衡时特别管用。get_group('北京')适合你想单独拎出一个组做排查的场景,省得再写一次布尔索引。

还有一个细节:groupby默认按列分组,但同样可以按行分组,只要把axis=1传进去。不过在常规数据分析中,按行分组的场景很少,知道有这么个参数就行。

2. 分组维度怎么选,决定了后续操作顺手不顺手

groupby的参数by是整个方法的灵魂,它可以接收非常多种形式:列名、多个列名、Series、自定义函数、索引层级等等。这节我把常用分组方式全部罗列出来,方便你对照选用。

2.1 按单个列分组

最基础的形式,直接传列名字符串:

df.groupby('城市')['销售额'].sum()

这里有一个高频疑问:为什么很多教程在groupby之后还要带一对方括号['销售额']?

因为代码里的执行顺序是:先按“城市”把全表分组,然后只从每个组里取出“销售额”这一列,最后做求和。这样返回的是Series,不带方括号则对全部数值列做聚合,返回DataFrame。两者结果形状不一样,用哪个取决于你接下来要什么。

如果你想对多个列做不同的聚合,又不希望返回 Series,可以用列表形式选列:

df.groupby('城市')[['销售额', '成本']].mean()

2.2 按多个列分组

传一个列名列表,就按多列组合成多个分组:

df2 = pd.DataFrame({ '城市': ['北京', '北京', '上海', '上海', '北京', '上海'], '季度': ['Q1', 'Q2', 'Q1', 'Q2', 'Q1', 'Q2'], '销售额': [100, 150, 200, 180, 120, 210] }) df2.groupby(['城市', '季度'])['销售额'].sum()

结果会是一个多重索引的 Series,外层索引是“城市”,内层是“季度”。这种结构在后续unstack()转置、用pivot_table对比时都非常方便。

我个人的经验是:多列分组常用于两个维度的交叉统计,比如“城市 × 季度”“部门 × 月份”。但要注意,分组键越多,分组数量越多,如果某个组合没有数据,默认情况下该组合不会出现在结果里,后面讲observed参数时还会再提到这个坑。

2.3 按索引层级分组

如果你的 DataFrame 本身就是多层索引,可以直接按索引层级名分组,不需要额外加列:

df3 = df2.set_index(['城市', '季度']) df3.groupby(level='城市')['销售额'].sum()

参数level指定按哪一层索引分组。这在处理时间序列或者已经做了set_index的多维数据时很省事,不用再把索引转回普通列。

2.4 按区间分组(cut 和 qcut)

连续数值列按区间分组,是groupby和pd.cut/pd.qcut组合最经典的用法。比如把销售额分成几个档位,再看每个档位的分布情况:

bins = [0, 100, 150, 200, 300] labels = ['低', '中低', '中高', '高'] df['销售档位'] = pd.cut(df['销售额'], bins=bins, labels=labels) df.groupby('销售档位', observed=False)['销售额'].agg(['count', 'mean'])

pd.cut按等间距分箱,pd.qcut按分位数分箱,两者都能把连续变量变成离散变量,然后就能作为分组键使用。业务里常见的是把年龄分年龄段、把消费金额分消费等级,这类需求几乎都能用这个组合完成。

2.5 按自定义函数分组

如果想按“城市名长度”或“销售额是否过均值”这类逻辑分组,可以传一个函数给groupby:

df.groupby(lambda x: '大城市' if df.loc[x, '销售额'] > 150 else '小城市')['销售额'].sum()

不过这种写法在实际业务中用得不多,因为可读性差,调试也麻烦。我更建议先在原表上用apply增加一列分桶结果,再用普通列名分组。代码会清楚很多:

df['城市规模'] = df['销售额'].apply(lambda v: '大城市' if v > 150 else '小城市') df.groupby('城市规模')['销售额'].sum()

按自定义函数分组适合临时性、探索性的数据课题,不适合放进正式脚本。

2.6 按外部 Series 分组

by参数可以传入另一个长度与 DataFrame 相同的 Series 或数组。即使这个 Series 不在 DataFrame 里,也能作为分组键:

groups = pd.Series(['A组'] * 3 + ['B组'] * 3) df.groupby(groups)['销售额'].mean()

这种用法在数据清洗时偶有奇效,比如根据外部规则文件给每一行打标签,再按标签分组汇总。不过正常项目里我还是推荐把分组键合并进表里,保持数据来源一致,逻辑更清晰。

3. 聚合操作的核心:agg 比表面看起来强大得多

分组之后最常用的动作就是聚合。sum()、mean()、count()这种直接调用的聚合函数只是入门,真正能拉开效率差距的是agg()方法,它支持同时应用多个聚合函数、为不同列指定不同聚合方式。

3.1 同时计算多个统计量

df.groupby('城市')['销售额'].agg(['sum', 'mean', 'max', 'min'])

结果会生成一个新的 DataFrame,列名分别是sum、mean、max、min。这在日报统计里几乎就是标配:每个城市的总销售额、客单价均值、最高单笔、最低单笔,一行代码全部出来。

3.2 不同列用不同聚合函数

传一个字典给agg,键是列名,值是聚合方式:

df.groupby('城市').agg({ '销售额': ['sum', 'mean'], '成本': 'sum' })

这里要特别提醒:字典的键必须是真实存在的列名,不能是经过变换的表达式。如果你想对“销售额 × 系数”这种计算结果做聚合,得先新增一列再聚合,或者用NamedAgg配合lambda实现,下一条会讲。

3.3 用 NamedAgg 给聚合结果命名

默认的聚合列名是函数名,比如sum、mean,一旦你用了lambda,列名就会变成<lambda_0>,非常难看。NamedAgg可以解决命名问题:

df.groupby('城市').agg( 总销售额=('销售额', 'sum'), 平均销售额=('销售额', 'mean'), 成本总计=('成本', 'sum') )

注意这是新写法,内部元组的第一个元素是列名,第二个是聚合函数名。生成的列名就是你指定的总销售额、平均销售额、成本总计,后续不需要再做rename。

3.4 在 row 上直接处理后再聚合

agg本质是“逐列”处理,如果你想在分组内先算一笔衍生指标,比如“销售额 - 成本”的毛利,再求毛利总和,有两种常见方案。

方案一:先算列,再分组聚合。

df['毛利'] = df['销售额'] - df['成本'] df.groupby('城市')['毛利'].sum()

方案二:用apply结合自定义函数:

df.groupby('城市').apply(lambda g: (g['销售额'] - g['成本']).sum())

这里我要认真说一下agg和apply的区别,因为这是很多人搞混的地方。

agg接收的函数是“按列”传入的,也就是说每个聚合函数一次拿到一列数据;apply接收的函数是整个子 DataFrame。这意味着:

  • 想用两列以上的数据计算一个标量,用apply。
  • 想对同一列计算多个标量,用agg配合列表。
  • 想对不同列分别计算,用agg配合字典。

举例来说,如果我想算每个城市“销售额”和“成本”两个向量的相关系数,agg就没法受理,因为相关系数需要同时传入两列;而apply可以:

df.groupby('城市').apply(lambda g: g['销售额'].corr(g['成本']))

我在实际项目中很少让agg和apply互相替代,它们的产出类型也不同。

3.5 聚合结果如何转回普通 DataFrame

聚合结果默认会把分组键作为索引,形成 MultiIndex 或 Index。想要变成普通列,有三种方式:

  • reset_index():最通用,把索引转回普通列。
  • 参数as_index=False:在groupby时直接指定,这样分组键就以列的形式保留。
  • .reset_index()后还可以再rename聚合结果列名。
# 方式一 df.groupby('城市', as_index=False)['销售额'].sum() # 方式二 df.groupby('城市')['销售额'].sum().reset_index()

两者的最终产出一样,我习惯用as_index=False,因为少一步链式调用,代码更干净。

4. transform 和 filter:聚合之外的两大场景,很多人没搞明白

很多资料把groupby讲完聚合就结束了,实际业务里还有两类高频需求:一是想在保留原表行数的基础上填充分组统计量(比如把每个城市的平均销售额填回每一行),二是想筛掉某些分组。前者用transform,后者用filter。

4.1 transform:不折叠行数,广播分组结果

transform执行聚合计算后,返回的是和原表行数相同的 Series 或 DataFrame,分组键不会折叠,而是把计算结果广播到每一行。这是它与agg最直观的差异。

直接看一个业务场景:我想把每条记录的销售额减去其所在城市的平均销售额,得到“偏离度”。用transform非常直接:

df['城市销售额均值'] = df.groupby('城市')['销售额'].transform('mean') df['偏离均值'] = df['销售额'] - df['城市销售额均值']

没有transform时,你大概率会写出这样的绕路代码:

city_mean = df.groupby('城市')['销售额'].mean() df['城市销售额均值'] = df['城市'].map(city_mean)

一天两天可能不痛不痒,但脚本里这种“先聚合再合并”的写法一旦多起来,可读性就会迅速下降。transform把这个过程压缩成一行,逻辑也更清晰。

transform同样支持多个函数、组合使用,但它的核心要求是:返回的长度必须与输入一致。如果你对transform传了一个返回标量的函数,它会自动广播;如果返回的是列表或数组,长度也得和当前分组的行数对齐。

4.2 分组内填充缺失值

数据清洗里的经典场景:每个城市的部分记录缺了销售额,你想用同城市的均值把空值补上。transform一行搞定:

df['销售额填充'] = df.groupby('城市')['销售额'].transform(lambda s: s.fillna(s.mean()))

先按城市分组,组内取均值,再fillna,最后广播到原行。这样清洗出出来的数据带有明显的业务逻辑:用同类别的均值填充,而不是用全局均值一股脑硬填。

4.3 groupby + transform 与 merge 的区别

这两者都能把分组统计结果“传回”原数据,但机制不同。merge需要你指定关联键,本质是外键连接,适合两个不同来源的数据表;而groupby + transform只对同一个表内部做分组,不用显式定义关联关系,索引会自动对齐。能用transform时我不推荐绕道merge,少一步关联就少一个出错点。

4.4 filter:按组整体筛选

filter用于过滤掉“整个分组”而不只是个别行。它其实有三种写法:

  • 按组的统计特征过滤;
  • 按组成员的某列值过滤;
  • 按组的索引或标签过滤。

最常见的是第一类:保留样本量足够大的组。

df.groupby('城市').filter(lambda g: len(g) >= 2)

这段代码会保留所有大于等于 2 行的城市分组,其余组的所有行都会被删掉。注意:这里返回的是分组之后的一个子 DataFrame,行数不定,和transform的“保行数”逻辑不同。

另一种按组内属性过滤的写法:

df.groupby('城市').filter(lambda g: g['销售额'].max() > 200)

只保留组内销售额最大值超过 200 的城市。这在业务上对应“剔除表现过差的组”这类需求,比如去掉退款率过高的省份、去掉成交量过低的渠道。

这里我要强调一点:filter不是用来做普通行筛选的。普通行筛选(比如“只保留销售额大于 100 的行”)直接用布尔索引即可,不要先groupby再filter,没有意义还浪费性能。

4.5 三个方法放到一起看

方法返回值形状典型用途映射关系
agg每个分组一行分组汇总、生成统计报表分组键 → 统计值
transform与原表行数一致组内填充、去均值、标准化每个分组行 → 广播值
filter原表子集剔除不合格分组分组 → 保留/删除整个组

这三者覆盖了分组后的绝大多数操作需求,也是我在面试候选人时最常考察的区分点。能把它们说清楚,说明对groupby的理解不是背代码,而是真正掌握到了模型层面。

5. 三大实战场景:组内排名、透视表替换、标准化与缺失值清洗

理论讲再多,都不如直接上业务场景。这一节我把实战里出现频率最高的三个组合方案完整写出来,你可以直接抄作业。

5.1 组内排名与 Top N 提取

需求:每个城市找出销售额最高的两条记录。

df['销售额排名'] = df.groupby('城市')['销售额'].rank(method='dense', ascending=False) df[df['销售额排名'] <= 2].sort_values(['城市', '销售额排名'])

rank是分组后最容易被忽略的宝藏方法。它的method参数有几个分支:

  • 'dense':并列名次连续,比如 1、1、2、3;
  • 'min':并列名次取最小,比如 1、1、3、4;
  • 'max':并列名次取最大,比如 1、1、2、4;
  • 'first':并列时按出现顺序分先后,比如 1、2、3、4。

业务上我常用'dense',因为当销售额相同时名次连续,方便后续按名次区间切分。

这里有一个容易踩的坑:groupby默认会在分组键上排序,所以分组内rank计算没问题,但你sort_values时注意不要按原始索引排序,否则每组数据顺序会乱掉。

5.2 用 groupby + unstack 复刻透视表

很多场景下,pivot_table能完成的工作,groupby组合也能完成。两者底层实现其实有关联,但groupby写起来更灵活。

例子:统计每个城市、每个季度的销售额总和,并转成宽表:

df2.groupby(['城市', '季度'])['销售额'].sum().unstack(fill_value=0)

结果会生成一张行是城市、列是季度的矩阵。这正是透视表的经典形态。区别在于:pivot_table更直观地接收index、columns、values、aggfunc四个参数,适合快速搭建;groupby + unstack可以在分组和聚合环节插入更多自定义逻辑,适合需要复杂前处理的场景。

如果你要同时统计均值、总和两个指标,pivot_table的aggfunc参数支持传列表:

df2.pivot_table(values='销售额', index='城市', columns='季度', aggfunc=['sum', 'mean'], fill_value=0)

而groupby版本的写法如下:

df2.groupby(['城市', '季度'])['销售额'].agg(['sum', 'mean']).unstack(fill_value=0)

谁的输出更符合你的字段要求就选谁,二者没有绝对优劣。我个人的体会是:如果纯做二维交叉表,用pivot_table更快;如果后面还要继续分组、过滤或转换,那就用groupby链式操作,代码从头到尾只是一个对象流。

5.3 分组标准化与缺失值填充

“组内标准化”在数据分析中很常见,比如按城市把销售额做 Z-score 标准化,排除城市间整体水平的干扰:

df['销售额标准化'] = df.groupby('城市')['销售额'].transform( lambda s: (s - s.mean()) / s.std() )

这个方法的价值在于:它把“每个城市自己的均值、标准差”作为基准,而不是全局统一基准。这样处理后,不同城市之间的绝对销售额差异被抹平,只看城市内部的相对波动,适合比较不同量纲的分组数据。

缺失值填充方面,除了前文说过的分组均值填充,还可以做分组前向填充:

df['销售额前向填充'] = df.groupby('城市')['销售额'].transform(lambda s: s.fillna(method='ffill'))

这种写法在时间序列数据里非常实用——按城市分组,组内按时间顺序把缺失值用上一个非缺失值填充。注意新版 pandas 里fillna(method='ffill')会提示改用ffill()方法,写法可以调整为:

df['销售额前向填充'] = df.groupby('城市')['销售额'].transform(lambda s: s.ffill())

6. 性能调优与避坑清单:这些细节决定了你的脚本能不能扛住真实数据

很多人在小数据集上跑groupby觉得很流畅,一到几百 MB 的真实数据就开始卡顿,问题往往出在几个不起眼的参数和用法上。这一节内容是我在实际项目中踩过坑之后总结出来的,每条都值得认真看。

6.1 分组列用 category 类型,性能会有本质区别

当你的分组键是字符串但取值有限时(比如只有“北京”“上海”“广州”等几十个城市),最好转成category类型。这能让分组操作从“字符串比较”降级为“整数编号查找”,速度提升非常显著:

df['城市'] = df['城市'].astype('category') df.groupby('城市')['销售额'].sum()

我试过用 1000 万行、50 个城市的表做分组求和,转类型之前耗时接近 10 秒,转之后 1.5 秒左右,体感差距极大。在read_csv阶段就能顺手转:

df = pd.read_csv('销售数据.csv', dtype={'城市': 'category'})

这个技巧对于任何需要反复分组的场景都值得养成习惯。

6.2 observed 参数:避开“空分组”陷阱

上一节我留了一个扣子:groupby配合category列时,默认行为是保留所有类别组合,即使某个组合没有任何数据。这会导致聚合结果里出现大量全为 0 或 NaN 的空分组行。

在 pandas 1.x 之后,处理category分组键时建议显式指定observed:

  • observed=True:只输出实际有数据的组合,行数少,速度快;
  • observed=False:输出所有可能的组合,适合需要固定类别体系的结构化报表。
df.groupby('城市', observed=True)['销售额'].sum()

如果你发现分组结果突然多出来很多空行,优先检查是不是observed没有设置。同理,在多列分组时,observed=False会产生笛卡尔积级别的空组合,数据量大时甚至会把内存打爆,这是必须警惕的。

6.3 sort 参数:排序不是免费的

groupby默认会对分组键排序。也就是说,即使你的数据本来已经是按时间排列的,groupby之后也会把分组键做一次排序。如果数据量很大且你不需要有序结果,加上sort=False能省去一次全局排序的开销:

df.groupby('城市', sort=False)['销售额'].sum()

但注意:sort=False只影响分组键的输出顺序,不影响分组内部的聚合逻辑。如果你想按分组统计值排序(比如按总销售额降序排城市),那可以这样写:

df.groupby('城市')['销售额'].sum().sort_values(ascending=False)

6.4 惰性求值带来的排查问题

前文说过groupby是惰性的,这在调试时会带来一点麻烦:你无法直接打印分组的内部内容。排查思路是:

  1. 用.get_group(组名)查看某个分组的数据长什么样;
  2. 用.size()快速确定各分组行数是否正常;
  3. 用.groups检查分组键的分布;
  4. 用.obj属性查看原始对象。

如果在聚合后得到奇怪结果,不要急着怀疑聚合函数,先用.get_group()把某个分组的数据单独拎出来人工核对,通常几秒钟就能定位问题来源。

6.5 链式赋值的陷阱

在groupby结果上做赋值时,要注意是否命中副本:

df.groupby('城市')['销售额'].transform(lambda s: s + 1) # 正确 df.groupby('城市')['销售额'].apply(lambda s: s + 1) # 不推荐用于赋值场景

apply返回的结果有时会与原始索引不完全对齐,直接赋值给新列时可能出现 NaN 错位。相比之下,transform的索引对齐机制更可靠。我建议你养成一个习惯:给原表新增一列分组计算结果时,优先用transform,它就是为了这个场景设计的。

6.6 警惕 apply 返回 DataFrame 时的列名错乱

apply很灵活,但灵活带来的副作用是输出结构的“不确定性”。举个例子:

df.groupby('城市').apply(lambda g: g[['销售额', '成本']].mean())

这段代码的结果可能会得到一个 MultiIndex 的 DataFrame,而不是你想象中的普通宽表。因为apply会把分组键拼进索引,函数返回的 Series/DataFrame 再参与对齐,列索引和行索引容易混乱。

我有一个压箱底的技巧:如果apply返回的是 Series 或 DataFrame,建议在函数末尾加一个reset_index(drop=True),或者返回结构明确的 DataFrame,然后统一处理索引。不要指望 pandas 自动帮你对齐,它的对齐规则有时候比你预期的更“聪明”,而那恰恰是出错的地方。

6.7 新版 pandas 的 include_groups 行为变化

从 pandas 2.0 开始,apply在分组后传入函数的 DataFrame 默认不包含分组键列,但早期版本和部分内部行为仍有差异。如果你在升级 pandas 后发现原先正常的apply代码突然多出或缺少一列,多半是include_groups相关行为调整导致的。

稳妥的解法有两个:

  • 在apply(lambda g: ...)内,用.select_dtypes(exclude=[分组键列])主动过滤;
  • 或者直接用.drop(columns=分组键列)明确去掉不需要的列。

这个问题在社区里问得非常频繁,碰到别慌,优先检查版本更新日志。

6.8 分组后不要直接用循环遍历

当你对groupby对象做for name, group in df.groupby('城市')时,每迭代一次相当于做了一次分组操作。如果分组的数量很多、数据量很大,这种遍历效率低得惊人。我有一次需要遍历 2000 多个组做独立处理,换成按组抽样后用map批量操作后,速度提升了将近 50 倍。

通用原则是:优先把需求转化为向量化操作,实在不能向量化,再考虑对少数分组遍历。groupby的apply就是帮你把遍历隐藏在内部循环里的手段,但若你的函数逻辑异常复杂,apply的性能也未必比显式循环好多少。遇到这种极端情况,考虑先用group_keys=False关闭分组键合并,减少索引对齐的负担。

df.groupby('城市', group_keys=False).apply(complex_func)

7. 常见问题速查表

把日常工作中被问到最多的groupby问题整理成一张表,方便你定位:

症状原因解决方法
分组后结果多出很多空行分组键是category类型,observed未设置设置observed=True
分组键在结果里变成了索引而不是列as_index默认值为 True加as_index=False或reset_index()
聚合结果的列名是<lambda_0>agg接收了匿名函数且未命名改用NamedAgg或agg(新列名=(列名, 函数))
给原表新增分组统计列时出现 NaN索引未对齐或用了apply而非transform改用transform
apply返回的 DataFrame 列名错乱apply的返回结构与索引对齐规则过复杂函数内reset_index(drop=True),或走agg路线
分组操作跑得太慢字符串列未转category/ 分组键默认排序转类型 +sort=False
分组后行数看起来不对混淆了transform(保行数)和agg(折叠行)明确需求后选对应方法
新版 pandas 里 apply 结果变化include_groups行为调整在函数内显式drop(columns=分组键)

这八类问题覆盖了我见过的绝大部分groupby使用故障,按表格逐条排查基本能定位。

8. 最后分享一个压箱底的小技巧

关于groupby,还有一个我后来才完全搞清楚的细节:apply返回 Series 时,如果 Series 的索引和你预期不符,pandas 会把索引当作行索引拼进结果,导致最终结果“多出一层索引”。这个现象很多教程都不会讲,但它几乎每个用过apply的人都会遇到。

我现在的习惯是:能不用apply就不用apply。能用agg解决的,用agg;能直接对原列算好的,先算好再聚合;实在要自定义复杂逻辑,就让函数返回DataFrame或者带明确索引的Series,并在最终结果上强制reset_index()。

groupby本身不复杂,复杂的是在分组、聚合、转换、过滤这些操作之间选出最合适的一条路。把这篇文章里的方法逐个在自己的数据上试一遍,你会发现原本要写几十行的数据加工流程,最后压缩到十几行,还更不容易出错。这套经验是实打实从一线项目里踩出来的,收藏起来,碰到实际问题时回翻一下,比临时查文档高效得多。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/21 19:37:03

diagram-design实战:从系统架构图到规范体系的完整指南

从同事随手甩过来的“画个架构图呗”&#xff0c;到后来我们专门为一个内部平台整理成套的 diagram-design 规范&#xff0c;前后踩过的坑可能比画的图还多。很多人觉得 diagram-design 就是拉几个框、连几根线、选个顺眼的配色&#xff0c;可真把一张图放到评审会上&#xff0…

作者头像 李华
网站建设 2026/10/21 19:36:43

智慧康养技术落地的三大断层与验证方法论

简介&#xff1a;本资源为《2023–2030年智慧康养行业调研及发展前景趋势预测报告》专业研报&#xff0c;面向养老产业从业者、健康科技企业战略规划人员、政策研究者及高校相关专业师生&#xff0c;聚焦智慧康养这一融合物联网、大数据与养老服务的新兴交叉领域&#xff0c;系…

作者头像 李华
网站建设 2026/10/21 19:35:22

JVM堆栈分析实战:从jstack到MAT,搞定CPU飙高与内存泄漏

做Java开发这行&#xff0c;迟早会碰到“线上告警响了、服务CPU飚了、接口批量超时、要么干脆内存溢出重启”这种用户体感级的故障。这时候靠日志打点往往不够&#xff0c;更直接的办法是给JVM做一次“快照体检”——把堆和线程栈导出来&#xff0c;看对象到底被谁拽着不放&…

作者头像 李华
网站建设 2026/10/21 19:35:01

从灵感混乱到项目落地:自建ArtCraft创作工作流全解析

1. 为什么我会自己搭一套“ArtCraft”创作工作流 先交代一下背景。我平时会在绘画、拼贴、木工、数字插画几个方向之间来回切换&#xff0c;身边的朋友常以为我“兴趣广泛”&#xff0c;但他们不知道的是&#xff0c;我最大的瓶颈从来不是灵感&#xff0c;而是灵感之后的混乱&a…

作者头像 李华
网站建设 2026/10/21 19:34:22

傅里叶叠层成像MATLAB仿真:低倍物镜如何实现高分辨率重建

简介&#xff1a;这份MATLAB仿真资源面向图像处理、计算成像方向的学习者与研究人员&#xff0c;聚焦傅里叶叠层成像&#xff08;FPM&#xff09;的图像重建算法实现。FPM通过采集不同照明角度下的低分辨率图像&#xff0c;利用傅里叶域信息叠加与相位恢复迭代优化&#xff0c;…

作者头像 李华
网站建设 2026/10/21 19:32:24

R语言GARCH建模实战:波动率预测与VaR风险度量全流程

很多做数据分析的朋友第一次接触GARCH模型时&#xff0c;都会先被它那一堆数学符号劝退。但如果你真正面对过股票、汇率或商品价格的日收益率序列&#xff0c;就会发现一个特别明显的事实&#xff1a;价格不是一直在温和波动&#xff0c;而是隔一段时间就剧烈震荡一阵&#xff…

作者头像 李华