Pandas数据合并完全指南:merge、concat、join从入门到精通
目录
一、为什么需要数据合并?
二、merge:最强大的数据关联工具
2.1 merge的核心定位
2.2 merge的基本语法
2.3 四种连接类型详解
2.4 实战代码示例
2.5 merge的高级技巧
2.6 merge的最佳实践
三、concat:最简单直接的数据拼接
3.1 concat的核心定位
3.2 concat的基本语法
3.3 纵向拼接 vs 横向拼接
3.4 处理索引冲突
3.5 concat的高级技巧
四、join:基于索引的便捷合并
4.1 join的核心定位
4.2 join的基本语法
4.3 join的使用场景
4.4 merge vs join:到底该用哪个?
五、三剑客终极对比
5.1 核心差异一览表
5.2 选择策略速查表
六、实战案例:综合运用三种方法
七、常见坑点与避坑指南
一、为什么需要数据合并?
在实际的数据分析工作中,我们经常需要处理来自多个来源的数据集。比如:将订单数据与客户信息合并、将多个月的销售记录拼接成一个完整的数据集、将用户基本信息和行为日志整合在一起。
Pandas作为Python数据分析的基石,提供了三种核心的数据合并方法:
merge:基于列值的关联性合并,类似于SQL中的JOIN操作。
concat:沿特定轴进行结构性堆叠,适合拼接相似结构的数据。
join:基于索引的便捷合并,本质上是merge的一种特化形式。
理解这三者的区别和适用场景,是写出高效、清晰的数据处理代码的关键。
二、merge:最强大的数据关联工具
2.1 merge的核心定位
pd.merge()是Pandas中最强大、最常用的数据合并方法。它用于根据一个或多个键(key)将两个DataFrame的行连接起来,类似于SQL中的JOIN操作。
merge的核心思想是:找到两个表中共同的字段(键),然后将它们像拼接乐高积木一样组合在一起。当一个表有用户信息,另一个表有订单信息,你想知道每个订单对应的用户叫什么名字时,merge就是最佳选择。
2.2 merge的基本语法
python pd.merge( left, # 左侧DataFrame right, # 右侧DataFrame how='inner', # 连接方式: inner/outer/left/right on=None, # 连接键(列名) left_on=None, # 左侧连接键 right_on=None, # 右侧连接键 left_index=False, # 是否使用左侧索引作为连接键 right_index=False, # 是否使用右侧索引作为连接键 suffixes=('_x', '_y') # 重复列名的后缀 )2.3 四种连接类型详解
| 连接类型 | 作用 | 适用场景 |
| inner(内连接) | 只保留两个表中键匹配的行 | 需要精确匹配的数据,如订单与发货记录 |
| left(左连接) | 保留左表所有行,右表无匹配则填充NaN | 需要保留主表完整信息,如所有客户及其订单 |
| right(右连接) | 保留右表所有行,左表无匹配则填充NaN | 优先保留右侧数据完整性 |
| outer(外连接) | 保留两个表的所有行,无匹配填充NaN | 数据一致性检测,查找不匹配记录 |
2.4 实战代码示例
inner join(内连接)
python import pandas as pd df1 = pd.DataFrame({ 'user_id': [1, 2, 3], 'name': ['张三', '李四', '王五'] }) df2 = pd.DataFrame({ 'user_id': [1, 2, 4], 'age': [25, 30, 28] }) result = pd.merge(df1, df2, on='user_id', how='inner') print(result) # user_id name age # 0 1 张三 25 # 1 2 李四 30 # 只保留了user_id为1和2的行,因为user_id=3和4不匹配left join(左连接)
python result_left = pd.merge(df1, df2, on='user_id', how='left') print(result_left) # user_id name age # 0 1 张三 25.0 # 1 2 李四 30.0 # 2 3 王五 NaN # 保留了左表的所有行,王五没有年龄信息,填充为NaNouter join(外连接)
python result_outer = pd.merge(df1, df2, on='user_id', how='outer', indicator=True) print(result_outer) # user_id name age _merge # 0 1 张三 25.0 both # 1 2 李四 30.0 both # 2 3 王五 NaN left_only # 3 4 NaN 28.0 right_only # indicator参数可以标识每条记录的来源2.5 merge的高级技巧
多键合并:当单个字段不足以唯一标识一条记录时,可以使用多个列作为连接键。
python df1 = pd.DataFrame({ 'year': [2023, 2023, 2024], 'month': [1, 2, 1], 'sales': [100, 150, 200] }) df2 = pd.DataFrame({ 'year': [2023, 2023, 2024], 'month': [1, 2, 2], 'target': [120, 140, 180] }) result = pd.merge(df1, df2, on=['year', 'month'])处理列名冲突:当两个DataFrame有相同的非键列名时,使用suffixes参数添加后缀区分。
python df1 = pd.DataFrame({'id': [1, 2], 'value': ['A', 'B']}) df2 = pd.DataFrame({'id': [1, 3], 'value': ['X', 'Y']}) result = pd.merge(df1, df2, on='id', suffixes=('_left', '_right')) print(result) # id value_left value_right # 0 1 A X # 1 2 B NaN2.6 merge的最佳实践
在使用merge时,有几个注意事项能帮你避免踩坑:
1. 验证键的唯一性:如果连接键存在重复值,可能导致笛卡尔积,行数意外增加。合并前先执行df['key'].is_unique检查。
2. 大数据集性能优化:当处理百万级以上的数据时,可以考虑先将连接键设为索引,再使用join方法,后者在某些场景下效率更高。
3. 善用indicator参数:设置为True会添加一个特殊列,标明每行数据的来源(both/left_only/right_only),这对于数据质量审计非常有用。
三、concat:最简单直接的数据拼接
3.1 concat的核心定位
pd.concat()是Pandas中最简单直接的数据合并方式。它用于沿着某个轴(行或列)将多个DataFrame或Series堆叠在一起,类似于在物理上把多个表格上下摞起来或左右并排放置。
concat不关心列值之间的匹配关系,它只是纯粹的结构性拼接。当你有多个月份的销售数据,它们的列结构完全一致,想把它们拼成一个完整的数据集时,concat就是最合适的选择。
3.2 concat的基本语法
python pd.concat( objs, # 要拼接的DataFrame列表 axis=0, # 0表示纵向拼接(行),1表示横向拼接(列) join='outer', # 处理索引对齐的方式:outer保留所有,inner保留交集 ignore_index=False, # 是否重置索引 keys=None, # 添加多级索引,用于标识数据来源 sort=False # 是否对非连接轴进行排序 )3.3 纵向拼接 vs 横向拼接
纵向拼接(axis=0,默认):相当于把两个表格上下叠在一起,要求列名对齐。这是最常见的用法。
python # 多个月份的销售数据 df_jan = pd.DataFrame({'product': ['A', 'B'], 'sales': [100, 150]}) df_feb = pd.DataFrame({'product': ['A', 'B'], 'sales': [120, 140]}) df_mar = pd.DataFrame({'product': ['A', 'C'], 'sales': [110, 130]}) # 纵向拼接 result = pd.concat([df_jan, df_feb, df_mar], ignore_index=True) print(result) # product sales # 0 A 100 # 1 B 150 # 2 A 120 # 3 B 140 # 4 A 110 # 5 C 130横向拼接(axis=1):相当于把两个表格左右并排放置,要求索引对齐。
python df_left = pd.DataFrame({'A': [1, 2, 3]}, index=[0, 1, 2]) df_right = pd.DataFrame({'B': [4, 5, 6]}, index=[0, 1, 2]) result = pd.concat([df_left, df_right], axis=1) print(result) # A B # 0 1 4 # 1 2 5 # 2 3 63.4 处理索引冲突
纵向拼接时,原始索引会被保留,可能导致重复的索引值。设置ignore_index=True可以重新生成0,1,2...的连续索引。
python # 不重置索引:索引会重复 result = pd.concat([df1, df2]) # 索引可能是 [0,1,0,1] 这样 # 重置索引:推荐做法 result = pd.concat([df1, df2], ignore_index=True) # 索引变成 [0,1,2,3]3.5 concat的高级技巧
使用keys参数标识数据来源:当拼接多个数据源后想追踪每行来自哪个原始表,可以使用keys添加多级索引。
python result = pd.concat([df_jan, df_feb], keys=['Jan', 'Feb']) # 生成的多级索引可以清晰地标识数据来源 print(result.loc['Jan']) # 只查看1月份的数据处理列名不一致的情况:使用join='inner'可以只保留所有表共有的列。
python df1 = pd.DataFrame({'A': [1, 2], 'B': [3, 4]}) df2 = pd.DataFrame({'A': [5, 6], 'C': [7, 8]}) # join='outer'(默认):保留所有列,缺失填充NaN result_outer = pd.concat([df1, df2], ignore_index=True) # A B C # 0 1 3.0 NaN # 1 2 4.0 NaN # 2 5 NaN 7.0 # 3 6 NaN 8.0 # join='inner':只保留共有的列(A列) result_inner = pd.concat([df1, df2], ignore_index=True, join='inner') # A # 0 1 # 1 2 # 2 5 # 3 6
四、join:基于索引的便捷合并
4.1 join的核心定位
DataFrame.join()是merge的一种特化形式,专门用于基于索引的数据合并。它的设计目标是提供一个更简洁、更高效的API来处理索引对齐的场景。
当你的数据是时间序列(以日期为索引),或者两个DataFrame共享同一套索引时,join会比merge更简洁,在某些大数据场景下性能也更好。
4.2 join的基本语法
python df_left.join( df_right, # 右侧DataFrame how='left', # 连接方式,默认left on=None, # 左侧用于对齐的列(而非索引) lsuffix='', # 左侧重复列名的后缀 rsuffix='', # 右侧重复列名的后缀 sort=False # 是否排序 )4.3 join的使用场景
场景一:基于索引合并
python df1 = pd.DataFrame({'name': ['张三', '李四', '王五']}, index=[1, 2, 3]) df2 = pd.DataFrame({'age': [25, 30, 28]}, index=[1, 2, 4]) # 基于索引进行左连接 result = df1.join(df2, how='left') print(result) # name age # 1 张三 25.0 # 2 李四 30.0 # 3 王五 NaN场景二:用on参数基于列连接
join也可以基于列进行连接,但此时该列会被用作索引:
python df1 = pd.DataFrame({'user_id': [1, 2, 3], 'name': ['张三', '李四', '王五']}) df2 = pd.DataFrame({'user_id': [1, 2, 4], 'age': [25, 30, 28]}) # 先将df2的user_id设为索引 df2_indexed = df2.set_index('user_id') result = df1.join(df2_indexed, on='user_id', how='left')场景三:同时合并多个DataFrame
join支持一次性合并多个DataFrame,这是它的一大优势:
python df_main = pd.DataFrame({'id': [1, 2, 3]}, index=[1, 2, 3]) df_info1 = pd.DataFrame({'age': [25, 30, 28]}, index=[1, 2, 3]) df_info2 = pd.DataFrame({'city': ['北京', '上海', '广州']}, index=[1, 2, 3]) df_info3 = pd.DataFrame({'score': [85, 92, 78]}, index=[1, 2, 3]) # 一次性合并多个 result = df_main.join([df_info1, df_info2, df_info3])4.4 merge vs join:到底该用哪个?
| 维度 | merge | join |
| 默认对齐方式 | 基于列值 | 基于索引 |
| 支持多个DataFrame | 一次只能两个 | 可以同时多个 |
| 语法复杂度 | 参数较多,功能强大 | 简洁直观 |
| 大数据性能 | 中等 | 略优(索引对齐更快) |
| 适用场景 | 通用场景,基于列关联 | 索引对齐场景 |
选择指南:如果你的数据已经在索引上对齐,或者你想基于索引合并,优先选择join;如果你需要基于列进行复杂的关联操作,或者需要outer/right等连接方式,使用merge。
五、三剑客终极对比
5.1 核心差异一览表
| 对比维度 | merge | concat | join |
| 合并依据 | 基于列值(键) | 基于位置/轴 | 基于索引 |
| 连接类型 | inner/left/right/outer | 仅沿轴堆叠 | left/inner(默认left) |
| 同时合并数 | 2个DataFrame | N个DataFrame | N个DataFrame |
| 是否支持SQL风格JOIN | 完美支持 | 不支持 | 部分支持 |
| 处理重复列名 | 使用suffixes参数 | 自动处理 | 使用lsuffix/rsuffix |
| 典型场景 | 表关联查询 | 数据堆叠拼接 | 索引对齐合并 |
| 底层逻辑 | 哈希/排序归并 | 内存拼接 | 索引对齐 |
5.2 选择策略速查表
| 你的需求 | 推荐方法 | 说明 |
| 根据共同字段关联数据 | merge | 如订单表与用户表通过user_id关联 |
| 上下追加相似结构数据 | concat(axis=0) | 如多个月份的销售记录合并 |
| 左右拼接宽表 | concat(axis=1) | 需确保索引对齐 |
| 基于索引快速合并 | join | 如时间序列数据合并 |
| 需要识别数据来源 | merge(indicator=True) 或 concat(keys=) | |
| 一次性合并多个DataFrame | concat 或 join | merge一次只能两个 |
| 大数据集合并(百万级以上) | join(基于索引时效率更优) | 实测性能差异可达5倍 |
六、实战案例:综合运用三种方法
案例一:电商数据分析
假设我们有三张表:
用户表(users):user_id, name, register_date
订单表(orders):order_id, user_id, order_amount, order_date
产品表(products):product_id, product_name, category
python import pandas as pd # 构造示例数据 users = pd.DataFrame({ 'user_id': [1, 2, 3, 4], 'name': ['张三', '李四', '王五', '赵六'], 'register_date': pd.date_range('2024-01-01', periods=4) }) orders = pd.DataFrame({ 'order_id': [101, 102, 103, 104], 'user_id': [1, 1, 2, 5], # user_id=5的用户不存在 'order_amount': [299, 450, 128, 899], 'order_date': pd.date_range('2024-06-01', periods=4) }) # 1. 使用merge:将订单与用户信息关联 orders_with_user = pd.merge(orders, users, on='user_id', how='left') print("订单关联用户信息:") print(orders_with_user[['order_id', 'user_id', 'name', 'order_amount']]) # 2. 使用merge(indicator):找出没有对应用户的订单(数据质量检查) merged_with_indicator = pd.merge(orders, users, on='user_id', how='outer', indicator=True) bad_orders = merged_with_indicator[merged_with_indicator['_merge'] == 'right_only'] print("\n用户表中没有对应记录的订单:") print(bad_orders) # 3. 使用concat:合并多个月份的销售数据 jan_sales = pd.DataFrame({'product': ['A', 'B'], 'sales': [1000, 800]}) feb_sales = pd.DataFrame({'product': ['A', 'B', 'C'], 'sales': [1200, 900, 400]}) mar_sales = pd.DataFrame({'product': ['A', 'B'], 'sales': [1100, 850]}) all_sales = pd.concat([jan_sales, feb_sales, mar_sales], keys=['Jan', 'Feb', 'Mar'], ignore_index=False) print("\n全年销售汇总:") print(all_sales)案例二:多数据源整合
python # 场景:整合来自不同部门的报表 # 销售部门:销售额报表(按月份索引) sales_report = pd.DataFrame({ 'sales_amount': [10000, 15000, 12000, 18000] }, index=['Jan', 'Feb', 'Mar', 'Apr']) # 市场部门:广告费用报表(按月份索引) marketing_report = pd.DataFrame({ 'ad_cost': [2000, 2500, 1800, 3000] }, index=['Jan', 'Feb', 'Mar', 'Apr']) # 财务部门:利润报表(索引可能不完全一致) finance_report = pd.DataFrame({ 'profit': [3000, 4500, 3600, 5400] }, index=['Jan', 'Feb', 'Mar', 'May']) # 使用join一次性合并所有报表(基于索引) full_report = sales_report.join([marketing_report, finance_report], how='outer') print("整合后的完整报表:") print(full_report) # 计算利润率 full_report['profit_margin'] = full_report['profit'] / full_report['sales_amount'] * 100 print("\n各月利润率:") print(full_report['profit_margin'])
七、常见坑点与避坑指南
坑点1:merge时键重复导致笛卡尔积
问题:当连接键在某一表中存在重复值时,merge会生成笛卡尔积,导致行数暴增。
解决方案:合并前检查键的唯一性,或使用validate参数进行验证。
python # 检查键是否唯一 print(df['key'].is_unique) # 应返回True # 使用validate参数限制合并类型 pd.merge(df1, df2, on='key', validate='one_to_one') # 确保一对一 pd.merge(df1, df2, on='key', validate='one_to_many') # 允许一对多坑点2:concat时索引重复
问题:纵向拼接后索引重复,可能导致后续操作出现意外结果。
解决方案:始终设置ignore_index=True,或在拼接前重置索引。
python # 推荐做法 result = pd.concat([df1, df2, df3], ignore_index=True)坑点3:列名冲突导致数据被覆盖
问题:merge或join时,如果左右表有相同的列名,后合并的列会覆盖先前的列。
解决方案:使用suffixes(merge)或lsuffix/rsuffix(join)参数。
python # merge中使用suffixes result = pd.merge(df1, df2, on='key', suffixes=('_left', '_right')) # join中使用lsuffix/rsuffix result = df1.join(df2, lsuffix='_left', rsuffix='_right')坑点4:concat时列不一致导致大量NaN
问题:纵向拼接时,如果各表的列名不完全一致,默认会用NaN填充缺失的列。
解决方案:根据业务需求选择使用join='inner'只保留共有列,或先统一列结构。
python # 只保留所有表共有的列 result = pd.concat([df1, df2], join='inner')坑点5:忽略数据类型不一致导致的合并失败
问题:连接键的数据类型不一致(如一列是int,另一列是float或字符串),会导致合并结果不符合预期。
解决方案:合并前确保连接键的数据类型一致。
python # 统一数据类型 df1['key'] = df1['key'].astype(str) df2['key'] = df2['key'].astype(str) result = pd.merge(df1, df2, on='key')希望这篇文章能帮助到你,如果觉得有用,欢迎点赞、收藏、关注!
