Python中如何合并多个DataFrame?concat与merge对比指南

絕刀狂花
发布: 2025-07-05 14:01:21
原创
985人浏览过

python中合并多个dataframe的核心方法有两种:一是使用pd.concat进行堆叠式合并,二是使用pd.merge进行关联式合并。pd.concat主要用于沿行或列方向堆叠数据,适用于结构相似的数据整合,关键参数包括objs(待合并对象)、axis(合并方向)、join(索引/列对齐方式)及ignore_index(是否重置索引)。pd.merge则基于共同键进行数据关联,支持内连接、左连接、右连接和外连接,核心参数有left/right(待合并的两个dataframe)、how(连接类型)、on/left_on/right_on(连接键)及suffixes(列名冲突处理)。选择concat时应确保数据结构一致或索引对齐,而merge适用于存在逻辑关联的数据整合,需注意键列清洗与连接类型选择。两者在性能上各有侧重,concat适合垂直或水平拼接,merge适合基于键的关系型合并,实际应用中应根据数据特征和需求合理选用。

Python中如何合并多个DataFrame?concat与merge对比指南

Python中合并多个DataFrame,本质上无非两种核心操作:一种是像堆积木一样,把数据框一个接一个地堆叠起来,这通常是 pd.concat 的职责;另一种则是像给不同表做“联姻”,根据某些共同的“媒人”(键)把它们关联起来,这正是 pd.merge 的强项。理解它们各自的适用场景和内在逻辑,是高效处理数据的基础。

Python中如何合并多个DataFrame?concat与merge对比指南

解决方案

在Python的pandas库中,合并多个DataFrame主要依赖于pd.concat()和pd.merge()这两个函数。它们解决的是不同类型的数据整合问题。

Python中如何合并多个DataFrame?concat与merge对比指南

pd.concat() 这个函数主要用于沿着某个轴(行或列)堆叠或连接DataFrame。你可以把它想象成把几个表格直接首尾相连,或者并排摆放。

  • 核心功能: 堆叠(append)或并排连接(concatenate)。
  • 主要参数:
    • objs: 一个DataFrame对象的序列或字典,这是要合并的数据框列表。
    • axis: 决定合并方向。axis=0(默认)表示按行堆叠,即增加行;axis=1表示按列并排,即增加列。
    • join: 当按列合并时,如何处理不完全匹配的索引。'outer'(默认)会保留所有索引,缺失值用NaN填充;'inner'只保留共同的索引。当按行合并时,如何处理列。'outer'会保留所有列,缺失值用NaN填充;'inner'只保留共同的列。
    • ignore_index: 如果设为True,合并后的DataFrame会重置索引,这在堆叠操作中非常有用,可以避免重复索引。

示例:

立即学习Python免费学习笔记(深入)”;

Python中如何合并多个DataFrame?concat与merge对比指南
import pandas as pd

df1 = pd.DataFrame({'A': ['A0', 'A1'], 'B': ['B0', 'B1']}, index=[0, 1])
df2 = pd.DataFrame({'A': ['A2', 'A3'], 'B': ['B2', 'B3']}, index=[2, 3])
df3 = pd.DataFrame({'C': ['C4', 'C5'], 'D': ['D4', 'D5']}, index=[4, 5])

# 按行堆叠 (默认 axis=0)
result_rows = pd.concat([df1, df2])
# print(result_rows)

# 按列并排 (axis=1)
# 注意:这里会根据索引进行对齐,如果索引不同,会填充NaN
result_cols = pd.concat([df1, df3], axis=1)
# print(result_cols)

# 按行堆叠并重置索引
result_reset_index = pd.concat([df1, df2], ignore_index=True)
# print(result_reset_index)
登录后复制

