深度解析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数据组合的任督二脉。现在,去你的数据中尝试一下吧!

相关推荐
童园管理札记2 小时前
2026实测|GPT-4.5+Agent智能体:3小时搭建企业级客服系统,附完整源码与部署教程(一)
经验分享·python·深度学习·重构·学习方法
wzl202612132 小时前
《基于企微会话存档的精准发送策略:从互动数据分析到防折叠群发》
java·数据分析·企业微信
大飞记Python2 小时前
【2026更新】Python基础学习指南(AI版)——安装
自动化测试·python·ai编程
AI街潜水的八角2 小时前
YOLO26手语识别项目实战1-三十五种手语实时检测系统数据集说明(含下载链接)
python·深度学习
2401_827499992 小时前
python核心语法03-数据存储容器
开发语言·python
哈伦20192 小时前
第一章 Jupyter Notebook基础实操
python·jupyter·基础操作
人道领域2 小时前
2026技术展望】Python与AI的深度融合:从“能用”到“好用”的质变之年
人工智能·python·大模型·agent
chushiyunen2 小时前
python异常模拟工具类(异常生成工具类)
开发语言·python
测试19982 小时前
python+selenium 定位到元素,无法点击的解决方法
自动化测试·软件测试·python·selenium·测试工具·测试用例·压力测试