当前位置: 首页 > news >正文

深度解析Pandas数据组合:从concat到merge,打通你的数据处理任督二脉

在数据分析的实战中,我们极少遇到所有数据都干净利落地躺在同一个表格里的情况。更多时候,数据分散在多个文件、多个表,甚至不同的数据源中。如何将这些零散的数据碎片高效、准确地拼接成一个完整的分析对象,是每一位数据从业者必须掌握的核心技能。

Pandas库为我们提供了两把处理这类问题的“瑞士军刀”:concat(连接)和merge(合并)。它们看似功能重叠,实则各司其职,一个侧重于结构的堆叠,一个侧重于基于键的关联。本文将彻底讲透这两者的区别与联系,并通过丰富的代码示例,让你在实际工作中能游刃有余地应对各种数据组合场景。

一、concat:轴向堆叠的艺术

concat函数的核心思想非常直观:它就像搭积木一样,沿着一条轴(行或列)将多个Pandas对象(Series或DataFrame)简单地堆叠在一起。它不关心数据的“关系”,只关心数据的“位置”。

1. Series与Series的堆叠

先来看最基础的单维数据堆叠。我们创建三个独立的Series,每个都有自己的索引。

import pandas as pd s1 = pd.Series(["A", "B"], index=[1, 2]) s2 = pd.Series(["D", "E"], index=[4, 5]) s3 = pd.Series(["G", "H"], index=[7, 8]) print(pd.concat([s1, s2, s3]))

输出:

1 A 2 B 4 D 5 E 7 G 8 H dtype: object

默认情况下,axis=0,即按行连接。这相当于把三个Series首尾相接,形成了一个更长的Series。原有的索引被完整保留,这非常有用,因为它保留了每个数据点原始的“身份标签”。

如果我们想把这些Series并排放在一起,形成一个DataFrame,就需要用到axis=1。

print(pd.concat([s1, s2, s3], axis=1))

输出:

0 1 2 1 A NaN NaN 2 B NaN NaN 4 NaN D NaN 5 NaN E NaN 7 NaN NaN G 8 NaN NaN H

这里发生了两件事:第一,它创建了一个三列的DataFrame,每列对应一个输入的Series;第二,因为各个Series的索引并不完全对齐,Pandas用NaN(Not a Number)自动填充了缺失的位置。这正是concat在列方向拼接时的默认行为——外连接(outer join),即取所有索引的并集。

2. DataFrame与Series的混合堆叠

现实场景中,我们经常需要将一个DataFrame与一个Series进行组合。例如,我们有一个用户基本信息表,现在要追加一行新用户的记录。

df1 = pd.DataFrame(data={"a": [1, 2], "b": [4, 5]}, index=[1, 2]) s1 = pd.Series(data=[7, 10], index=[1, 2], name="a") # 按行连接 print(pd.concat([df1, s1]))

输出:

a b 1 1 4.0 2 2 5.0 1 7 NaN 2 10 NaN

这里concat将Series s1作为新行添加到了DataFrame df1的下面。请注意,s1拥有自己的索引[1,2],并且它被转换成了一个单列的DataFrame。由于df1有a和b两列,而s1只有a列,因此在b列的位置填充了NaN。

而按列连接时,效果又不同了。

print(pd.concat([df1, s1], axis=1))

输出:

a b a 1 1 4 7 2 2 5 10

concat将Series s1作为新的一列添加到DataFrame的右侧。有趣的是,df1和s1都包含名为a的列,但concat并不会自动处理列名冲突,而是直接保留,这在后续分析中可能引起混淆,需要我们注意。

3. DataFrame与DataFrame的堆叠

当处理多个结构相似的表时,concat显得尤为高效。

df1 = pd.DataFrame(data={"a": [1, 2], "b": [4, 5]}, index=[1, 2]) df2 = pd.DataFrame(data={"a": [7, 8], "b": [10, 11]}, index=[1, 2]) # 按行连接:追加行 print(pd.concat([df1, df2]))

输出:

a b 1 1 4 2 2 5 1 7 10 2 8 11

这就像把两个结构完全相同的表格上下叠放在一起。如果索引重复,它也会保留。

# 按列连接:追加列 print(pd.concat([df1, df2], axis=1))

输出:

