在数据分析的世界里,我们常常需要把不同来源的数据“组合”起来,比如把用户信息表和订单表合并分析,或者把不同月份的销售数据拼接成一个大表。pandas 提供了 merge 和 concat 两个超级实用的工具,能帮我们轻松完成这些操作。今天我们就用最简单的方式,一步步掌握这两个基础操作,新手也能快速上手!
一、为什么需要数据合并?¶
想象一下,你有两个表格:一个是“学生基本信息表”(包含姓名、年龄),另一个是“学生成绩表”(包含姓名、科目、分数)。如果想分析每个学生的成绩和年龄,就需要把这两个表“合并”起来。pandas 的 merge 和 concat 就是干这个的!
二、concat:简单拼接,适合“加行”或“加列”¶
concat 就像“把两张纸上下或左右粘在一起”,不需要关联键,直接按顺序拼接。
1. 基本语法¶
pd.concat(objs, axis=0, ignore_index=False)
objs:要拼接的 DataFrame 列表(比如[df1, df2])axis=0:默认“上下拼接”(行方向);axis=1:“左右拼接”(列方向)ignore_index=True:重置索引(避免拼接后索引重复,新手必看!)
2. 行方向拼接(上下拼接)¶
场景:把两个结构相同的表按行合并(比如不同月份的销售数据)。
示例:
import pandas as pd
# 创建两个简单的 DataFrame
df1 = pd.DataFrame({
'姓名': ['张三', '李四'],
'年龄': [20, 21]
})
df2 = pd.DataFrame({
'姓名': ['王五', '赵六'],
'年龄': [22, 23]
})
# 上下拼接(默认 axis=0)
result_row = pd.concat([df1, df2])
print("不重置索引的拼接结果:")
print(result_row)
# 重置索引(推荐!避免索引重复)
result_row_reset = pd.concat([df1, df2], ignore_index=True)
print("\n重置索引后的拼接结果:")
print(result_row_reset)
输出对比:
- 不重置索引时,结果会保留原索引(0,1,0,1)
- 重置索引后,索引会变成 0,1,2,3(更清晰)
3. 列方向拼接(左右拼接)¶
场景:把两个结构不同但有共同“行标识”的表按列合并(比如学生信息表+成绩表)。
示例:
# 新增一个成绩表
score = pd.DataFrame({
'科目': ['数学', '语文'],
'分数': [90, 85]
})
# 左右拼接(axis=1)
result_col = pd.concat([df1, score], axis=1)
print("\n列方向拼接结果:")
print(result_col)
输出:
姓名 年龄 科目 分数
0 张三 20 数学 90
1 李四 21 语文 85
(注意:这里 df1 和 score 行数不同,拼接后会自动补 NaN 吗?不会! 因为列拼接要求行数必须相同,否则会报错。如果行数不同,会用 NaN 填充吗?测试发现,如果行数不同,比如 df1 有 2 行,score 有 2 行才可以。如果行数不同,会报错。所以列拼接通常要求行数量一致。)
三、merge:基于“键”合并,像 SQL JOIN¶
merge 就像“按身份证号匹配两个人的信息”,需要共同的“键”(比如姓名、ID)来关联,类似 SQL 的 JOIN 操作。
1. 基本语法¶
pd.merge(left, right, on=None, how='inner', left_on=None, right_on=None)
left/right:要合并的两个 DataFrameon:共同的列名(如果两个表的键名相同)how:合并方式(inner/left/right/outer,默认inner)left_on/right_on:如果键名不同,用这两个参数分别指定(比如左表用id,右表用student_id)
2. 核心合并方式(以“学生表”和“成绩表”为例)¶
假设我们有:
- 学生表(student):包含 student_id(学号)、姓名、年龄
- 成绩表(score):包含 student_id(学号)、科目、分数
# 创建示例数据
student = pd.DataFrame({
'student_id': [1, 2, 3],
'姓名': ['张三', '李四', '王五'],
'年龄': [20, 21, 22]
})
score = pd.DataFrame({
'student_id': [1, 2, 4], # 注意:4号学生在学生表中不存在
'科目': ['数学', '语文', '英语'],
'分数': [90, 85, 95]
})
(1)内连接(how='inner',默认)¶
效果:只保留两个表都有的“共同键”(student_id)对应的行。
inner_merge = pd.merge(student, score, on='student_id')
print("内连接结果:")
print(inner_merge)
输出:
student_id 姓名 年龄 科目 分数
0 1 张三 20 数学 90
1 2 李四 21 语文 85
- 结果只保留
student_id=1和2(学生表中 3 号、成绩表中 4 号被排除)
(2)左连接(how='left')¶
效果:保留左表(student)的所有行,右表(score)没有匹配的用 NaN 填充。
left_merge = pd.merge(student, score, on='student_id', how='left')
print("\n左连接结果:")
print(left_merge)
输出:
student_id 姓名 年龄 科目 分数
0 1 张三 20 数学 90.0
1 2 李四 21 语文 85.0
2 3 王五 22 NaN NaN
- 学生表的 3 号学生在成绩表中没有数据,所以科目和分数列填
NaN
(3)右连接(how='right')¶
效果:保留右表(score)的所有行,左表(student)没有匹配的用 NaN 填充。
right_merge = pd.merge(student, score, on='student_id', how='right')
print("\n右连接结果:")
print(right_merge)
输出:
student_id 姓名 年龄 科目 分数
0 1 张三 20.0 数学 90
1 2 李四 21.0 语文 85
2 4 NaN NaN 英语 95
- 成绩表的 4 号学生在学生表中没有数据,所以姓名和年龄列填
NaN
(4)外连接(how='outer')¶
效果:保留两个表的所有行,没有匹配的都用 NaN 填充。
outer_merge = pd.merge(student, score, on='student_id', how='outer')
print("\n外连接结果:")
print(outer_merge)
输出:
student_id 姓名 年龄 科目 分数
0 1 张三 20.0 数学 90.0
1 2 李四 21.0 语文 85.0
2 3 王五 22.0 NaN NaN
3 4 NaN NaN 英语 95.0
(5)键名不同时怎么办?¶
如果两个表的键名不一样(比如左表用 id,右表用 user_id),需要用 left_on 和 right_on 指定。
# 假设学生表的键名是 id,成绩表的键名是 student_id
student_renamed = student.rename(columns={'student_id': 'id'})
score_renamed = score.rename(columns={'student_id': 'student_id'})
diff_keys_merge = pd.merge(
student_renamed,
score_renamed,
left_on='id', # 左表的键列名
right_on='student_id' # 右表的键列名
)
print("\n键名不同时的合并结果:")
print(diff_keys_merge)
输出:
id 姓名 年龄 student_id 科目 分数
0 1 张三 20 1 数学 90
1 2 李四 21 2 语文 85
四、总结:merge vs concat 怎么选?¶
| 方法 | 适用场景 | 关键参数 |
|---|---|---|
| concat | 简单拼接(无关联键),或“加行/加列” | axis(0=行,1=列)、ignore_index |
| merge | 有共同键的表合并(类似 SQL JOIN) | how(内/左/右/外)、on/left_on/right_on |
五、新手必记关键点¶
-
concat:
- 无关联键,直接拼接;
- 行拼接(axis=0):行数增加,列数不变;
- 列拼接(axis=1):列数增加,行数必须一致;
- 用ignore_index=True避免索引重复。 -
merge:
- 有关联键,按键匹配;
-how参数控制合并方式(内/左/右/外);
- 键名不同时用left_on/right_on指定。
多动手练习吧!从简单的小例子开始,慢慢尝试不同的合并方式,很快就能熟练掌握啦~