Pandas数据处理:合并、分组聚合与透视表实战指南

发布时间:2026/10/22 23:16:00
Pandas数据处理:合并、分组聚合与透视表实战指南 1. 数据合并把分散的数据表拼成一张完整蓝图刚开始用pandas做数据分析时我经常遇到一个很真实的困境数据从来不会整整齐齐地躺在一张表里等着你。业务系统的订单表是一张表用户信息在另一张表商品类目又单独放一张表。真正要开始分析的时候第一步永远是“把相关数据拼起来”。这就是DataFrame合并最核心的价值——它解决的是数据组织层面的问题而不是具体算法层面的问题。pandas里负责合并的API主要有三类很多初学者容易混用但其实它们的定位非常清晰concat负责“物理拼接”merge负责“按键匹配”join是merge在索引场景下的便捷写法。下面把这三类方法的适用场景、语法细节和隐性坑位逐一拆开。1.1 concat纵向追加和横向拼接concat是合并操作里最直观的一个它的核心逻辑是“沿着某个轴把多个DataFrame直接拼起来”。默认axis0表示纵向拼也就是把行增加axis1表示横向拼也就是把列增加。举个例子假设你有两个DataFrame分别记录了一周前三天和后四天的销售明细import pandas as pd df_first pd.DataFrame({ 日期: [2024-01-01, 2024-01-02, 2024-01-03], 销售额: [1200, 1500, 980] }) df_second pd.DataFrame({ 日期: [2024-01-04, 2024-01-05, 2024-01-06, 2024-01-07], 销售额: [2100, 1800, 1650, 2200] }) df_week pd.concat([df_first, df_second], axis0, ignore_indexTrue)这里有两个非常关键的参数ignore_indexTrue和keys。如果你不设置ignore_indexTrue拼接后的行索引会保留原来的索引值运气好是0到2和0到3那么你得到的DataFrame索引会变成0、1、2、0、1、2、3这种重复索引在处理时很容易搞出奇怪的结果。我几乎每次用concat都会显式写上ignore_indexTrue除非我真的有保留原始索引的业务需求。另一个容易踩坑的地方是列不完全一致的情况。比如前一个DataFrame有“日期”“销售额”两列后一个多了一列“退货数”默认concat会生成空值df_second_extra pd.DataFrame({ 日期: [2024-01-04, 2024-01-05], 销售额: [2100, 1800], 退货数: [10, 5] }) df_result pd.concat([df_first, df_second_extra], axis0, ignore_indexTrue)这个结果里“退货数”这一列在前三行会是NaN。如果你确定只是希望取交集列可以设置joininner。老实说比如要做环比分析两边列结构本来就不完全一样joininner反而是更安全的做法。1.2 merge按列合并与四种连接方式在实际业务分析中merge的使用频率远高于concat因为大多数关联需求都是“按某一列或者某几列把两张表对齐”而不是单纯地上下拼接。merge的底层逻辑非常接近SQL的JOIN对于熟悉数据库的人很容易理解。常用的参数是on、how和suffixes。on指定键列how指定连接方式。连接方式主要有四种连接方式行为说明适用场景inner只保留两边都匹配得上的行只看交集比如只统计有订单又有用户信息的记录left保留左边表全部行右边匹配不上填NaN主表在左副表补充信息是最常用的分析方式right保留右边表全部行左边匹配不上填NaN与left相反实际中用得相对少outer两边全部保留匹配不上各自填NaN需要看全量差异时使用用一份常见的订单表和用户表来演示orders pd.DataFrame({ 订单号: [A001, A002, A003, A004], 用户ID: [U01, U02, U01, U04], 金额: [99, 199, 59, 399] }) users pd.DataFrame({ 用户ID: [U01, U02, U03], 姓名: [小张, 小李, 小王], 城市: [北京, 上海, 广州] }) result orders.merge(users, on用户ID, howleft)这段代码执行完后订单数据一条都不会少而用户表里没有对应关系的“小王”不会出现在结果中虽然“小王”在用户表里存在但因为他没有下过单所以不会出现在left连接的结果里。反过来如果用inner就只保留U01和U02对应的订单记录也就是3条而不是4条。merge还有一个容易让新手困惑的问题当两张表都有“金额”这种同名字段时pandas会自动加后缀_x和_y。比如订单表里有“金额”支付表里也有“金额”合并后你会看到“金额_x”和“金额_y”。这在你同时要分析订单金额和支付金额时其实挺好用但如果你觉得后缀刺眼可以主动用suffixes(_订单, _支付)指定更清晰的后缀。1.3 join索引对齐的合并方式join是merge在索引对齐场景下的快捷方式。很多表在清洗后行索引本身就是业务主键比如用户ID、日期等。这时候用on参数反而要多敲几行代码直接用join按索引合并更省事。例如df_price pd.DataFrame({价格: [15, 20, 25]}, index[苹果, 香蕉, 橙子]) df_stock pd.DataFrame({库存: [100, 80, 60]}, index[苹果, 香蕉, 橙子]) df_combined df_price.join(df_stock, howinner)join本质上是merge(left, right, left_indexTrue, right_indexTrue, how...)的缩写。这里how的逻辑和merge一致。有一个细节需要注意如果右侧表的索引有重复值join会像我后面要讲到的merge一样产生笛卡尔积导致行数膨胀这时候要特别小心。2. 分组聚合从明细数据中提炼业务规律如果说合并解决的是“数据凑齐了”的问题那么分组聚合解决的就是“从凑齐的数据里看出门道”的问题。我见过太多新人写数据分析代码时陷入一种循环不断df[df[城市] 北京]然后分组求均值。这样不仅代码啰嗦而且写出来的分析思路是碎片的很难完整地回答业务问题。groupby的设计思想其实就是“拆分-应用-合并”。先把数据按某个维度拆成若干组然后对每一组应用某个函数或操作最后把各组结果合并起来。这个概念不复杂但真正熟练运用它需要理解几个关键点分组键的指定方式、聚合函数的选择、agg与apply的区别以及transform的逐行映射能力。2.1 groupby的基础用法与数据拆分逻辑groupby可以按单个字段分组也可以按多个字段分级分组。单字段是最常见的import pandas as pd import numpy as np sales pd.DataFrame({ 城市: [北京, 上海, 北京, 广州, 上海, 广州, 北京, 上海], 商品: [A, B, A, B, A, A, B, B], 销售额: [500, 600, 450, 700, 800, 650, 300, 900] }) city_group sales.groupby(城市) city_total city_group[销售额].sum()执行完这段代码city_total是城市 北京 1250 上海 2300 广州 1350 Name: 销售额, dtype: int64这是分组的天然结果本身是个Series。想要保持DataFrame结构可以用.reset_index()或者直接用as_indexFalse参数city_total_df sales.groupby(城市, as_indexFalse)[销售额].sum()再说多字段分组。多字段分组的意义在于观察组合维度下的数据表现比如想知道每个城市每种商品卖了多少city_item sales.groupby([城市, 商品], as_indexFalse)[销售额].sum()这个结果会按城市和商品两列的组合排列。需要注意的是groupby默认会在分组列上排序如果你希望保持数据原本出现的顺序可以加sortFalse。数据量大时sortFalse还能节省一点计算时间虽然提升不一定明显但思路是对的。2.2 agg一次性应用多个聚合函数我在实战中极少只对一个字段求和。很多时候业务方会问“这个城市总体销售额多少单均多少卖出多少单最大的一单是多少”这时候如果一个个算再合并到一起效率很低。agg就是专门解决这个问题的。stats sales.groupby(城市)[销售额].agg([sum, mean, count, max, min])执行后你会得到一张统计表每一列对应一个聚合函数。这里有个小技巧你能给结果列重命名让输出更好读stats sales.groupby(城市)[销售额].agg( 总销售额sum, 平均客单mean, 订单数count, 最大单max, 最小单min ).reset_index()需要注意agg里的函数名必须是能被pandas识别的聚合函数字符串形式支持sum、mean、count、max、min、median、std、size等也可以直接传入NumPy函数如np.sum。但自定义的lambda函数在agg里的效率不如在apply里高如果只是简单的逻辑建议直接用apply。2.3 apply与transform跨行取值和保留原形状的处理apply和transform是新手很容易混淆的两个方法但它们的语义差异非常明显。apply会对每个分组独立执行你传入的函数它的灵活性最高。比如你想算“每个城市销售额最高月份的销售额占该城市总销售额的比例”这种计算如果用agg是做不到的必须applydef top_sales_share(group): top group[销售额].max() total group[销售额].sum() return top / total shares sales.groupby(城市).apply(top_sales_share)这个例子里函数接收一个分组DataFrame作为输入返回一个标量。apply会把各组的结果组装成一个Series。transform的输出形状和输入一致它不会减少行数所以你可以把计算结果直接赋给原DataFrame的新列。这个特性在做“原数据加派生字段”的场景下极其实用。举个例子我想给每个订单增加一列“该城市平均销售额”city_avg sales.groupby(城市)[销售额].transform(mean) sales[城市平均销售额] city_avg执行后每一行都会保留对应的城市平均销售额而且行数、顺序都和原表一致。这一点是transform和agg最本质的区别。很多新人在需要“按分组计算新字段”时会先去算聚合结果再去merge其实直接用transform一步到位既省代码又不容易出错。2.4 分组聚合的常见操作禁忌不要忽略分组后索引的处理。如果后续还要和其他DataFrame合并最好用as_indexFalse或者reset_index()否则你会在索引对齐上花掉不少时间。不要用apply做简单的聚合。agg在性能上通常优于apply因为agg是向量化执行而apply在底层会有更多Python级循环。数据量大时这个差距会被放大。不要忘记groupby之后可能产生多级索引。多列分组后reset出来的索引是MultiIndex格式后续操作建议先reset成普通列否则很多场景操作起来非常别扭。3. 数据透视表用一张表回答多维度分析问题数据透视表是Excel用户最熟悉的数据分析工具之一而pandas的pivot_table把同样的能力搬到了Python里。它和Excel透视表的核心逻辑一致但表达能力更强而且可以自动化嵌入复杂的分析流程里。很多人觉得透视表和分组聚合很像确实pivot_table本质上是在做“分组聚合 行转列”的组合操作。但两者有一个明显区别groupby返回的长表结构适合继续做清洗和计算而pivot_table返回的宽表结构更适合直接阅读和汇报。业务人员看宽表时能更快地比较不同维度的数值差异。3.1 pivot_table的核心参数与行列取值pivot_table的核心参数是index行维度、columns列维度、values要聚合的值和aggfunc聚合方式。来一个标准的例子。还是用上面的销售数据想知道每个城市在不同商品上的销售额矩阵sales pd.DataFrame({ 城市: [北京, 上海, 北京, 广州, 上海, 广州, 北京, 上海], 商品: [A, B, A, B, A, A, B, B], 销售额: [500, 600, 450, 700, 800, 650, 300, 900] }) pivot_sales sales.pivot_table( index城市, columns商品, values销售额, aggfuncsum )执行结果商品 A B 城市 北京 950 300 上海 800 1500 广州 650 700这张表的逻辑非常清楚行是城市列是商品交叉点是销售额。一眼就能看出上海B商品的销量扛起了大盘而北京B商品相对偏弱。这就是透视表在汇报场景中比长表更直观的原因。如果需要同时看多个指标的汇总比如既看销售额合计又看订单数可以用列表传参pivot_multi sales.pivot_table( index城市, values销售额, aggfunc[sum, count, mean] )这样你会得到MultiIndex列名的输出列名会是(sum, 销售额)、(count, 销售额)这种形式。如果需要把这些结果和原有数据整合建议加上.reset_index()再把MultiIndex列名拍平。3.2 pivot与pivot_table的区别pandas里还有一个pivot方法很多新手分不清它和pivot_table的区别。简单说pivot要求原始数据在指定的行列组合上没有重复否则会报错“Index contains duplicate entries”而pivot_table会通过聚合函数自动处理重复数据。举个例子假如原始数据里“北京A商品”出现了两次sales_dup pd.DataFrame({ 城市: [北京, 北京], 商品: [A, A], 销售额: [500, 450] }) # 会直接报错 pivot_dup sales_dup.pivot(index城市, columns商品, values销售额) # 会正常输出并求和 pivot_table_dup sales_dup.pivot_table( index城市, columns商品, values销售额, aggfuncsum )所以除非你非常确定数据不会重复否则一律用pivot_table更安全。pivot的适用场景其实很窄更多时候它只是pivot_table在无重复数据下的简化版。3.3 用crosstab快速生成频次统计crosstab是pivot_table在频次统计场景下的特化版。它的输入是两个或多个序列输出的是一个交叉频次表非常适合做分类变量之间的关系探索。例子df_survey pd.DataFrame({ 性别: [男, 女, 男, 女, 女, 男, 男, 女], 是否复购: [是, 是, 否, 是, 否, 否, 是, 是] }) cross pd.crosstab(df_survey[性别], df_survey[是否复购])输出是否复购 否 是 性别 女 1 2 男 2 2这个表能直接告诉我们男女在复购行为上的基本分布而且实现代码极为简短。crosstab也支持normalizeindex来输出行比例的分布这在分析占比时特别方便cross_rate pd.crosstab(df_survey[性别], df_survey[是否复购], normalizeindex)这样一来每一行都代表该性别内部复购与不复购的比例能够更公平地比较男女之间因为样本量不同而产生的差异。3.4 数据透视表结果清洗与再操作透视表生成之后往往还需要做一步收尾工作。最常用的操作是把透视结果转回普通DataFramepivot_sales_reset pivot_sales.reset_index()如果你还需要把宽表转成长表用melt或者stack。我个人觉得凡是要继续做数据清洗和建模的透视结果都应该转回长表凡是要输出给业务看的才保留宽表形式。4. 常见问题与排查技巧实录在使用DataFrame合并、分组聚合和数据透视表的过程中有几个问题几乎是每个数据分析师都会遇到的。下面把它们整理成一份速查表并分享一些实战中的处理思路。4.1 合并后行数膨胀这是merge最容易踩的坑之一。当两个DataFrame的键列都有重复值时merge会产生笛卡尔积即两边匹配行数相乘的结果。举例来说左表有两条“U01”的订单记录右表里“U01”出现了两次比如用户有两条联系方式合并后就会产生4条记录。这在业务上可能是有意义的扩展也可能是一个数据质量问题。排查时我通常的做法是先在合并前检查键列的重复情况orders[用户ID].duplicated().sum() users[用户ID].duplicated().sum()如果左右两边都有重复就要先想清楚“重复的含义是什么”再决定是先对右表去重还是用drop_duplicates保留需要的那条记录。4.2 合并后数据的空值处理用left或outer连接时总会有一些行在右表中找不到匹配结果产生NaN。这些NaN不代表没有数据而是代表“没有匹配上”。分析之前一定要区分这两种情况。我的习惯是先把合并结果的空值情况统计出来再决定填充方式。如果空值占比过高首先要怀疑键列的数据类型不一致。一个非常典型的场景是订单表里的“用户ID”是字符串类型用户表里的“用户ID”是数值类型导致merge的时候一个也匹配不上。这时候用astype(str)或者astype(int)统一键列类型问题立刻解决。orders[用户ID] orders[用户ID].astype(str) users[用户ID] users[用户ID].astype(str) merged orders.merge(users, on用户ID, howleft)这个坑出现的频率相当高尤其是当数据来自不同系统时ID的存储格式不同几乎是常态。4.3 分组聚合后索引混乱groupby后如果没有及时重置索引后续操作很容易出现“明明看着是列却变成了索引”的情况。最常见的排查方法就是打印df.index和df.columns确认数据形态是否符合预期。我建议在完成分组聚合后直接养成一个习惯df.groupby(...).agg(...).reset_index()。保持数据以干净列形式呈现后续做各种操作都会顺畅很多。4.4 透视表的空值与填充策略pivot_table默认会对没有交叉数据的单元格填充NaN。很多业务报表中这些NaN其实更适合显示为0。你可以在生成透视表之后直接fillna(0)pivot_sales_filled pivot_sales.fillna(0)但要注意如果是比率类的聚合结果比如[aggfuncmean]计算平均单价空值填充为0可能会扭曲业务含义。这时候要结合场景判断不要机械地统一填充。4.5 性能优化实用笔记数据量上了千万级之后合并和分组的性能问题会变得非常明显。几个可以立刻见效的优化习惯在merge之前先对右表的键列做去重。如果确定右表键值是唯一的加上drop_duplicates(subset[键列])可以大大减少计算量。用categorical类型代替字符串类型。对低基数的分类字段比如城市、商品类别转成category类型groupby和pivot_table的运算速度会肉眼可见地提升。避免在循环里反复调用merge。如果多个表需要按顺序逐层合并尽量先把所有需要的数据提前准备好一遍合并减少中间步骤。大数据量情况下直接用polars或者duckdb处理会比pandas快一个量级以上。如果你的数据规模已经到了“跑一次要等半分钟”的程度那么换个工具引擎是更理性的选择没必要死磕pandas。5. 小结心得把合并、分组与透视组合成分析链条在我实际做了大量数据分析工作之后最大的体会是这三个功能很少单独使用它们通常串成一条完整的数据处理链路。合并负责把数据积聚起来分组聚合负责提炼维度上的统计指标透视表负责把这些指标以适合阅读的形式展示出来。链路一旦跑通你就能真正从原始数据中快速生成一张可以回答问题、驱动决策的报表而不是停留在“一个个算指标再手动拼接”的低效阶段。我建议每个学习数据分析的读者都亲手做一次完整的练习用一份销售明细数据先通过merge把商品信息补全再用groupby加agg计算出各城市和各商品的核心指标最后用pivot_table输出一份可以汇报给业务的宽表。这个过程会把你对pandas的理解从“会用某个函数”提升到“能设计数据加工流程”的层次而这种流程设计能力才是数据分析工作真正值钱的部分。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询