a b a b 1 1 4 7 10 2 2 5 8 11

按列连接时,它将两个DataFrame并排放置,形成了更宽的表格。同样,列名重复的问题需要我们自己留意。

4. 重置索引:让数据更规整

很多时候,拼接后的索引可能变得混乱(例如有重复),我们并不关心原始索引,只想得到一个全新的、连续的整数索引。这时,ignore_index=True参数就派上了用场。

pd.concat([df1, df2], ignore_index=True)

输出:

a b 0 1 4 1 2 5 2 7 10 3 8 11

这个操作非常实用,尤其是在你完成数据清洗、准备将数据导入数据库或进行下一步分析时,一个干净的索引能避免很多麻烦。

5. 像SQL的JOIN一样控制列的交集

concat不仅仅能简单堆叠,它还能通过join参数控制其他轴上的合并逻辑。默认是join='outer'(并集),这意味着所有列都会被保留。

df1 = pd.DataFrame(data={"a": [1, 2], "b": [4, 5]}, index=[1, 2]) df2 = pd.DataFrame(data={"b": [7, 8], "c": [10, 11]}, index=[2, 3]) print(pd.concat([df1, df2]))

输出:

a b c 1 1.0 4 NaN 2 2.0 5 NaN 2 NaN 7 10.0 3 NaN 8 11.0

df1有a、b两列,df2有b、c两列。join='outer'将三列全部保留,缺失处填充NaN。

如果我们只关心两个DataFrame中都存在的列,可以使用join='inner'(交集)。

print(pd.concat([df1, df2], join="inner"))

输出:

b 1 4 2 5 2 7 3 8

结果中只保留了公共的b列。这种按列的交集合并,为处理列结构不一致的数据提供了极大的灵活性。

二、merge:基于键的关系型连接

如果说concat是物理上的堆叠,那么merge就是逻辑上的关联。它的工作方式与SQL中的JOIN操作如出一辙,通过一个或多个共同的键(key)将不同DataFrame中的行连接起来。这是数据分析中最常用、最强大的数据组合方式。

1. 理解数据连接的类型

merge的核心是理解参与连接的字段之间的基数关系。这决定了最终结果的行数。

  • 一对一连接:最常见的场景,就像员工表和部门表通过唯一的员工ID连接。每个键在左右两边都只出现一次。
df1 = pd.DataFrame({"employee": ["Bob", "Jake", "Lisa", "Sue"], "group": ["Accounting", "Engineering", "Engineering", "HR"]}) df2 = pd.DataFrame({"employee": ["Lisa", "Bob", "Jake", "Sue"], "hire_date": [2004, 2008, 2012, 2014]}) # 默认使用两表中同名的列'employee'进行连接 df_merged = pd.merge(df1, df2) print(df_merged)

输出:

employee group hire_date 0 Bob Accounting 2008 1 Jake Engineering 2012 2 Lisa Engineering 2004 3 Sue HR 2014
  • 多对一连接:例如,员工表(多)和主管表(一)。一个主管管理多个员工,因此“主管”在左表中重复出现,在右表中是唯一的。
df1 = pd.DataFrame({"employee": ["Bob", "Jake", "Lisa", "Sue"], "group": ["Accounting", "Engineering", "Engineering", "HR"]}) df2 = pd.DataFrame({"group": ["Accounting", "Engineering", "HR"], "supervisor": ["Carly", "Guido", "Steve"]}) df_merged = pd.merge(df1, df2) print(df_merged)

输出:

employee group supervisor 0 Bob Accounting Carly 1 Jake Engineering Guido 2 Lisa Engineering Guido 3 Sue HR Steve

注意观察,Engineering组的主管Guido,被自动匹配给了该组的所有员工Jake和Lisa。

  • 多对多连接:当两边的键都存在重复时,就会发生多对多连接,其结果是产生笛卡尔积。例如,员工技能表。一个员工可能有多个技能,一个技能也可能对应多个员工。
df1 = pd.DataFrame({"employee": ["Bob", "Jake", "Lisa", "Sue"], "group": ["Accounting", "Engineering", "Engineering", "HR"]}) df2 = pd.DataFrame({"group": ["Accounting", "Accounting", "Engineering", "Engineering", "HR", "HR"], "skills": ["math", "spreadsheets", "coding", "linux", "spreadsheets", "organization"]}) df_merged = pd.merge(df1, df2) print(df_merged)

