Pandas数据合并完全指南:merge、concat、join从入门到精通

目录

一、为什么需要数据合并?

二、merge:最强大的数据关联工具

[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的最佳实践)

三、concat:最简单直接的数据拼接

[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的高级技巧)

四、join:基于索引的便捷合并

[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时,有几个注意事项能帮你避免踩坑:

  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 复制代码
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')

希望这篇文章能帮助到你,如果觉得有用,欢迎点赞、收藏、关注!

相关推荐
Shorasul2 小时前
c++ 跨平台线程封装 c++如何封装pthread和std--thread
jvm·数据库·python
2401_832635582 小时前
CSS如何利用Sass简化CSS书写_通过嵌套与简写优化编码效率
jvm·数据库·python
2402_854808372 小时前
如何处理MongoDB跨分片事务报错_4.2+分布式事务的限制与两阶段提交延迟
jvm·数据库·python
vegetablec2 小时前
如何进行SQL数学计算_运用ROUND与CEIL处理数值精度
jvm·数据库·python
疯狂打码的少年2 小时前
【Day13 Java转Python】装饰器、生成器与lambda——Python的函数式“三件套”
java·开发语言·python
石榴树下的七彩鱼2 小时前
Python OCR 文字识别 API 接入完整教程
开发语言·人工智能·后端·python·ocr·api·图片识别
信看2 小时前
看所有网卡参数,确认 RM520N-GL 网卡
开发语言·python
qq_189807032 小时前
c++怎么解决ifstream在读取UTF-16文件时的乱码_imbue用法【避坑】
jvm·数据库·python
不过如此19512 小时前
pyinstaller打包GUI项目实践
windows·python·ui