承接《Pandas 重复数据处理》,本篇系统讲解 Pandas 数据合并:
merge、concat、join、append(废弃),对比语法、连接类型、对齐逻辑、多键合并、合并后冲突处理、实战场景,是多表关联、数据拼接的核心章节,保持系列教程统一风格。
前置导入与示例数据
import pandas as pd
import numpy as np
df_left = pd.DataFrame({
"user_id": [1,2,3,4,5],
"name": ["张三","李四","王五","赵六","孙七"],
"city": ["杭州","上海","杭州","北京","广州"]
})
df_right = pd.DataFrame({
"user_id": [2,3,4,6,7],
"salary": [12000,15000,11000,9000,13000],
"level": ["B","A","C","D","B"]
})
print("左表:")
print(df_left)
print("右表:")
print(df_right)
一、四个方法一句话区分
pd.merge():按关键字段关联(类似SQL join),支持内连接、左连接、右连接、全外连接,最常用pd.concat():按行或者按列直接拼接,依靠索引对齐,不需要关联键,适合结构相同的表堆叠df.join():简化版merge,默认基于索引关联df.append():Pandas 2.0+ 已经移除,改用pd.concat
二、pd.merge 详解(SQL式关联)
基础语法
pd.merge(
left,
right,
how="inner",
on=None,
left_on=None,
right_on=None,
left_index=False,
right_index=False,
suffixes=("_x","_y"),
indicator=False
)
2.1 内连接 how=”inner”(默认)
只保留两边匹配上的记录
df_inner = pd.merge(df_left, df_right, on="user_id", how="inner")
print("内连接:")
print(df_inner)
2.2 左连接 how=”left”(业务最高频)
保留左表全部,右表匹配不到的填充NaN
df_left_join = pd.merge(df_left, df_right, on="user_id", how="left")
print("左连接:")
print(df_left_join)
2.3 右连接 how=”right”
保留右表全部,左表匹配不到填充NaN
df_right_join = pd.merge(df_left, df_right, on="user_id", how="right")
2.4 全外连接 how=”outer”
两边所有数据都保留,匹配不上的填充NaN
df_outer = pd.merge(df_left, df_right, on="user_id", how="outer")
2.5 关联键名字不一样:left_on / right_on
左表id叫uid,右表叫user_id
df_a = pd.DataFrame({"uid":[1,2,3],"val":[10,20,30]})
df_b = pd.DataFrame({"user_id":[2,3,4],"score":[80,90,70]})
res = pd.merge(df_a, df_b, left_on="uid", right_on="user_id", how="left")
2.6 多字段联合关联(复合主键)
df_m = pd.merge(df_left, df_right, on=["user_id","city"], how="left")
2.7 suffixes 重名字段后缀
两个表存在相同字段名,自动加后缀区分,默认 _x、_y
pd.merge(df_left, df_right, on="user_id", suffixes=("_L","_R"))
2.8 indicator 标记数据来源
增加 _merge 列,标记这条数据来自哪边:both / left_only / right_only
df_mark = pd.merge(df_left, df_right, on="user_id", how="outer", indicator=True)
print(df_mark["_merge"].value_counts())
三、pd.concat 拼接(堆叠)
concat 不做匹配,按索引对齐,分纵向拼接(axis=0,行堆叠)、横向拼接(axis=1,列拼接)
# 构造两个结构相同的表
df1 = pd.DataFrame({"id":[1,2],"v":[10,20]})
df2 = pd.DataFrame({"id":[3,4],"v":[30,40]})
3.1 纵向拼接 axis=0(合并多张同结构报表)
df_concat_row = pd.concat([df1, df2], axis=0, ignore_index=True)
ignore_index=True:重新生成连续索引,避免索引重复混乱
3.2 横向拼接 axis=1(按索引并排)
df_concat_col = pd.concat([df1, df2], axis=1)
横向concat依赖索引对齐,不是按某一列匹配,和merge完全不一样!
3.3 join 参数(concat的join)
join="outer"默认:索引取并集join="inner":索引取交集,只保留两边索引都存在的行
pd.concat([df1, df2], axis=1, join="inner")
四、df.join() 索引关联
join 本质是 merge 的简写,默认使用索引关联
df_left_idx = df_left.set_index("user_id")
df_right_idx = df_right.set_index("user_id")
res_join = df_left_idx.join(df_right_idx, how="left")
适合:已经把关联键设为索引的场景。
五、merge、concat 选型对比
| 方法 | 对齐依据 | 用途 | 类比SQL |
|---|---|---|---|
| merge | 指定字段(on/left_on) | 多表按主键关联 | JOIN(INNER/LEFT/RIGHT/FULL) |
| concat axis=0 | 索引 | 多张相同结构表上下堆叠 | UNION ALL |
| concat axis=1 | 索引 | 按索引并排增加列 | 索引对齐横向拼接 |
| join | 索引 | 索引上的左关联 | 索引LEFT JOIN |
六、实战高频场景
场景1:合并多张月度报表(concat)
df_jan = pd.read_csv("sale_jan.csv")
df_feb = pd.read_csv("sale_feb.csv")
df_all = pd.concat([df_jan, df_feb], ignore_index=True)
场景2:订单主表关联用户维度表(merge左连接,最常用)
order_df = pd.DataFrame({"order_id":[101,102,103],"user_id":[1,2,3],"amount":[200,300,150]})
user_df = pd.DataFrame({"user_id":[1,2,4],"name":["张三","李四","老王"]})
order_with_user = pd.merge(order_df, user_df, on="user_id", how="left")
七、合并后常见配套操作
- 查看关联匹配情况:
indicator=True+ value_counts - 去重:合并后容易产生笛卡尔积重复,配合
drop_duplicates - 缺失值填充:左连接未匹配成功会出现NaN,
fillna - 笛卡尔积:关联键不唯一时,行数爆炸,是大坑!
八、高频坑清单
- concat(axis=1) 误以为是按列值匹配,实际是按索引对齐,数据错乱
- merge 关联键存在重复值,触发笛卡尔积,行数暴涨
- 忘记
ignore_index,concat后索引重复,筛选、合并出错 - 重名字段没有设置suffixes,列被覆盖混淆
- 数据类型不一致(int user_id 和 str user_id),匹配不上,全部NaN
- append 已经被移除,老代码直接报错,替换成concat
- 大表merge性能差:优先把关联键设为索引、排序
九、综合实战模板
def merge_left_table(left_df, right_df, join_keys):
"""左连接模板,业务最常用"""
return pd.merge(
left_df,
right_df,
on=join_keys,
how="left",
suffixes=("_L","_R")
)
def concat_stack(df_list):
"""多张同结构表纵向堆叠"""
return pd.concat(df_list, axis=0, ignore_index=True)
# 调用示例
res_merge = merge_left_table(df_left, df_right, ["user_id"])
print(res_merge)
十、速查表
| 需求 | 推荐写法 |
|---|---|
| 按主键关联两张表,保留主表全部 | merge how=”left” |
| 取两边交集匹配数据 | merge how=”inner” |
| 合并多张结构一样的报表 | concat axis=0, ignore_index=True |
| 基于索引横向并排 | concat axis=1 / df.join |
| 查看哪些记录匹配失败 | merge(indicator=True) |
0 条笔记