输出:

employee group skills 0 Bob Accounting math 1 Bob Accounting spreadsheets 2 Jake Engineering coding 3 Jake Engineering linux 4 Lisa Engineering coding 5 Lisa Engineering linux 6 Sue HR spreadsheets 7 Sue HR organization

由于左表有2个Engineering,右表也有2个Engineering,因此Engineering组产生了4行结果。

2. 精准控制合并的键

merge提供了多种方式来指定连接键,这是其灵活性的体现。

  • 通过on指定共同列名:当两个DataFrame有完全相同名称的连接列时,这是最简洁的写法。
pd.merge(df1, df2, on="employee")
  • 通过left_on和right_on指定不同的列名:当键的列名不同时,我们需要分别指定。
df1 = pd.DataFrame({"employee": ["Bob", "Jake", "Lisa", "Sue"], "group": ["Accounting", "Engineering", "Engineering", "HR"]}) df2 = pd.DataFrame({"name": ["Bob", "Jake", "Lisa", "Sue"], "salary": [70000, 80000, 120000, 90000]}) pd.merge(df1, df2, left_on="employee", right_on="name")

输出:

employee group name salary 0 Bob Accounting Bob 70000 1 Jake Engineering Jake 80000 2 Lisa Engineering Lisa 120000 3 Sue HR Sue 90000
  • 通过left_index和right_index合并索引:这是一种非常高效的合并方式,尤其当DataFrame的索引本身就代表有意义的实体时。
# 将'employee'列设为索引 df1_indexed = df1.set_index("employee") df2_indexed = df2.set_index("employee") # 通过索引合并 pd.merge(df1_indexed, df2_indexed, left_index=True, right_index=True)

此时,结果中的行由索引(employee)决定,而不是列。

3. 驾驭数据连接的集合操作规则(JOIN类型)

这是merge的精髓所在。通过how参数,我们可以精确控制哪些行应该出现在最终结果中。这直接对应了SQL中的四种主要JOIN类型。

  • 内连接(inner join,默认):只保留左右两边键都匹配的行。
  • 外连接(outer join):保留左右两边键的所有行,不匹配的地方用NaN填充。
  • 左连接(left join):保留左表的所有行,右表只保留匹配的行。
  • 右连接(right join):保留右表的所有行,左表只保留匹配的行。
df1 = pd.DataFrame({"name": ["Peter", "Paul", "Mary"], "food": ["fish", "beans", "bread"]}) df2 = pd.DataFrame({"name": ["Mary", "Joseph"], "drink": ["wine", "beer"]}) print("内连接:\n", pd.merge(df1, df2, how="inner")) print("\n左连接:\n", pd.merge(df1, df2, how="left")) print("\n右连接:\n", pd.merge(df1, df2, how="right")) print("\n外连接:\n", pd.merge(df1, df2, how="outer"))

输出:

内连接: name food drink 0 Mary bread wine 左连接: name food drink 0 Peter fish NaN 1 Paul beans NaN 2 Mary bread wine 右连接: name food drink 0 Mary bread wine 1 Joseph NaN beer 外连接: name food drink 0 Mary bread wine 1 Joseph NaN beer 2 Paul beans NaN 3 Peter fish NaN

理解并熟练运用这四种连接方式,是解决复杂数据整合问题的关键。

4. 优雅处理重复列名

当两个DataFrame拥有同名的非连接列时,merge会自动为它们添加后缀_x和_y以作区分。我们可以通过suffixes参数自定义这些后缀,让结果表更具可读性。

df1 = pd.DataFrame({"name": ["Bob", "Jake", "Lisa", "Sue"], "rank": [1, 2, 3, 4]}) df2 = pd.DataFrame({"name": ["Bob", "Jake", "Lisa", "Sue"], "rank": [3, 1, 4, 2]}) print(pd.merge(df1, df2, on="name", suffixes=("_first", "_second")))

输出:

name rank_first rank_second 0 Bob 1 3 1 Jake 2 1 2 Lisa 3 4 3 Sue 4 2

总结