pd.merge() 这个函数用于通过一个或多个键(列)将两个DataFrame连接起来,类似于SQL中的JOIN操作。它基于列的值进行匹配,而不是简单地堆叠。

  • 核心功能: 根据共同列(或索引)进行数据关联。
  • 主要参数:
    • left, right: 要合并的两个DataFrame。
    • how: 指定合并类型。
      • 'inner'(默认):只保留左右DataFrame中键都存在的行。
      • 'left':保留左DataFrame的所有行,右DataFrame中匹配的行,不匹配的用NaN填充。
      • 'right':保留右DataFrame的所有行,左DataFrame中匹配的行,不匹配的用NaN填充。
      • 'outer':保留左右DataFrame中的所有行,不匹配的用NaN填充。
    • on: 用于连接的列名(如果左右DataFrame的键列名相同)。
    • left_on, right_on: 如果左右DataFrame的键列名不同,分别指定。
    • suffixes: 当左右DataFrame有相同列名(非键列)时,用于区分这些列的后缀。

示例:

立即学习Python免费学习笔记(深入)”;

df_employees = pd.DataFrame({
    'employee_id': [1, 2, 3, 4],
    'name': ['Alice', 'Bob', 'Charlie', 'David'],
    'department_id': [101, 102, 101, 103]
})

df_departments = pd.DataFrame({
    'department_id': [101, 102, 104],
    'department_name': ['HR', 'IT', 'Finance']
})

# 内连接:只保留员工和部门ID都匹配的记录
merged_inner = pd.merge(df_employees, df_departments, on='department_id', how='inner')
# print(merged_inner)

# 左连接:保留所有员工信息,匹配部门信息
merged_left = pd.merge(df_employees, df_departments, on='department_id', how='left')
# print(merged_left)
登录后复制

concat 适用场景与常见误区

我个人觉得,pd.concat 最能体现其价值的地方,就是当你手里有一堆结构相同或者非常相似的数据碎片时。比如,你从数据库里分批导出了按月份划分的销售数据,或者从不同渠道抓取了格式一致的用户行为日志,这时候,把它们堆叠起来形成一个完整的、更大的数据集,concat 简直是神来之笔。它尤其适合处理时间序列数据,或者当你需要把多个文件(例如CSV)的内容合并成一个DataFrame时。

然而,concat 也不是没有坑。最常见的误区,我觉得就是索引的处理。如果你只是简单地 pd.concat([df1, df2]),而这两个DataFrame又恰好有相同的索引值(比如都是从0开始),那么合并后的结果就会出现重复索引。这在后续的数据处理中,比如使用 loc 进行选择时,可能会导致意想不到的结果。所以,我几乎成了 ignore_index=True 的忠实拥趸,尤其是在做行堆叠时,它能帮你自动生成一个干净、连续的新索引。

# 常见误区:索引重复
df_a = pd.DataFrame({'value': [10, 20]}, index=[0, 1])
df_b = pd.DataFrame({'value': [30, 40]}, index=[0, 1]) # 同样有0, 1索引

# 结果索引会重复
bad_concat = pd.concat([df_a, df_b])
# print(bad_concat)
# print(bad_concat.loc[0]) # 这会返回两行!

# 解决方案:重置索引
good_concat = pd.concat([df_a, df_b], ignore_index=True)
# print(good_concat)
# print(good_concat.loc[0]) # 只返回一行
登录后复制

另一个小陷阱是列的不匹配。当你 axis=0 进行行堆叠时,如果各个DataFrame的列名不完全一致,concat 默认会采取 join='outer' 策略,也就是保留所有列,不匹配的地方用 NaN 填充。这通常是期望的行为,但如果你只想要那些所有DataFrame都共有的列,那就得明确指定 join='inner'。我有时候会忘记这一点,结果发现合并出来的数据框多了很多 NaN 列,回头检查才发现是列名没对齐。

df_sales = pd.DataFrame({'product': ['A', 'B'], 'price': [100, 200]})
df_returns = pd.DataFrame({'product': ['A', 'C'], 'quantity': [1, 2]})

# 默认 outer join,会保留所有列
outer_concat = pd.concat([df_sales, df_returns], axis=0)
# print(outer_concat)

# inner join,只保留共同列(product)
inner_concat = pd.concat([df_sales, df_returns], axis=0, join='inner')
# print(inner_concat)
登录后复制

merge 的连接类型与复杂性处理

说到合并,其实除了最直接的拼接,我们更多时候面对的是数据间的“联姻”,这也就是 merge 的用武之地了。pd.merge 最强大的地方在于它能够模拟SQL的各种连接操作,通过 how 参数来精确控制连接的逻辑。

