pandas 提供了多种表合并方式:merge(类 sql 的 join)、join(按索引合并)、concat(轴向拼接)、combine(按规则合并)等。
pd.merge()— 最通用的合并
基本用法
import pandas as pd
left = pd.dataframe({
'key': ['a', 'b', 'c', 'd'],
'value_left': [1, 2, 3, 4]
})
right = pd.dataframe({
'key': ['b', 'c', 'd', 'e'],
'value_right': [20, 30, 40, 50]
})
# 默认内连接(inner join)—— 取交集
result = pd.merge(left, right, on='key')
# key value_left value_right
# b 2 20
# c 3 30
# d 4 40
how参数 — 四种连接方式
# 内连接:只保留两表都存在的 key pd.merge(left, right, on='key', how='inner') # 左连接:保留左表所有行 pd.merge(left, right, on='key', how='left') # 右连接:保留右表所有行 pd.merge(left, right, on='key', how='right') # 外连接:保留两表所有行(并集) pd.merge(left, right, on='key', how='outer') # 交叉连接:笛卡尔积(pandas 1.2+) pd.merge(left, right, how='cross')
| how | 行为 | sql 等价 |
|---|---|---|
| 'inner' | 取交集(默认) | inner join |
| 'left' | 保留左表全部 | left join |
| 'right' | 保留右表全部 | right join |
| 'outer' | 保留两表全部(并集) | full outer join |
| 'cross' | 笛卡尔积 | cross join |
多列连接
left = pd.dataframe({
'部门': ['技术', '技术', '销售'],
'城市': ['北京', '上海', '北京'],
'员工': ['张三', '李四', '王五']
})
right = pd.dataframe({
'部门': ['技术', '技术', '销售'],
'城市': ['北京', '深圳', '北京'],
'评分': [95, 88, 76]
})
# 单列连接
pd.merge(left, right, on='部门') # 会产生重复
# 多列连接(精确匹配)
pd.merge(left, right, on=['部门', '城市'], how='inner')
不同列名连接
left = pd.dataframe({
'emp_id': [1, 2, 3],
'name': ['张三', '李四', '王五']
})
right = pd.dataframe({
'employee_id': [1, 2, 4],
'salary': [8000, 9000, 10000]
})
# 使用 left_on / right_on
result = pd.merge(left, right,
left_on='emp_id', right_on='employee_id')
indicator— 追踪来源
result = pd.merge(left, right, on='key', how='outer', indicator=true)
# 新增 _merge 列: 'both' / 'left_only' / 'right_only'
# 自定义指示列名
result = pd.merge(left, right, on='key', how='outer',
indicator='来源')
# 快速查看哪些 key 只在一侧
print(result['来源'].value_counts())
suffixes— 重名列后缀
left = pd.dataframe({'key': [1, 2], 'value': [10, 20]})
right = pd.dataframe({'key': [1, 2], 'value': [100, 200]})
result = pd.merge(left, right, on='key',
suffixes=('_左', '_右'))
# key value_左 value_右
dataframe.join()— 按索引合并
left = pd.dataframe(
{'a': [1, 2, 3]},
index=['a', 'b', 'c']
)
right = pd.dataframe(
{'b': [10, 20, 30]},
index=['a', 'b', 'd']
)
# 按索引左连接
left.join(right, how='left')
# 按索引外连接
left.join(right, how='outer')
# 左表用列,右表用索引
left_with_key = pd.dataframe({
'key': ['a', 'b', 'c'],
'a': [1, 2, 3]
})
left_with_key.join(right.set_index('...'), on='key') # 注意:left 不能有重复 key
# 一次 join 多个 dataframe
right2 = pd.dataframe({'c': [100, 200]}, index=['a', 'c'])
left.join([right, right2], how='inner')
pd.concat()— 轴向拼接
纵向拼接(axis=0,默认)
df1 = pd.dataframe({'a': [1, 2], 'b': [3, 4]}, index=['a', 'b'])
df2 = pd.dataframe({'a': [5, 6], 'b': [7, 8]}, index=['c', 'd'])
# 纵向拼接
result = pd.concat([df1, df2])
# axis=0(默认)
# 保留原索引
result = pd.concat([df1, df2], ignore_index=true) # 重建 0..n-1 索引
# 添加分组键(生成 multiindex)
result = pd.concat([df1, df2], keys=['第一组', '第二组'])
# 可通过 result.loc['第一组'] 取回原数据
# 添加分组键为列
result = pd.concat([df1, df2], keys=['第一组', '第二组'],
names=['组别', '原索引'])
横向拼接(axis=1)
df1 = pd.dataframe({'a': [1, 2], 'b': [3, 4]})
df2 = pd.dataframe({'c': [5, 6], 'd': [7, 8]})
result = pd.concat([df1, df2], axis=1)
join参数控制列对齐
df1 = pd.dataframe({'a': [1, 2]}, index=['a', 'b'])
df2 = pd.dataframe({'b': [3, 4]}, index=['b', 'c'])
pd.concat([df1, df2], axis=1, join='outer') # 取并集(默认)
pd.concat([df1, df2], axis=1, join='inner') # 取交集
ignore_indexvsverify_integrity
# ignore_index: 忽略原索引,重建 0..n-1 pd.concat([df1, df2], ignore_index=true) # verify_integrity: 检查索引是否有重复 pd.concat([df1, df1], verify_integrity=true) # 抛出 valueerror
pd.merge_asof()— 近似匹配连接
按最近(或最近且≤ / ≥)的键匹配,常用于时间序列对齐。
trades = pd.dataframe({
'time': pd.to_datetime(['09:30:01', '10:15:30', '14:45:00']),
'price': [100.5, 101.2, 99.8]
})
quotes = pd.dataframe({
'time': pd.to_datetime(['09:30:00', '10:00:00', '14:00:00', '15:00:00']),
'bid': [100.0, 101.0, 99.5, 100.5],
'ask': [101.0, 102.0, 100.5, 101.5]
})
# 匹配最近的前一个报价
result = pd.merge_asof(trades, quotes, on='time')
# direction 参数
# 'backward' (默认): 取 quotes.time <= trades.time 的最后一行
# 'forward': 取 quotes.time >= trades.time 的第一行
# 'nearest': 取时间差的绝对值最小的行
result_fwd = pd.merge_asof(trades, quotes, on='time', direction='forward')
# tolerance: 限制最大时间差
result_tol = pd.merge_asof(trades, quotes, on='time',
tolerance=pd.timedelta('5min'))
pd.merge_ordered()— 有序合并
类似 merge 但会对结果排序,并支持前向填充。
a = pd.dataframe({'k': [1, 3, 5], 'a_val': [10, 30, 50]})
b = pd.dataframe({'k': [2, 3, 4], 'b_val': [20, 30, 40]})
result = pd.merge_ordered(a, b, on='k')
# 结果按 k 排序,缺失值 nan
# 前向填充
result = pd.merge_ordered(a, b, on='k', fill_method='ffill')
combine/combine_first— 按元素值规则合并
df1 = pd.dataframe({'a': [1, np.nan, 3], 'b': [4, 5, np.nan]})
df2 = pd.dataframe({'a': [10, 20, 30], 'b': [40, np.nan, 60]})
# combine_first: 用 df2 填充 df1 的缺失值
result = df1.combine_first(df2)
# df1 有值就用 df1,nan 就用 df2
# combine: 自定义合并规则
result = df1.combine(df2, lambda s1, s2: np.where(s1 > s2, s1, s2))
# 取每个位置较大的值
result = df1.combine(df2, np.maximum) # 等价写法
方法对比
| 方法 | 合并依据 | 主要参数 | 适用场景 |
|---|---|---|---|
| pd.merge() | 列值 | on, how, left_on, right_on | 通用表关联 |
| df.join() | 索引 | on (可选), how | 索引对齐 |
| pd.concat() | 轴 | axis, join, keys | 行/列拼接 |
| pd.merge_asof() | 最近匹配 | on, direction, tolerance | 时间序列对齐 |
| df.combine_first() | 元素级 | — | 填补缺失值 |
| df.combine() | 元素级 | func | 自定义合并规则 |
总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持代码网。
发表评论