跳到主要内容

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 对照

SQLPandas
SELECT * FROM a INNER JOIN b ON a.id = b.idpd.merge(a, b, on='id', how='inner')
SELECT * FROM a LEFT JOIN b ON a.id = b.idpd.merge(a, b, on='id', how='left')
SELECT * FROM a LEFT JOIN b ON a.id = b.id WHERE b.id IS NULLpd.merge(a, b, on='id', how='left', indicator=True).query('_merge == "left_only"')
SELECT * FROM a UNION ALL SELECT * FROM bpd.concat([a, b], ignore_index=True)
SELECT * FROM a CROSS JOIN ba.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 有什么优化技巧?

答案

  1. 确保 key 类型一致:int 和 str 类型的 key merge 会非常慢
  2. 先筛选再合并:减少参与 merge 的行数
  3. 设置索引df.set_index('key') 后用 join 更快
  4. 使用 Polars:大数据量 JOIN 比 Pandas 快 5~20 倍

相关链接