Pandas分组聚合:从groupby到agg的完整指南与实战技巧

发布时间:2026/7/31 6:00:42
Pandas分组聚合:从groupby到agg的完整指南与实战技巧 1. 从“看总数”到“看分组”为什么我们需要分组聚合做数据分析尤其是用Python的Pandas库你肯定遇到过这样的场景老板给你一张全国各门店的销售明细表让你“看看情况”。如果你只是简单地算个总销售额、平均客单价然后交差大概率会被打回来。因为老板真正想知道的可能是“华北区和华南区哪个卖得好”、“哪个产品品类在周末销量会暴增”、“新老客户的人均消费差距有多大”。这些问题背后都有一个共同的核心操作——分组聚合。简单来说分组聚合就是“分而治之”的数据思维。它先把数据按照某个或某几个维度比如地区、时间、产品类别拆分成不同的子集分组然后对每个子集独立进行统计计算聚合最后将结果组合起来形成一个新的、概括性的数据集。这就像学校老师不会只关心全班的平均分更要看男生和女生的平均分、每个科目的平均分一样。DataFrame.groupby()和agg()就是Pandas里实现这一思想的“黄金搭档”。我刚开始用Pandas时也习惯用循环去遍历每个分组效率低且代码冗长。直到彻底搞懂了groupby()和agg()才真正体会到向量化运算的威力。它们不仅能一行代码解决复杂的分组统计其灵活的语法更能应对各种刁钻的分析需求。接下来我就结合多年踩坑经验带你从原理到实战彻底掌握这两个核心方法。2. groupby() 的核心机制它到底创建了什么很多人对groupby()有误解以为它一执行就直接输出了一个汇总表。其实不然理解它返回的中间对象是灵活运用分组聚合的关键。2.1 GroupBy 对象一个“惰性”的拆分计划当你对一个DataFrame执行df.groupby(key)时Pandas并不会立即进行计算。它返回的是一个DataFrameGroupBy对象。你可以把它想象成一个“智能的拆分蓝图”。这个对象内部已经记录了如何根据‘key’列的唯一值将原始数据拆分成若干组但它按兵不动等待你发出具体的聚合指令比如求和、求平均。import pandas as pd # 示例数据销售记录 data { 销售员: [张三, 李四, 张三, 王五, 李四, 王五], 产品: [A, B, A, A, B, C], 销售额: [100, 150, 200, 120, 180, 90], 数量: [1, 2, 2, 1, 3, 1] } df pd.DataFrame(data) print(df) # 执行groupby 查看其类型 grouped df.groupby(销售员) print(type(grouped)) # 输出class pandas.core.groupby.generic.DataFrameGroupBy这个grouped对象本身没有直观的汇总数据。我们可以用.groups属性窥探一下它的拆分计划它会返回一个字典键是分组标签销售员名字值是对应原数据行的索引位置。print(grouped.groups) # 输出{张三: [0, 2], 李四: [1, 4], 王五: [3, 5]}注意GroupBy对象是惰性的这意味着在调用聚合函数如.sum().mean().agg()之前几乎没有计算开销。这对于探索性数据分析非常高效你可以先定义好分组逻辑再尝试多种聚合方式。2.2 分组键的多种玩法不止于列名分组的关键在于“按什么分”。groupby()的by参数极其灵活。按单列分组最常用的方式df.groupby(列名)。按多列分组传入一个列名的列表实现多级分组分层索引。例如想同时看每个销售员卖每种产品的情况df.groupby([销售员 ‘产品’])。结果会是一个具有多层索引MultiIndex的Series或DataFrame。按外部序列或数组分组只要长度与DataFrame行数相同任何序列、列表、数组都可以作为分组键。这让你可以按非数据框内的逻辑进行分组比如按自定义的时间段、按分箱后的连续变量等。按函数分组by参数可以接收一个函数该函数会应用于索引如果未设置索引则是行号根据函数返回值进行分组。例如有一个日期时间索引想按年份分组df.groupby(lambda x: x.year)。按字典或Series映射分组这适用于将现有值映射到新的分组标签。比如你想把几个产品归类到“大类”下。# 示例按多列分组 multi_grouped df.groupby([销售员 ‘产品’]) print(multi_grouped.sum()) # 输出 # 销售额 数量 # 销售员 产品 # 张三 A 300 3 # 李四 B 330 5 # 王五 A 120 1 # C 90 1 # 示例按字典映射分组 product_map {A: 电子类 ‘B’: ‘电子类’ ‘C’: ‘文具类’} df[产品大类] df[产品].map(product_map) print(df.groupby(产品大类).sum())2.3 迭代GroupBy对象亲手触摸每个分组为了深入理解我们可以像遍历字典一样遍历GroupBy对象。每次迭代会得到一个元组(分组标签 对应分组的子DataFrame)。这在调试或需要对每个分组进行复杂操作时非常有用。for name, group in df.groupby(销售员): print(f销售员 {name}) print(group) print(- * 20) # 输出 # 销售员 张三 # 销售员 产品 销售额 数量 # 0 张三 A 100 1 # 2 张三 A 200 2 # -------------------- # 销售员 李四 # ...3. agg() 方法聚合操作的指挥家如果说groupby()是负责拆分的“元帅”那么agg()aggregate的缩写就是负责调度各种统计函数的“指挥家”。它强大之处在于可以对不同的列应用不同的聚合函数并且支持多种函数形式。3.1 agg() 的基本语法与单函数应用最基本的用法是在GroupBy对象上调用.agg(func)。func可以是一个字符串如‘sum’‘mean’‘std’、一个Python函数如np.max或一个函数名如‘max’。# 对分组后所有数值列应用同一个聚合函数 print(grouped.sum()) # 等价于 grouped.agg(sum) print(grouped.agg(mean))3.2 多函数应用与自定义列聚合这是agg()的精华所在。通过传递一个字典、列表或函数列表实现精细化控制。字典映射最常用、最清晰字典的键是列名值是要应用的函数或函数列表。Pandas会按照字典的指示对指定的列应用指定的聚合。# 对‘销售额’列求和对‘数量’列求平均值和最大值 result grouped.agg({销售额: sum ‘数量’: [‘mean’ ‘max’]}) print(result) # 输出 # 销售额 数量 # sum mean max # 销售员 # 张三 300 1.5 2 # 李四 330 2.5 3 # 王五 210 1.0 1这个结果DataFrame的列变成了多层索引MultiIndex第一层是原始列名第二层是聚合函数名。你可以用.xs()或.loc来精确选取数据。函数列表对所有列应用同一组函数。grouped.agg([‘sum’ ‘mean’])会对每个数值列都计算总和和平均值。自定义函数当内置函数无法满足需求时可以传入自己定义的函数或lambda表达式。# 计算每个销售员的销售额范围最大值-最小值 def range_func(x): return x.max() - x.min() result_custom grouped.agg({ ‘销售额’: [‘sum’ range_func] ‘数量’: ‘mean’ }) print(result_custom)实操心得使用自定义函数时尤其是lambda要确保函数能正确接收一个Series即单个列的一个分组并返回一个标量值。对于复杂的多步计算定义具名函数通常比lambda更清晰、更易调试。3.3 重命名聚合结果列使用字典或列表进行多函数聚合时产生的多层索引列名有时并不直观。我们可以通过agg()中传递元组来重命名。# 在agg函数中直接重命名 result_named grouped.agg( 销售总额(销售额 ‘sum’) 平均数量(数量 ‘mean’) 最大单笔销售额(销售额 ‘max’) ) print(result_named) # 输出 # 销售总额 平均数量 最大单笔销售额 # 销售员 # 张三 300 1.5 200 # 李四 330 2.5 180 # 王五 210 1.0 120这种方式生成的列名是扁平的、可读性强的单层索引在后续数据处理中更方便。4. 高级分组聚合技巧与实战场景掌握了基础我们来看看如何用groupby()和agg()解决更实际、更复杂的问题。4.1 分组后过滤用filter()筛选分组有时我们不想聚合所有分组而是想根据分组本身的统计特征筛选出符合条件的分组。例如找出总销售额超过250的销售员的所有原始记录。# filter函数接收一个函数该函数以每个分组的子DataFrame为参数返回布尔值 high_sales_df df.groupby(销售员).filter(lambda x: x[销售额].sum() 250) print(high_sales_df) # 输出张三和李四的所有记录因为王五的总销售额210250filter()返回的是原始DataFrame中属于被保留分组的所有行而不是聚合结果。4.2 分组后转换用transform()保持原形状transform()是另一个神器。它也会对每个分组应用函数但最终返回一个与原始数据行数相同、索引对齐的Series或DataFrame。常用于“组内标准化”、“计算组内排名”等场景。# 计算每个销售员组内每笔销售额相对于该组平均销售额的占比 df[组内销售额占比] df.groupby(销售员)[销售额].transform( lambda x: x / x.mean() ) print(df)这样新列‘组内销售额占比’就加到了原数据中你可以清晰地看到张三的第一笔100元销售额在他个人平均销售额150元中占66.7%。4.3 分组后应用任意函数apply()的威力apply()是更通用的方法它可以将一个能处理子DataFrame的函数应用到每个分组并自动将结果拼接起来。它的灵活性最高但性能通常不如专门的agg或transform。# 一个稍复杂的例子获取每个销售员销售额最高的那条记录 def top_sale_record(group): return group.loc[group[‘销售额’].idxmax()] top_records df.groupby(‘销售员’ as_indexFalse).apply(top_sale_record) print(top_records)注意事项apply虽然强大但它是“最后的选择”。对于标准的聚合求和、平均和转换组内计算优先使用内置的聚合函数或transform它们的底层是Cython优化过的速度比apply快几个数量级。只有在逻辑非常自定义、无法用内置函数组合实现时才考虑apply。4.4 处理分组键与索引as_index参数在groupby()中as_index参数控制着分组键是否成为结果DataFrame的索引。默认as_indexTrue分组键会成为索引如上文所有示例。有时我们需要分组键作为普通列以便进行后续合并或其他操作。result_as_column df.groupby(‘销售员’ as_indexFalse).agg({‘销售额’: ‘sum’}) print(result_as_column) # 输出 # 销售员 销售额 # 0 张三 300 # 1 李四 330 # 2 王五 210 # 此时‘销售员’是列而不是索引5. 性能优化与常见陷阱在实际处理百万、千万级数据时分组聚合的性能至关重要。以下是一些关键点选择合适的分组键尽量使用整数或字符串等简单类型作为分组键避免使用复杂的对象。如果分组键是分类变量可以将其转换为category数据类型能极大提升groupby速度尤其是当唯一值数量远小于总行数时。df[‘销售员’] df[‘销售员’].astype(‘category’)聚合函数的选择内置的字符串函数如‘sum’‘mean’是性能最优的其次是NumPy函数如np.sum最慢的是Python自定义函数或lambda。在agg字典中优先使用字符串函数名。避免在分组内进行迭代永远不要尝试在groupby后写for循环去逐行处理每个分组内的数据。Pandas的向量化操作即一次对整个列或Series进行计算才是其高效的核心。如果你发现自己写了循环99%的情况有更好的向量化方法。注意缺失值处理默认情况下大多数聚合函数如summean会忽略NaN值。但有些函数如count不会。如果你希望NaN被包含在计算中例如mean计算时分母包含NaN行需要使用skipnaFalse参数但这通常不是你想要的行为需要格外小心。分组键中的NaN默认情况下groupby会自动忽略分组键为NaN的整行数据。如果你不希望这样需要设置dropnaFalse。df_with_na df.copy() df_with_na.loc[0 ‘销售员’] pd.NA print(df_with_na.groupby(‘销售员’ dropnaFalse).sum())6. 从SQL到Pandas思维转换如果你熟悉SQL理解Pandas的分组聚合会容易得多。它们本质上是相同的概念。GROUP BY子句 -df.groupby()SUM()AVG()COUNT()等聚合函数 -agg()中的函数字符串或函数HAVING子句对聚合结果过滤 - 在聚合后的结果上使用布尔索引。例如SQL中的HAVING SUM(销售额) 250对应 Pandas 的result[result[‘销售额_sum’] 250]。SELECT多列聚合 -agg()中的字典映射。掌握groupby()和agg()你就掌握了Pandas数据处理中最核心、最强大的武器之一。它让你能从不同维度、不同粒度洞察数据从粗糙的“总计”走向精细的“分组洞察”。刚开始可能会被多层索引、各种参数搞得头晕但多写、多练、多思考“我想按什么分分完后想算什么”很快就能得心应手。记住在遇到复杂分组逻辑时先别急着写循环停下来想想用groupbyagg/transform/filter/apply的组合能否更优雅、更高效地解决。