concat和merge是Pandas数据组合的双子星,它们的设计哲学完美体现了Pandas的简洁与强大。

  • concat是“堆叠器”:它专注于数据的物理拼接,无论是行方向的追加,还是列方向的扩展。当你的目标是合并两个结构完全相同、只是数据不同的表,或者想为现有数据简单地添加一行/一列时,concat是最直接、最轻量的选择。
  • merge是“关联器”:它模拟了SQL的JOIN操作,是解决复杂数据关联问题的终极武器。当你的数据分散在多个表,且这些表之间通过某种“键”(如用户ID、订单号、日期)存在逻辑关系时,merge就是你首选的工具。它不仅能处理多种基数关系(1:1, 1:M, M:N),还能通过how参数灵活控制连接的逻辑,并通过suffixes等参数优雅地管理列名冲突。

在实战中,一个成熟的Pandas工作流通常会这样组合运用它们:

  1. 先用read_csv等函数加载多个数据源。
  2. 用merge根据业务键(如用户ID)将相关的维表(如用户信息表)与事实表(如订单表)进行关联,构建一个宽表。
  3. 再用concat将这个宽表与按时间或批次新生成的同类数据堆叠在一起,形成完整的分析数据集。

掌握这两个函数,意味着你已经拥有了处理大部分数据组合场景的能力。希望这篇文章能帮助你从原理到实践,彻底打通Pandas数据组合的任督二脉。现在,去你的数据中尝试一下吧!

http://www.cnnetsun.cn/news/1633202.html

相关文章:

  • CarSim仿真效率提升秘籍:活用Dataset和Library菜单的5个高级技巧
  • Qwen3-VL-4B Pro参数详解:Temperature/Max Tokens滑块调节效果实测
  • Qwen3-VL-30B部署避坑指南:从下载到运行一气呵成
  • Spring事务管理器选型指南:从DataSource到JTA,别再傻傻分不清了
  • 告别例程导入烦恼:Zynq 7020 + Vitis 2023高效开发工作流搭建实录
  • KEIL 5.38如何手动安装ARM Compiler V5?完整配置流程分享
  • 告别重复造轮子:用快马AI一键生成openclaw项目高效串口调试工具
  • 2026 Twitch多账号挂播攻略:如何安全防关联并领取所有Twitch掉宝奖励?
  • 智能车比赛必备:手把手教你用FoxGlove搭建OriginCar监控系统(附避坑指南)
  • 50天学习FPGA第35天-增量编译
  • 小爱音箱音乐自由终极指南:解锁无限听歌的智能解决方案
  • Ubuntu 22.04 上部署 Caddy:从零搭建安全高效的现代 Web 服务
  • GG3M 反熵增演化数学模型完整推导过程
  • 3步实现GitHub资源精准获取:DownGit带来的开发者效率革命
  • INICT03A计算机传输模块
  • MAA游戏助手:如何让《明日方舟》的日常任务自动完成?
  • 手把手教你用ZEMAX复现Thorlabs锥透镜生成贝塞尔光束(附Edmund透镜库文件)
  • PlugY:暗黑破坏神2单机增强方案的开源工具创新实践
  • 驱动适配与优化:5个高效步骤解决Realtek 8852AE无线网卡问题
  • 深入解析RS485接口:从硬件设计到工业应用
  • Wan2.2-I2V-A14B开源大模型部署:对比Stable Video Diffusion成本效益分析
  • WinCC 7.5 纯水项目场景控制案例——上位机组态模板分享
  • PyTorch 2.8深度学习镜像入门必看:RTX 4090D环境验证与快速上手步骤
  • 基于STM32F与ESP8266的智能桌面天气时钟:从网络授时到OLED显示的完整实现
  • 零基础教程:5个简单步骤用Mi-Create打造个性化小米手表表盘
  • PP-DocLayoutV3多场景:政务公文/金融合同/科研论文/新闻报纸四类文档泛化测试
  • Qwen3-8B实战测评:8B模型中的逻辑推理王者,实测效果惊艳
  • QT 5.14.0实战:手把手教你用QLineEdit打造一个带验证码的登录框(附完整样式代码)
  • 告别手动复制粘贴!用Java + Apache POI 5.0.0自动生成周报PPT(附完整源码)
  • Qwen3.5-2B人工智能启蒙:零基础开发者快速理解多模态AI