how 参数的奥秘:

  • how='inner' (内连接):这是默认选项,也是最严格的。它只保留左右两个DataFrame中键都存在的行。你可以想象成,只有当两个数据源都能找到匹配的对象时,这条记录才会被纳入结果。
  • how='left' (左连接):以左边的DataFrame为基准。它会保留左DataFrame的所有行,并尝试在右DataFrame中找到匹配的行。如果找到了,就合并;如果没找到,右DataFrame对应的列就用 NaN 填充。这在你想保留所有“主”数据(比如所有客户信息),然后附加“次要”数据(比如他们的订单历史)时非常有用。
  • how='right' (右连接):与左连接相反,以右边的DataFrame为基准。保留右DataFrame的所有行,并尝试在左DataFrame中找到匹配的行。
  • how='outer' (外连接):这是最“包容”的连接方式。它会保留左右两个DataFrame中的所有行。如果某个键只存在于一个DataFrame中,另一个DataFrame对应的列就会用 NaN 填充。这在你想看到所有可能的组合,即使数据不完全匹配时,会很有帮助。
# 示例数据
df_users = pd.DataFrame({
    'user_id': [1, 2, 3, 4],
    'username': ['alpha', 'beta', 'gamma', 'delta']
})

df_orders = pd.DataFrame({
    'order_id': [101, 102, 103, 104],
    'user_id': [1, 2, 5, 1], # user_id 5 不在 df_users 中
    'amount': [100, 150, 200, 120]
})

# 内连接:只显示有订单的用户
inner_join = pd.merge(df_users, df_orders, on='user_id', how='inner')
# print("\nInner Join:\n", inner_join)

# 左连接:显示所有用户,以及他们的订单(如果没有则为NaN)
left_join = pd.merge(df_users, df_orders, on='user_id', how='left')
# print("\nLeft Join:\n", left_join)

# 右连接:显示所有订单,以及对应的用户信息(如果用户不存在则为NaN)
right_join = pd.merge(df_users, df_orders, on='user_id', how='right')
# print("\nRight Join:\n", right_join)

# 外连接:显示所有用户和所有订单,无论是否匹配
outer_join = pd.merge(df_users, df_orders, on='user_id', how='outer')
# print("\nOuter Join:\n", outer_join)
登录后复制

处理多键合并或非等值连接: 当需要根据多个列进行合并时,merge 同样游刃有余。你只需要把 on、left_on 或 right_on 参数的值从单个字符串变成一个字符串列表即可。比如,如果你需要根据 (department_id, employee_name) 来匹配数据,那就 on=['department_id', 'employee_name']。

df_emp_details = pd.DataFrame({
    'emp_id': [1, 2, 3],
    'first_name': ['John', 'Jane', 'Peter'],
    'last_name': ['Doe', 'Smith', 'Jones']
})

df_emp_salaries = pd.DataFrame({
    'employee_id': [1, 2, 3],
    'first': ['John', 'Jane', 'Peter'],
    'last': ['Doe', 'Smith', 'Jones'],
    'salary': [50000, 60000, 70000]
})

# 多键合并
multi_key_merge = pd.merge(df_emp_details, df_emp_salaries,
                           left_on=['emp_id', 'first_name', 'last_name'],
                           right_on=['employee_id', 'first', 'last'],
                           how='inner')
# print("\nMulti-key Merge:\n", multi_key_merge)
登录后复制

至于非等值连接(比如 A.value > B.value),pd.merge 本身并不直接支持,它主要用于等值连接。如果真有这样的需求,我通常会考虑几种变通方法:要么是先进行一个宽泛的合并,然后用布尔索引进行筛选;要么是利用 pd.merge_asof(针对时间序列的近似匹配);再复杂一点,可能就要回到循环或者自定义函数,但那已经超出了 merge 的核心范畴了。

性能考量与最佳实践:何时选择 concat 或 merge?

在处理大数据量时,concat 和 merge 的性能表现确实值得掰扯一下。这就像是开车,你知道目的地,但得选对路和交通工具

