在数据分析的实战中我们极少遇到所有数据都干净利落地躺在同一个表格里的情况。更多时候数据分散在多个文件、多个表甚至不同的数据源中。如何将这些零散的数据碎片高效、准确地拼接成一个完整的分析对象是每一位数据从业者必须掌握的核心技能。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默认情况下axis0即按行连接。这相当于把三个Series首尾相接形成了一个更长的Series。原有的索引被完整保留这非常有用因为它保留了每个数据点原始的“身份标签”。如果我们想把这些Series并排放在一起形成一个DataFrame就需要用到axis1。print(pd.concat([s1, s2, s3], axis1))输出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用NaNNot 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], namea) # 按行连接 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], axis1))输出a b a 1 1 4 7 2 2 5 10concat将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], axis1))输出a b a b 1 1 4 7 10 2 2 5 8 11按列连接时它将两个DataFrame并排放置形成了更宽的表格。同样列名重复的问题需要我们自己留意。4. 重置索引让数据更规整很多时候拼接后的索引可能变得混乱例如有重复我们并不关心原始索引只想得到一个全新的、连续的整数索引。这时ignore_indexTrue参数就派上了用场。pd.concat([df1, df2], ignore_indexTrue)输出a b 0 1 4 1 2 5 2 7 10 3 8 11这个操作非常实用尤其是在你完成数据清洗、准备将数据导入数据库或进行下一步分析时一个干净的索引能避免很多麻烦。5. 像SQL的JOIN一样控制列的交集concat不仅仅能简单堆叠它还能通过join参数控制其他轴上的合并逻辑。默认是joinouter并集这意味着所有列都会被保留。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.0df1有a、b两列df2有b、c两列。joinouter将三列全部保留缺失处填充NaN。如果我们只关心两个DataFrame中都存在的列可以使用joininner交集。print(pd.concat([df1, df2], joininner))输出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, onemployee)通过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_onemployee, right_onname)输出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_indexTrue, right_indexTrue)此时结果中的行由索引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, howinner)) print(\n左连接:\n, pd.merge(df1, df2, howleft)) print(\n右连接:\n, pd.merge(df1, df2, howright)) print(\n外连接:\n, pd.merge(df1, df2, howouter))输出内连接: 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, onname, 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工作流通常会这样组合运用它们先用read_csv等函数加载多个数据源。用merge根据业务键如用户ID将相关的维表如用户信息表与事实表如订单表进行关联构建一个宽表。再用concat将这个宽表与按时间或批次新生成的同类数据堆叠在一起形成完整的分析数据集。掌握这两个函数意味着你已经拥有了处理大部分数据组合场景的能力。希望这篇文章能帮助你从原理到实践彻底打通Pandas数据组合的任督二脉。现在去你的数据中尝试一下吧