Pandas合并与连接(merge/join/concat)
多表数据整合技术完全指南
一、概述
在数据分析工作中,多表合并 是最常见也最重要的操作之一。真实世界的数据很少完整地存在于一张表中,往往分散在多个数据源。Pandas 提供了三种核心合并机制:merge、join 和 concat,它们分别对应不同的应用场景。
merge
SQL风格连接
基于列值匹配
支持inner/left/right/outer
join
索引连接
基于索引匹配
merge的语法糖
concat
轴连接
沿行/列拼接
类似SQL的UNION
核心概念速览
merge :最灵活、最常用的合并方式,基于一个或多个键将两个DataFrame的行连接起来,类似SQL的JOIN操作
join :merge的简化版本,默认基于索引进行连接,语法更简洁
concat :纯粹的拼接操作,沿行方向(增加行)或列方向(增加列)合并数据,不依赖键匹配
二、pd.merge() — SQL风格连接
pd.merge() 是Pandas中最强大、最常用的合并函数。它根据一个或多个键(列名)将两个DataFrame的行连接起来,操作方式与SQL的JOIN完全一致。
2.1 基本用法
# 创建两个示例DataFrame
import pandas as pd
df1 = pd.DataFrame({
'员工ID' : [101 , 102 , 103 , 104 ],
'姓名' : ['张三' , '李四' , '王五' , '赵六' ],
'部门' : ['技术' , '市场' , '技术' , '财务' ]
})
df2 = pd.DataFrame({
'员工ID' : [101 , 102 , 103 , 105 ],
'薪资' : [15000 , 12000 , 18000 , 9000 ],
'职位' : ['工程师' , '经理' , '高级工程师' , '助理' ]
})
# 默认内连接(inner join)
result = pd.merge(df1, df2, on='员工ID' )
print(result)
员工ID 姓名 部门 薪资 职位
0 101 张三 技术 15000 工程师
1 102 李四 市场 12000 经理
2 103 王五 技术 18000 高级工程师
可以看到,merge 默认使用 inner join,只保留两个表中都有匹配的行(员工ID 104和105被排除)。
三、merge参数详解
3.1 how参数 — 连接方式
how 是merge最重要的参数,控制连接方式,与SQL的JOIN类型一一对应。
how参数
SQL对应
说明
'inner'
INNER JOIN
只保留键匹配的行(默认)
'left'
LEFT JOIN
保留左表所有行,右表无匹配则填NaN
'right'
RIGHT JOIN
保留右表所有行,左表无匹配则填NaN
'outer'
FULL OUTER JOIN
保留两个表所有行,无匹配填NaN
# 左连接:保留左表所有行
left_join = pd.merge(df1, df2, on='员工ID' , how='left' )
print(left_join)
# 右连接:保留右表所有行
right_join = pd.merge(df1, df2, on='员工ID' , how='right' )
print(right_join)
# 外连接:保留所有行
outer_join = pd.merge(df1, df2, on='员工ID' , how='outer' )
print(outer_join)
# left_join 输出:
员工ID 姓名 部门 薪资 职位
0 101 张三 技术 15000 工程师
1 102 李四 市场 12000 经理
2 103 王五 技术 18000 高级工程师
3 104 赵六 财务 NaN NaN
# right_join 输出:
员工ID 姓名 部门 薪资 职位
0 101 张三 技术 15000 工程师
1 102 李四 市场 12000 经理
2 103 王五 技术 18000 高级工程师
3 105 NaN NaN 9000 助理
# outer_join 输出:
员工ID 姓名 部门 薪资 职位
0 101 张三 技术 15000 工程师
1 102 李四 市场 12000 经理
2 103 王五 技术 18000 高级工程师
3 104 赵六 财务 NaN NaN
4 105 NaN NaN 9000 助理
理解连接方式
选择策略:
inner :只关心两个表都有记录的数据,最常用且效率最高
left :以左表为主,补充右表信息(如:员工表 left join 薪资表)
right :以右表为主,功能与left对称,通常用left替代
outer :需要完整数据视图时使用,注意会产生大量NaN
3.2 on / left_on / right_on — 连接键指定
当两个表用于连接的列名相同时,直接用 on 指定;列名不同时,分别用 left_on 和 right_on。
# 列名相同:使用 on
pd.merge(df1, df2, on='员工ID' )
# 列名不同:使用 left_on 和 right_on
df2_renamed = df2.rename(columns={'员工ID' : '员工编号' })
result = pd.merge(df1, df2_renamed,
left_on='员工ID' ,
right_on='员工编号' )
print(result)
# 多键连接:传递列名列表
orders = pd.DataFrame({
'客户ID' : [1 , 1 , 2 ],
'产品ID' : ['A' , 'B' , 'A' ],
'金额' : [100 , 200 , 150 ]
})
details = pd.DataFrame({
'客户ID' : [1 , 1 , 2 ],
'产品ID' : ['A' , 'B' , 'B' ],
'产品名' : ['手机' , '电脑' , '平板' ]
})
# 多键内连接
merged_multi = pd.merge(orders, details, on=['客户ID' , '产品ID' ], how='inner' )
print(merged_multi)
# 多键合并结果:
客户ID 产品ID 金额 产品名
0 1 A 100 手机
1 1 B 200 电脑
3.3 suffixes参数 — 重叠列名处理
当两个表有同名的非键列时,merge会自动添加后缀区分,默认后缀为 ('_x', '_y')。
# 两个表都有同名的'备注'列
df_a = pd.DataFrame({
'ID' : [1 , 2 ],
'名称' : ['A' , 'B' ],
'备注' : ['来自源A' , '来自源A' ]
})
df_b = pd.DataFrame({
'ID' : [1 , 2 ],
'值' : [10 , 20 ],
'备注' : ['来自源B' , '来自源B' ]
})
# 默认后缀
result = pd.merge(df_a, df_b, on='ID' )
print(result.columns) # Index(['ID', '名称', '备注_x', '值', '备注_y'])
# 自定义后缀
result = pd.merge(df_a, df_b, on='ID' , suffixes=('_左表' , '_右表' ))
print(result)
ID 名称 备注_左表 值 备注_右表
0 1 A 来自源A 10 来自源B
1 2 B 来自源A 20 来自源B
四、DataFrame.join() — 索引连接
join() 是 merge 基于索引连接的快捷方式。它默认使用两个DataFrame的索引作为连接键,语法更简洁。
# 创建基于索引的数据
left = pd.DataFrame({
'名称' : ['苹果' , '香蕉' , '橙子' ]
}, index=['A' , 'B' , 'C' ])
right = pd.DataFrame({
'价格' : [5.5 , 3.0 , 4.2 ],
'库存' : [100 , 200 , 150 ]
}, index=['A' , 'B' , 'D' ])
# 左连接(默认)
result = left.join(right)
print(result)
# 指定连接方式
result_inner = left.join(right, how='inner' )
print(result_inner)
# 左连接结果:
名称 价格 库存
A 苹果 5.5 100.0
B 香蕉 3.0 200.0
C 橙子 NaN NaN
# 内连接结果:
名称 价格 库存
A 苹果 5.5 100
B 香蕉 3.0 200
merge vs join 对比
merge :基于列值连接,需要指定 on/left_on/right_on,更灵活
join :基于索引连接,语法简洁,适合索引对齐的场景
join可混合使用 :join也可以按列连接,通过传入 on 参数指定左表的列
本质关系 :join是merge的特例,df1.join(df2) 等价于 pd.merge(df1, df2, left_index=True, right_index=True)
join 还有一个非常有用的特性:可以一次性合并多个DataFrame。
# 一次合并多个DataFrame
df_a = pd.DataFrame({'A' : [1 , 2 ]}, index=['x' , 'y' ])
df_b = pd.DataFrame({'B' : [3 , 4 ]}, index=['x' , 'y' ])
df_c = pd.DataFrame({'C' : [5 , 6 ]}, index=['x' , 'y' ])
result = df_a.join([df_b, df_c])
print(result)
A B C
x 1 3 5
y 2 4 6
五、pd.concat() — 轴连接
concat 用于纯粹的拼接操作,不依赖键匹配。它沿行方向(axis=0)或列方向(axis=1)合并数据。
5.1 行方向连接(axis=0)
按行拼接,相当于SQL的UNION ALL。这是最常用的 concat 方式,用于追加数据。
# 创建两个季度数据
q1 = pd.DataFrame({
'产品' : ['A' , 'B' , 'C' ],
'销量' : [100 , 200 , 150 ],
'月份' : [1 , 1 , 1 ]
})
q2 = pd.DataFrame({
'产品' : ['A' , 'B' , 'D' ],
'销量' : [130 , 180 , 90 ],
'月份' : [2 , 2 , 2 ]
})
# 默认axis=0,按行拼接
all_data = pd.concat([q1, q2])
print(all_data)
产品 销量 月份
0 A 100 1
1 B 200 1
2 C 150 1
0 A 130 2
1 B 180 2
2 D 90 2
5.2 ignore_index参数
如上例所示,concat 保留了原DataFrame的索引。使用 ignore_index=True 可以重置为连续整数索引。
# 重置索引
all_data_reset = pd.concat([q1, q2], ignore_index=True )
print(all_data_reset)
# 输出:
# 产品 销量 月份
# 0 A 100 1
# 1 B 200 1
# 2 C 150 1
# 3 A 130 2
# 4 B 180 2
# 5 D 90 2
5.3 keys参数 — 添加层级索引
keys 参数可以为每个源DataFrame添加标签,创建MultiIndex层级索引。
# 添加层级索引标签
result = pd.concat([q1, q2], keys=['Q1' , 'Q2' ])
print(result)
产品 销量 月份
Q1 0 A 100 1
1 B 200 1
2 C 150 1
Q2 0 A 130 2
1 B 180 2
2 D 90 2
5.4 列方向连接(axis=1)
按列拼接,相当于横向合并,通常基于索引对齐。
# 列方向连接
left = pd.DataFrame({
'ID' : [1 , 2 , 3 ],
'名称' : ['X' , 'Y' , 'Z' ]
})
right = pd.DataFrame({
'价格' : [10.5 , 20.0 , 15.0 ],
'数量' : [50 , 30 , 40 ]
})
combined = pd.concat([left, right], axis=1 )
print(combined)
ID 名称 价格 数量
0 1 X 10.5 50
1 2 Y 20.0 30
2 3 Z 15.0 40
5.5 join参数 — 内外连接
concat 默认使用 join='outer',保留所有索引。使用 join='inner' 可以只保留交集。
# outer join(默认):保留所有行
df_a = pd.DataFrame({'A' : [1 , 2 ]}, index=['x' , 'y' ])
df_b = pd.DataFrame({'B' : [3 , 4 ]}, index=['y' , 'z' ])
outer = pd.concat([df_a, df_b], axis=1 )
print('outer:' )
print(outer)
# inner join:只保留共同索引
inner = pd.concat([df_a, df_b], axis=1 , join='inner' )
print('inner:' )
print(inner)
outer:
A B
x 1.0 NaN
y 2.0 3.0
z NaN 4.0
inner:
A B
y 2 3
六、多对多合并
当两个表的连接键都有重复值时,就会发生多对多合并。这种情况下,会产生笛卡尔积——左表每个匹配行与右表每个匹配行组合。
# 多对多合并示例
left_mm = pd.DataFrame({
'组' : ['A' , 'A' , 'B' ],
'值_L' : [1 , 2 , 3 ]
})
right_mm = pd.DataFrame({
'组' : ['A' , 'A' , 'B' , 'B' ],
'值_R' : [10 , 20 , 30 , 40 ]
})
result = pd.merge(left_mm, right_mm, on='组' )
print(result)
组 值_L 值_R
0 A 1 10
1 A 1 20
2 A 2 10
3 A 2 20
4 B 3 30
5 B 3 40
注意多对多合并的陷阱
多对多合并会显著增加行数(笛卡尔积),大表操作时需格外注意内存消耗
合并前建议检查连接键的重复情况:df.duplicated(subset=['键']).sum()
如果不需要笛卡尔积,可以先对重复键做聚合去重再进行合并
# 检查连接键重复情况
print('左表组重复数:' , left_mm.duplicated(subset=['组' ]).sum())
print('右表组重复数:' , right_mm.duplicated(subset=['组' ]).sum())
# 先聚合再合并(避免笛卡尔积)
left_agg = left_mm.groupby('组' )['值_L' ].agg(list).reset_index()
right_agg = right_mm.groupby('组' )['值_R' ].agg(list).reset_index()
clean_result = pd.merge(left_agg, right_agg, on='组' )
print(clean_result)
七、索引合并与层级处理
实际工作中,很多DataFrame的索引承载着业务含义(如时间序列、分组标签)。Pandas提供了丰富的索引合并功能。
7.1 left_index / right_index 参数
merge 可以通过 left_index 和 right_index 参数指定索引作为连接键。
# 左表用列、右表用索引
df_left = pd.DataFrame({
'键' : ['a' , 'b' , 'c' ],
'值' : [1 , 2 , 3 ]
})
df_right_idx = pd.DataFrame({
'描述' : ['Alpha' , 'Beta' , 'Gamma' ]
}, index=['a' , 'b' , 'd' ])
# left_on + right_index 混合使用
mixed = pd.merge(
df_left, df_right_idx,
left_on='键' ,
right_index=True ,
how='left'
)
print(mixed)
键 值 描述
0 a 1 Alpha
1 b 2 Beta
2 c 3 NaN
7.2 多重索引合并
当索引是MultiIndex时,合并操作需要特别注意层级匹配。
# 多重索引合并
mi_left = pd.DataFrame({
'得分' : [95 , 88 , 76 ]
}, index=pd.MultiIndex.from_tuples([
('2024' , 'Q1' ),
('2024' , 'Q2' ),
('2024' , 'Q3' )
], names=['年' , '季' ]))
mi_right = pd.DataFrame({
'目标' : [90 , 90 , 85 ]
}, index=pd.MultiIndex.from_tuples([
('2024' , 'Q1' ),
('2024' , 'Q2' ),
('2024' , 'Q4' )
], names=['年' , '季' ]))
# 基于多重索引合并
mi_result = mi_left.join(mi_right, how='outer' )
print(mi_result)
得分 目标
年 季
2024 Q1 95.0 90.0
Q2 88.0 90.0
Q3 76.0 NaN
Q4 NaN 85.0
八、重叠列名处理高级技巧
除了 suffixes 参数外,还有几种方式处理合并后的重叠列名问题。
8.1 合并前重命名
# 方法1:合并前手动重命名
df_b_renamed = df_b.rename(columns={'备注' : '备注_B' })
result = pd.merge(df_a, df_b_renamed, on='ID' )
print(result.columns)
# 方法2:合并后重命名列
result = pd.merge(df_a, df_b, on='ID' , suffixes=('' , '_B' ))
print(result.columns)
8.2 选择性地保留列
# 只选择需要的列,避免冲突
df_b_subset = df_b[['ID' , '值' ]] # 排除'备注'列
result = pd.merge(df_a, df_b_subset, on='ID' )
print(result)
# 合并后删除不需要的重叠列
result = pd.merge(df_a, df_b, on='ID' )
result.drop(columns=['备注_y' ], inplace=True )
result.rename(columns={'备注_x' : '备注' }, inplace=True )
print(result)
九、合并效率优化
当处理大规模数据时,合并效率至关重要。以下是一些实用的优化技巧。
性能优化策略
内连接优先 :inner join 不需要处理NaN,计算量最小
减少数据量 :合并前先过滤不需要的行和列
设置索引 :对连接键设置索引可以加速合并
使用类别类型 :将字符串列转为category类型,减少内存占用
分块处理 :超大表可以分块读取后合并
# 策略1:合并前过滤
df2_filtered = df2[df2['薪资' ] > 10000 ]
result = pd.merge(df1, df2_filtered, on='员工ID' )
# 策略2:设置索引加速
df1_idx = df1.set_index('员工ID' )
df2_idx = df2.set_index('员工ID' )
result = df1_idx.join(df2_idx)
# 策略3:类别类型优化
df1['部门' ] = df1['部门' ].astype('category' )
# 策略4:大表分块合并
import numpy as np
chunks = np.array_split(large_df, 10 )
results = []
for chunk in chunks:
merged_chunk = pd.merge(chunk, lookup_table, on='key' )
results.append(merged_chunk)
final_result = pd.concat(results, ignore_index=True )
十、append()已弃用 vs concat
在早期版本的Pandas中,DataFrame.append() 是常用的行追加方法。但自 Pandas 1.4.0 起,append() 已被标记为弃用(deprecated),并在 Pandas 2.0 中正式移除。官方推荐统一使用 pd.concat()。
append() 已移除 — 不要在新代码中使用
如果你使用的是 Pandas 2.0+,直接调用 df.append() 会抛出 AttributeError。所有追加操作都应迁移到 pd.concat()。
# 旧写法(已移除,不要使用)
# df1.append(df2) # ❌ AttributeError
# df1.append([df2, df3]) # ❌ AttributeError
# 新写法(推荐)
result = pd.concat([df1, df2], ignore_index=True )
# 追加多个表
result = pd.concat([df1, df2, df3], ignore_index=True )
# 如果只需要追加单行(类似旧的 df.append(row))
new_row = pd.DataFrame([{'产品' : 'E' , '销量' : 300 , '月份' : 3 }])
result = pd.concat([q1, new_row], ignore_index=True )
print(result)
产品 销量 月份
0 A 100 1
1 B 200 1
2 C 150 1
3 E 300 3
concat 相比 append 的优势
性能更好 :concat 预先分配内存,append 逐次扩展导致多次复制
语义一致 :concat 统一了行追加和列拼接,API更简洁一致
功能更强 :concat 支持 keys、join 等高级参数
循环中效率 :在循环中反复 append 效率极低,应在循环外使用列表收集后用 concat
# 循环中高效追加的最佳实践
# ❌ 错误做法(每次循环都复制DataFrame)
result = pd.DataFrame()
for chunk in chunks:
result = pd.concat([result, chunk]) # 越来越慢!
# ✅ 正确做法(先收集再合并)
pieces = []
for chunk in chunks:
pieces.append(chunk)
result = pd.concat(pieces, ignore_index=True )
十一、综合案例
下面通过一个完整的电商数据分析案例,综合运用 merge、join 和 concat 三种合并方式。
案例:电商订单数据分析
场景: 分析某电商平台2025年1-3月的订单数据,计算各品类销售额排名和环比增长。
# ========== 数据准备 ==========
import pandas as pd
import numpy as np
# 1. 用户信息表
users = pd.DataFrame({
'user_id' : [1 , 2 , 3 , 4 ],
'name' : ['Alice' , 'Bob' , 'Charlie' , 'David' ],
'city' : ['北京' , '上海' , '广州' , '深圳' ]
})
# 2. 商品信息表
products = pd.DataFrame({
'product_id' : ['P01' , 'P02' , 'P03' , 'P04' ],
'product_name' : ['手机' , '电脑' , '耳机' , '键盘' ],
'category' : ['数码' , '数码' , '配件' , '配件' ],
'price' : [5999 , 8999 , 399 , 299 ]
})
# 3. 各月订单数据
orders_jan = pd.DataFrame({
'order_id' : [1001 , 1002 , 1003 ],
'user_id' : [1 , 2 , 1 ],
'product_id' : ['P01' , 'P02' , 'P03' ],
'quantity' : [2 , 1 , 3 ],
'month' : ['2025-01' ] * 3
})
orders_feb = pd.DataFrame({
'order_id' : [1004 , 1005 , 1006 , 1007 ],
'user_id' : [2 , 3 , 4 , 1 ],
'product_id' : ['P01' , 'P03' , 'P04' , 'P02' ],
'quantity' : [1 , 2 , 1 , 1 ],
'month' : ['2025-02' ] * 4
})
orders_mar = pd.DataFrame({
'order_id' : [1008 , 1009 ],
'user_id' : [3 , 1 ],
'product_id' : ['P02' , 'P01' ],
'quantity' : [1 , 2 ],
'month' : ['2025-03' ] * 2
})
# ========== 第一步:合并所有订单 ==========
all_orders = pd.concat(
[orders_jan, orders_feb, orders_mar],
ignore_index=True
)
print('所有订单:' )
print(all_orders)
# ========== 第二步:关联用户信息 ==========
orders_with_user = pd.merge(
all_orders, users,
on='user_id' ,
how='left'
)
print('\n关联用户信息:' )
print(orders_with_user[['order_id' , 'name' , 'city' , 'product_id' , 'quantity' ]])
# ========== 第三步:关联商品信息 ==========
orders_full = pd.merge(
orders_with_user, products,
on='product_id' ,
how='left'
)
# 计算订单金额
orders_full['amount' ] = orders_full['quantity' ] * orders_full['price' ]
print('\n完整订单明细(含金额):' )
print(orders_full[['order_id' , 'name' , 'product_name' ,
'category' , 'quantity' , 'amount' , 'month' ]])
# ========== 第四步:各品类月度销售额 ==========
category_monthly = orders_full.groupby(
['category' , 'month' ]
)['amount' ].sum().reset_index()
print('\n各品类月度销售额:' )
print(category_monthly)
# ========== 第五步:计算环比增长 ==========
# 将各品类数据拆开计算环比
categories = category_monthly['category' ].unique()
growth_list = []
for cat in categories:
cat_data = category_monthly[category_monthly['category' ] == cat]
cat_data = cat_data.set_index('month' )
cat_data['growth' ] = cat_data['amount' ].pct_change() * 100
growth_list.append(cat_data.reset_index())
# 用concat合并所有品类的环比数据
growth_result = pd.concat(growth_list, ignore_index=True )
print('\n各品类销售额环比增长(%):' )
print(growth_result)
所有订单:
order_id user_id product_id quantity month
0 1001 1 P01 2 2025-01
1 1002 2 P02 1 2025-01
2 1003 1 P03 3 2025-01
3 1004 2 P01 1 2025-02
4 1005 3 P03 2 2025-02
5 1006 4 P04 1 2025-02
6 1007 1 P02 1 2025-02
7 1008 3 P02 1 2025-03
8 1009 1 P01 2 2025-03
关联用户信息:
order_id name city product_id quantity
0 1001 Alice 北京 P01 2
1 1002 Bob 上海 P02 1
2 1003 Alice 北京 P03 3
3 1004 Bob 上海 P01 1
4 1005 Charlie 广州 P03 2
5 1006 David 深圳 P04 1
6 1007 Alice 北京 P02 1
7 1008 Charlie 广州 P02 1
8 1009 Alice 北京 P01 2
完整订单明细(含金额):
order_id name product_name category quantity amount month
0 1001 Alice 手机 数码 2 11998 2025-01
1 1002 Bob 电脑 数码 1 8999 2025-01
2 1003 Alice 耳机 配件 3 1197 2025-01
3 1004 Bob 手机 数码 1 5999 2025-02
4 1005 Charlie 耳机 配件 2 798 2025-02
5 1006 David 键盘 配件 1 299 2025-02
6 1007 Alice 电脑 数码 1 8999 2025-02
7 1008 Charlie 电脑 数码 1 8999 2025-03
8 1009 Alice 手机 数码 2 11998 2025-03
各品类月度销售额:
category month amount
0 配件 2025-01 1197
1 配件 2025-02 1097
2 数码 2025-01 20997
3 数码 2025-02 14998
4 数码 2025-03 20997
各品类销售额环比增长(%):
category month amount growth
0 配件 2025-01 1197 NaN
1 配件 2025-02 1097 -8.3542
2 数码 2025-01 20997 NaN
3 数码 2025-02 14998 -28.5707
4 数码 2025-03 20997 40.0000
十二、常见错误与排查
常见问题及解决方案
KeyError/列名不存在 :检查列名是否拼写正确,使用 df.columns.tolist() 查看所有列
合并后行数异常增多 :连接键存在重复值导致笛卡尔积,检查重复情况
意外出现NaN :确认连接方式是否正确,检查键值是否完全匹配
MemoryError :数据量过大,用分块处理或先过滤再合并
数据类型不一致 :int列与float列合并可能引起类型提升,确保类型一致
# 排查工具函数
def inspect_merge (left, right, key):
"""检查合并前的数据质量"""
print(f'左表行数: {len(left)}, 右表行数: {len(right)}' )
print(f'左表键唯一值数: {left[key].nunique()}' )
print(f'右表键唯一值数: {right[key].nunique()}' )
print(f'左表键重复行: {left.duplicated(subset=[key]).sum()}' )
print(f'右表键重复行: {right.duplicated(subset=[key]).sum()}' )
# 检查键值类型是否一致
print(f'左表键类型: {left[key].dtype}' )
print(f'右表键类型: {right[key].dtype}' )
# 检查键值交集
common_keys = set(left[key]) & set(right[key])
print(f'共同键值数: {len(common_keys)}' )
核心要点总结
merge 是核心 :pd.merge() 是Pandas中最灵活、最常用的合并函数,支持 inner/left/right/outer 四种连接方式,与SQL JOIN完全对应
join 是 merge 的语法糖 :df.join() 基于索引连接,语法简洁,适合索引对齐的场景,且支持一次合并多个DataFrame
concat 是纯拼接 :pd.concat() 沿轴方向拼接数据(axis=0增加行,axis=1增加列),不依赖键匹配,支持ignore_index和keys参数
多键合并 :通过传递列名列表实现多键连接,有效避免笛卡尔积
重叠列名处理 :使用 suffixes 参数自定义后缀,或在合并前后进行重命名
append 已移除 :Pandas 2.0+ 已移除 append(),统一使用 pd.concat() 替代
效率优化 :内连接优先、先过滤后合并、设置索引、使用category类型、避免循环中反复concat
多对多陷阱 :连接键重复会产生笛卡尔积,合并前务必检查重复情况
十三、进一步思考与实践
掌握Pandas的合并操作是数据分析进阶的重要里程碑。在实践中,建议从以下几个方向继续深入:
进阶方向
SQL vs Pandas对照学习 :将SQL JOIN语句与Pandas merge对照理解,加深对连接语义的把握
掌握 reduce 批量合并 :functools.reduce 结合 merge 实现多个DataFrame的连续合并
理解数据库范式 :学习数据库范式设计,理解为何数据需要拆分存储、合并使用
pandas.merge_asof :学习时间序列最近匹配合并,适用于事件时间对齐场景
并行处理 :大规模数据合并可考虑 Dask、Polars 等并行计算框架
# functools.reduce 批量合并
from functools import reduce
dfs = [df1, df2, df3, df4]
result = reduce(
lambda left, right: pd.merge(left, right, on='key' , how='outer' ),
dfs
)
# merge_asof:时间序列最近匹配
# 适用于将交易事件匹配到最近的行情数据
trades = pd.DataFrame({
'time' : pd.to_datetime(['2025-01-01 09:30:05' ,
'2025-01-01 09:30:15' ]),
'price' : [100.5 , 101.0 ]
})
quotes = pd.DataFrame({
'time' : pd.to_datetime(['2025-01-01 09:30:00' ,
'2025-01-01 09:30:10' ,
'2025-01-01 09:30:20' ]),
'bid' : [100.0 , 100.3 , 100.8 ],
'ask' : [100.6 , 100.9 , 101.2 ]
})
matched = pd.merge_asof(trades, quotes, on='time' , direction='backward' )
print(matched)
time price time bid ask
0 2025-01-01 09:30:05 100.5 2025-01-01 09:30:00 100.0 100.6
1 2025-01-01 09:30:15 101.0 2025-01-01 09:30:10 100.3 100.9
本文全面总结了Pandas中merge、join、concat三大合并操作的核心用法、参数详解和实战技巧
本学习笔记为本人学习资料,不得转载
环境要求: Python 3.8+ / Pandas 1.4+ (推荐 Pandas 2.0+)