Pandas 数据合并
问题
Pandas 中如何实现多表合并?merge、join、concat 有什么区别?
答案
多表合并是数据分析中的高频操作,等价于 SQL 的 JOIN。面试中常要求"用 Pandas 实现 LEFT JOIN"或对比三种合并方式的区别。
三种合并方式对比
| 方式 | 等价 SQL | 方向 | 适用场景 |
|---|---|---|---|
pd.merge() | JOIN | 横向(按列) | 两表按键关联 |
df.join() | JOIN(按索引) | 横向(按索引) | 按索引合并 |
pd.concat() | UNION ALL | 纵向/横向 | 多表堆叠 |
merge — 按键合并(最常用)
merge 基本用法
import pandas as pd
orders = pd.DataFrame({
'order_id': [1, 2, 3, 4],
'user_id': [101, 102, 103, 104],
'amount': [100, 200, 150, 300]
})
users = pd.DataFrame({
'user_id': [101, 102, 103, 105],
'name': ['Alice', 'Bob', 'Charlie', 'Eve'],
'city': ['北京', '上海', '广州', '深圳']
})
# INNER JOIN(默认)—— 只保留两表都有的
pd.merge(orders, users, on='user_id', how='inner')
# LEFT JOIN —— 保留左表全部
pd.merge(orders, users, on='user_id', how='left')
# RIGHT JOIN —— 保留右表全部
pd.merge(orders, users, on='user_id', how='right')
# FULL OUTER JOIN —— 保留两表全部
pd.merge(orders, users, on='user_id', how='outer')
关联键不同名
关联键不同名
# 左表列名和右表列名不同
orders = orders.rename(columns={'user_id': 'uid'})
pd.merge(orders, users, left_on='uid', right_on='user_id', how='left')
多键关联
多键合并
# 按多个键关联(等价于 SQL 的 ON a.col1 = b.col1 AND a.col2 = b.col2)
pd.merge(df1, df2, on=['city', 'date'], how='left')
处理重名列
suffix 处理重名列
# 两表有同名列时,自动添加后缀
result = pd.merge(df1, df2, on='user_id', suffixes=('_left', '_right'))
# 结果中会有 amount_left, amount_right
merge 的 indicator 参数
indicator 查看匹配情况
result = pd.merge(orders, users, on='user_id', how='outer', indicator=True)
# _merge 列显示每行来源:'both' / 'left_only' / 'right_only'
# 找出左表独有的行(类似 SQL: WHERE b.key IS NULL)
left_only = result[result['_merge'] == 'left_only']
concat — 纵向/横向堆叠
concat 用法
df1 = pd.DataFrame({'name': ['Alice', 'Bob'], 'age': [25, 30]})
df2 = pd.DataFrame({'name': ['Charlie', 'David'], 'age': [35, 28]})
# 纵向堆叠(UNION ALL)
pd.concat([df1, df2], ignore_index=True)
# 横向拼接
pd.concat([df1, df2], axis=1)
# 多个 DataFrame 堆叠
dfs = [df1, df2, df3]
pd.concat(dfs, ignore_index=True)
concat 注意事项
- 纵向堆叠时,列名不一致的列会用 NaN 填充
- 务必加
ignore_index=True重置索引,否则索引会重复 concat不会自动去重,如需去重,后续调用drop_duplicates()
Pandas vs SQL JOIN 对照
| SQL | Pandas |
|---|---|
SELECT * FROM a INNER JOIN b ON a.id = b.id | pd.merge(a, b, on='id', how='inner') |
SELECT * FROM a LEFT JOIN b ON a.id = b.id | pd.merge(a, b, on='id', how='left') |
SELECT * FROM a LEFT JOIN b ON a.id = b.id WHERE b.id IS NULL | pd.merge(a, b, on='id', how='left', indicator=True).query('_merge == "left_only"') |
SELECT * FROM a UNION ALL SELECT * FROM b | pd.concat([a, b], ignore_index=True) |
SELECT * FROM a CROSS JOIN b | a.merge(b, how='cross') |
实战:多表关联分析
电商订单分析:三表关联
# 订单表
orders = pd.DataFrame({
'order_id': [1, 2, 3],
'user_id': [101, 102, 101],
'product_id': ['P1', 'P2', 'P1'],
'amount': [100, 200, 100]
})
# 用户表
users = pd.DataFrame({
'user_id': [101, 102, 103],
'name': ['Alice', 'Bob', 'Charlie'],
'level': ['VIP', '普通', 'VIP']
})
# 商品表
products = pd.DataFrame({
'product_id': ['P1', 'P2', 'P3'],
'product_name': ['手机', '电脑', '耳机'],
'category': ['电子', '电子', '配件']
})
# 三表关联
result = (
orders
.merge(users, on='user_id', how='left')
.merge(products, on='product_id', how='left')
)
# 按用户等级统计订单金额
result.groupby('level').agg(
order_count=('order_id', 'count'),
total_amount=('amount', 'sum'),
avg_amount=('amount', 'mean')
).reset_index()
常见面试问题
Q1: merge 的 how 参数有哪些选项?
答案:
| how | 说明 | 等价 SQL |
|---|---|---|
'inner' | 只保留两表都有的键 | INNER JOIN |
'left' | 保留左表全部 | LEFT JOIN |
'right' | 保留右表全部 | RIGHT JOIN |
'outer' | 保留两表全部 | FULL OUTER JOIN |
'cross' | 笛卡尔积 | CROSS JOIN |
Q2: merge、join、concat 怎么选?
答案:
- merge:按列值关联两张表,最常用,等价 SQL JOIN
- join:按索引关联,是 merge 的简写,适合索引已设置好的场景
- concat:纵向堆叠多张表(UNION ALL),或横向拼接(axis=1)
- 实际工作中 90% 用 merge,10% 用 concat
Q3: 如何用 Pandas 实现 SQL 的 ANTI JOIN?
答案:
ANTI JOIN = 左表有但右表没有的行:
# 方式一:indicator + 筛选
result = pd.merge(df1, df2, on='key', how='left', indicator=True)
anti_join = result[result['_merge'] == 'left_only'].drop(columns='_merge')
# 方式二:isin 取反
anti_join = df1[~df1['key'].isin(df2['key'])]
Q4: 大数据量 merge 有什么优化技巧?
答案:
- 确保 key 类型一致:int 和 str 类型的 key merge 会非常慢
- 先筛选再合并:减少参与 merge 的行数
- 设置索引:
df.set_index('key')后用 join 更快 - 使用 Polars:大数据量 JOIN 比 Pandas 快 5~20 倍