性能表现:

  • concat: 对于简单的行堆叠,concat 通常非常高效,因为它主要是内存块的拼接。尤其是当所有DataFrame的列结构完全一致时,它的开销相对较小。然而,如果需要进行大量的列级别合并(axis=1),并且索引不完全对齐,那么 concat 可能需要在内部进行大量的重索引和数据填充,这会消耗更多的时间和内存。
  • merge: merge 的性能通常取决于你选择的连接类型 (how) 以及用于连接的键列。在内部,pandas会尝试优化,比如使用哈希表或排序合并算法。如果键列已经被索引(通过 df.set_index()),或者键列的数据类型是高效的(比如整数或分类类型),merge 的性能会更好。但如果键列是字符串且非常长,或者数据量巨大且键列没有优化,merge 可能会变得相当慢,因为它需要进行大量的字符串比较和哈希计算。

选择哪一个?这其实是个“哲学问题”,答案完全取决于你的数据关系和目标。

我的经验是:

  • 选择 concat 的时机:

    • 数据结构一致,需要垂直扩展: 你有多个DataFrame,它们拥有相同的列名和数据类型,你只是想把它们堆叠起来,形成一个更长的DataFrame。比如,每日销售数据文件,你需要把它们合并成月度或年度报告。
    • 需要水平扩展,且索引能够对齐: 你有多个DataFrame,它们有相同的行索引,你只是想把它们并排放置,增加列。比如,一个DataFrame有用户基本信息,另一个有用户的联系方式,它们的索引都是 user_id。
    • 数据来源分散,但内容同质: 比如爬取了多个网页,每个网页的数据结构都一样,需要汇总。
  • 选择 merge 的时机:

    • 数据存在关联关系,需要水平整合: 你的数据分布在不同的DataFrame中,但它们之间存在逻辑上的关联(比如通过 customer_id 或 product_id)。你需要根据这些关联键来组合信息。这就像数据库中的表连接。
    • 需要根据特定条件筛选或补充数据: 你想根据某个DataFrame的完整性来决定合并结果(如 left join 或 right join),或者你想找出所有匹配和不匹配的项(如 outer join)。
    • 数据来源异构,但逻辑相关: 比如一个DataFrame是订单明细,另一个是产品信息,你需要把产品名称、价格等信息加到订单明细中。

最佳实践建议:

  1. 数据清洗先行: 在进行任何合并操作之前,务必确保你的键列(或其他需要对齐的列)是干净的。检查数据类型是否一致,是否存在前导/尾随空格,大小写是否统一。一个小小的差异都可能导致 merge 失败或 concat 产生 NaN。
  2. 明确 how 参数: 对于 merge,一定要想清楚你需要哪种连接类型。是只保留匹配项 (inner),还是保留左边所有 (left),或者所有 (outer)?这直接决定了结果数据集的完整性和大小。
  3. 索引的考量:
    • 对于 concat(axis=0),如果原始索引不重要,总是考虑 ignore_index=True 来避免索引重复。
    • 对于 merge,如果你想基于索引进行合并,可以使用 left_index=True 和 right_index=True。如果键列是索引,并且数据量很大,pandas在某些情况下会利用索引的有序性来加速合并。
  4. 内存管理: 当处理非常大的DataFrame时,合并操作可能会消耗大量内存。如果遇到内存不足的问题,可以考虑:
    • 逐块处理数据(例如,使用 pd.read_csv(chunksize=...))。
    • 优化DataFrame的数据类型(例如,使用 category 类型代替 object 类型来存储重复的字符串)。
    • 仅选择必要的列进行合并,减少不必要的内存开销。

总的来说,concat 和 merge 都是pandas数据处理的基石,它们解决的问题截然不同。选择合适的工具,理解其背后的逻辑和潜在的“坑”,能让你在数据处理的道路上走得更稳更快。

以上就是Python中如何合并多个DataFrame?concat与merge对比指南的详细内容,更多请关注php中文网其它相关文章!

最佳 Windows 性能的顶级免费优化软件
最佳 Windows 性能的顶级免费优化软件

每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。

下载
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
最新问题
开源免费商场系统广告
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板
关于我们 免责申明 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送
PHP中文网APP
随时随地碎片化学习
PHP中文网抖音号
发现有趣的

Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号