目录
[2.1 merge的核心定位](#2.1 merge的核心定位)
[2.2 merge的基本语法](#2.2 merge的基本语法)
[2.3 四种连接类型详解](#2.3 四种连接类型详解)
[2.4 实战代码示例](#2.4 实战代码示例)
[2.5 merge的高级技巧](#2.5 merge的高级技巧)
[2.6 merge的最佳实践](#2.6 merge的最佳实践)
[3.1 concat的核心定位](#3.1 concat的核心定位)
[3.2 concat的基本语法](#3.2 concat的基本语法)
[3.3 纵向拼接 vs 横向拼接](#3.3 纵向拼接 vs 横向拼接)
[3.4 处理索引冲突](#3.4 处理索引冲突)
[3.5 concat的高级技巧](#3.5 concat的高级技巧)
[4.1 join的核心定位](#4.1 join的核心定位)
[4.2 join的基本语法](#4.2 join的基本语法)
[4.3 join的使用场景](#4.3 join的使用场景)
[4.4 merge vs join:到底该用哪个?](#4.4 merge vs join:到底该用哪个?)
[5.1 核心差异一览表](#5.1 核心差异一览表)
[5.2 选择策略速查表](#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
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
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
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
# 保留了左表的所有行,王五没有年龄信息,填充为NaN
outer join(外连接)
python
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
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
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 NaN
2.6 merge的最佳实践
在使用merge时,有几个注意事项能帮你避免踩坑:
-
验证键的唯一性:如果连接键存在重复值,可能导致笛卡尔积,行数意外增加。合并前先执行df['key'].is_unique检查。
-
大数据集性能优化:当处理百万级以上的数据时,可以考虑先将连接键设为索引,再使用join方法,后者在某些场景下效率更高。
-
善用indicator参数:设置为True会添加一个特殊列,标明每行数据的来源(both/left_only/right_only),这对于数据质量审计非常有用。
三、concat:最简单直接的数据拼接
3.1 concat的核心定位
pd.concat()是Pandas中最简单直接的数据合并方式。它用于沿着某个轴(行或列)将多个DataFrame或Series堆叠在一起,类似于在物理上把多个表格上下摞起来或左右并排放置。
concat不关心列值之间的匹配关系,它只是纯粹的结构性拼接。当你有多个月份的销售数据,它们的列结构完全一致,想把它们拼成一个完整的数据集时,concat就是最合适的选择。
3.2 concat的基本语法
python
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
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
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 6
3.4 处理索引冲突
纵向拼接时,原始索引会被保留,可能导致重复的索引值。设置ignore_index=True可以重新生成0,1,2...的连续索引。
python
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
python
result = pd.concat([df_jan, df_feb], keys=['Jan', 'Feb'])
# 生成的多级索引可以清晰地标识数据来源
print(result.loc['Jan']) # 只查看1月份的数据
处理列名不一致的情况:使用join='inner'可以只保留所有表共有的列。
python
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
python
df_left.join(
df_right, # 右侧DataFrame
how='left', # 连接方式,默认left
on=None, # 左侧用于对齐的列(而非索引)
lsuffix='', # 左侧重复列名的后缀
rsuffix='', # 右侧重复列名的后缀
sort=False # 是否排序
)
4.3 join的使用场景
场景一:基于索引合并
python
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
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
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
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
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
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
python
# 推荐做法
result = pd.concat([df1, df2, df3], ignore_index=True)
坑点3:列名冲突导致数据被覆盖
问题:merge或join时,如果左右表有相同的列名,后合并的列会覆盖先前的列。
解决方案:使用suffixes(merge)或lsuffix/rsuffix(join)参数。
python
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
python
# 只保留所有表共有的列
result = pd.concat([df1, df2], join='inner')
坑点5:忽略数据类型不一致导致的合并失败
问题:连接键的数据类型不一致(如一列是int,另一列是float或字符串),会导致合并结果不符合预期。
解决方案:合并前确保连接键的数据类型一致。
python
python
# 统一数据类型
df1['key'] = df1['key'].astype(str)
df2['key'] = df2['key'].astype(str)
result = pd.merge(df1, df2, on='key')
希望这篇文章能帮助到你,如果觉得有用,欢迎点赞、收藏、关注!