pandas数据合并
💡 核心要点:Pandas提供了强大的数据合并功能,包括堆叠合并(concat)和主键合并(merge),以及数据重塑功能(stack/unstack),是数据预处理的核心技能。
📚 目录
- [堆叠合并 - concat](#堆叠合并 - concat)
- [主键合并 - merge](#主键合并 - merge)
- [数据重塑 - stack/unstack](#数据重塑 - stack/unstack)
- 实战应用场景
- 知识点速查表
1. 堆叠合并 - concat
1.1 核心概念
📌 什么是堆叠合并
堆叠合并(Concatenation)是将多个DataFrame像积木一样拼接在一起的操作,类似于把多张表格物理地拼在一起。
💡 两种堆叠方向
- 横向堆叠(axis=1):沿X轴方向,增加列(特征),类似于在表格右侧添加新列
- 纵向堆叠(axis=0):沿Y轴方向,增加行(记录),类似于在表格下方添加新行
💡 生活化类比:横向堆叠就像给学生档案添加成绩单,纵向堆叠就像把两个班级的学生名单合并成一个年级名单。
1.2 concat函数详解
🔧 函数签名
python
pd.concat(objs, axis=0, join='outer', ignore_index=False, verify_integrity=False)
📊 参数说明
| 参数 | 类型 | 说明 | 默认值 |
|---|---|---|---|
objs |
list | 要合并的DataFrame列表 | 必填 |
axis |
int | 合并方向:0=纵向,1=横向 | 0 |
join |
str | 连接方式:'outer'=并集,'inner'=交集 | 'outer' |
ignore_index |
bool | 是否忽略原索引,重新生成0开始的索引 | False |
verify_integrity |
bool | 是否检查索引重复(True时有重复会报错) | False |
1.3 横向堆叠(增加列)
💡 知识点:横向合并增加特征
横向堆叠用于将不同来源的特征数据合并到同一张表中,根据行索引进行匹配。
📝 代码示例:学生信息与成绩合并
python
import pandas as pd
# 学生基本信息表
dic1 = {
'name': ['lisa', 'cc', 'john'],
'age': [18, 19, 20],
'gender': ['女', '男', '男']
}
stu1 = pd.DataFrame(dic1)
# 学生成绩表
dic2 = {
'Chinese': [90, 85, 96],
'English': [85, 95, 65]
}
score1 = pd.DataFrame(dic2)
# 横向堆叠(axis=1)
result = pd.concat(objs=[stu1, score1], axis=1)
print(result)
输出结果:
name age gender Chinese English
0 lisa 18 女 90 85
1 cc 19 男 85 95
2 john 20 男 96 65
⚠️ 注意事项:索引匹配规则
- 横向堆叠时,pandas会根据行索引自动匹配数据
- 如果行索引不一致,会产生NaN(缺失值)
📝 代码示例:索引不匹配的情况
python
# 修改score1的行索引
score1.index = [1, 2, 3]
# 横向堆叠(默认join='outer',取并集)
result = pd.concat(objs=[stu1, score1], axis=1, join='outer')
print(result)
输出结果:
name age gender Chinese English
0 lisa 18.0 女 NaN NaN
1 cc 19.0 男 90.0 85.0
2 john 20.0 男 85.0 95.0
3 NaN NaN NaN 96.0 65.0
🔍 对比说明:join参数的影响
| join参数 | 说明 | 结果 |
|---|---|---|
'outer' |
并集(默认),保留所有索引 | 索引不匹配处填充NaN |
'inner' |
交集,只保留共同索引 | 只保留两表都有的索引 |
📝 代码示例:使用inner连接
python
# 只保留共同的行索引
result = pd.concat(objs=[stu1, score1], axis=1, join='inner')
print(result)
# 输出:只有索引1和2的数据
💎 最佳实践
python
✅ 推荐:合并前确保索引一致
score1.index = stu1.index # 先对齐索引
result = pd.concat([stu1, score1], axis=1)
❌ 避免:直接合并索引不一致的表
result = pd.concat([stu1, score1], axis=1) # 可能产生大量NaN
1.4 纵向堆叠(增加行)
💡 知识点:纵向合并增加记录
纵向堆叠用于将多批数据合并成一个完整的数据集,根据列名进行匹配。
📝 代码示例:合并多个班级的学生信息
python
# 第一批学生信息
dic1 = {
'name': ['lisa', 'cc', 'john'],
'age': [18, 19, 20],
'gender': ['女', '男', '男']
}
stu1 = pd.DataFrame(dic1)
# 第二批学生信息
dic3 = {
'name': ['lili', 'xiaoming'],
'age': [18, 19]
}
stu2 = pd.DataFrame(dic3)
# 纵向堆叠(axis=0)
result = pd.concat(
objs=[stu1, stu2],
axis=0,
join='outer',
ignore_index=True
)
print(result)
输出结果:
name age gender
0 lisa 18 女
1 cc 19 男
2 john 20 男
3 lili 18 NaN
4 xiaoming 19 NaN
⚠️ 注意事项:列名匹配与索引处理
- 列名不一致:缺失的列会填充NaN(如上例中stu2没有gender列)
- 索引重复问题:两个表的索引可能重复(如都是0,1,2)
- 索引处理方案 :使用
ignore_index=True重新生成索引
📊 参数详解:verify_integrity
python
# verify_integrity=False(默认):允许索引重复
result = pd.concat([stu1, stu2], axis=0, verify_integrity=False)
# 成功合并,但索引会重复(0,1,2,0,1)
# verify_integrity=True:检查索引重复
result = pd.concat([stu1, stu2], axis=0, verify_integrity=True)
# 报错:ValueError: Indexes have overlapping values
💎 最佳实践:纵向合并时重置索引
python
✅ 推荐:使用ignore_index=True
result = pd.concat([stu1, stu2], axis=0, ignore_index=True)
# 自动生成新索引:0,1,2,3,4
❌ 避免:保留重复索引
result = pd.concat([stu1, stu2], axis=0)
# 索引重复:0,1,2,0,1(容易引起混淆)
1.5 实战案例:销售数据合并
🎯 应用场景1:合并上下半年销售数据
📝 代码示例
python
import pandas as pd
# 上半年销售数据
data_first_half = {
'Month': ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun'],
'Sales': [150, 200, 250, 300, 350, 400]
}
df_first_half = pd.DataFrame(data_first_half)
# 下半年销售数据
data_second_half = {
'Month': ['Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
'Sales': [450, 500, 550, 600, 650, 700]
}
df_second_half = pd.DataFrame(data_second_half)
# 纵向堆叠得到全年数据
yearly_sales = pd.concat(
objs=[df_first_half, df_second_half],
axis=0,
ignore_index=True
)
print(yearly_sales)
输出结果:
Month Sales
0 Jan 150
1 Feb 200
...
11 Dec 700
🎯 应用场景2:合并不同部门的数据
📝 代码示例
python
# 销售部数据
sales_data = {
'Date': ['2023-01-01', '2023-01-02', '2023-01-03'],
'Sales': [1000, 1500, 2000]
}
df_sales = pd.DataFrame(sales_data)
df_sales.set_index('Date', inplace=True) # 将日期设为索引
# 市场部数据
marketing_data = {
'Date': ['2023-01-01', '2023-01-02', '2023-01-03'],
'Marketing_Expense': [300, 400, 500]
}
df_marketing = pd.DataFrame(marketing_data)
df_marketing.set_index('Date', inplace=True)
# 横向堆叠合并
result = pd.concat(
objs=[df_sales, df_marketing],
axis=1
).reset_index() # 将索引重置为列
print(result)
输出结果:
Date Sales Marketing_Expense
0 2023-01-01 1000 300
1 2023-01-02 1500 400
2 2023-01-03 2000 500
🔧 关键方法:set_index() 和 reset_index()
| 方法 | 作用 | 参数 |
|---|---|---|
set_index(col, inplace=True) |
将某列设为行索引 | inplace=True直接修改原数据 |
reset_index() |
将行索引重置为列 | 返回新DataFrame |
💡 知识点:为什么要使用索引
- 横向合并时,pandas通过索引匹配数据
- 将日期设为索引,可以确保按日期正确对齐数据
2. 主键合并 - merge
2.1 核心概念
📌 什么是主键合并
主键合并(Merge)类似于SQL的JOIN操作,通过指定的**关键列(主键)**将两个表关联起来,而不是简单的物理拼接。
💡 与concat的区别
| 特性 | concat | merge |
|---|---|---|
| 合并方式 | 物理堆叠(按位置/索引) | 逻辑关联(按主键值) |
| 适用场景 | 结构相同的数据拼接 | 关系型数据关联 |
| 类比 | 把两张纸贴在一起 | 根据学号匹配学生和成绩 |
💡 生活化类比:concat像是把两摞卡片叠在一起,merge像是根据身份证号把个人信息和银行账户关联起来。
2.2 merge函数详解
🔧 函数签名
python
pd.merge(left, right, how='inner', on=None, left_on=None, right_on=None,
left_index=False, right_index=False)
📊 参数说明
| 参数 | 类型 | 说明 | 默认值 |
|---|---|---|---|
left |
DataFrame | 左表(主表) | 必填 |
right |
DataFrame | 右表(从表) | 必填 |
how |
str | 连接方式:'inner'/'outer'/'left'/'right' | 'inner' |
on |
str/list | 主键列名(两表列名相同时使用) | None |
left_on |
str/list | 左表的主键列名 | None |
right_on |
str/list | 右表的主键列名 | None |
left_index |
bool | 使用左表的行索引作为主键 | False |
right_index |
bool | 使用右表的行索引作为主键 | False |
2.3 四种连接方式
📌 核心概念:SQL风格的连接
merge提供了四种连接方式,对应SQL中的JOIN操作。
🔍 对比说明:四种连接方式
| 连接方式 | 说明 | 保留数据 | SQL等价 |
|---|---|---|---|
inner |
内连接 | 只保留两表都有的记录 | INNER JOIN |
outer |
外连接(全连接) | 保留两表所有记录 | FULL OUTER JOIN |
left |
左连接 | 保留左表所有记录 | LEFT JOIN |
right |
右连接 | 保留右表所有记录 | RIGHT JOIN |
📝 代码示例:准备测试数据
python
import pandas as pd
# 左表:学生基本信息
left_tb = pd.DataFrame(
[[1, '张三', 20, 1],
[2, '李四', 21, 0],
[4, '王五', 22, 1]],
columns=['id', 'name', 'age', 'gender']
)
# 右表:学生成绩
right_tb = pd.DataFrame(
[[1, 81, 91],
[2, 51, 41],
[3, 99, 98],
[4, 61, 71]],
columns=['id', 'math', 'chinese']
)
数据说明:
- 左表有id: 1, 2, 4(缺少3)
- 右表有id: 1, 2, 3, 4(完整)
2.3.1 内连接(inner)
💡 知识点:只保留匹配的记录
内连接只保留两个表中主键都存在的记录,类似于求交集。
📝 代码示例
python
# 内连接:只保留id在两表都存在的记录
result = pd.merge(
left=left_tb,
right=right_tb,
how='inner',
on='id'
)
print(result)
输出结果:
id name age gender math chinese
0 1 张三 20 1 81 91
1 2 李四 21 0 51 41
2 4 王五 22 1 61 71
⚠️ 注意事项
- id=3的记录被排除(左表中不存在)
- 结果只包含id为1,2,4的记录
🎯 应用场景
- 数据清洗:只保留完整的记录
- 数据验证:检查两表的交集
2.3.2 外连接(outer)
💡 知识点:保留所有记录
外连接保留两个表中所有的记录,缺失的地方填充NaN。
📝 代码示例
python
# 外连接:保留所有记录
result = pd.merge(
left=left_tb,
right=right_tb,
how='outer',
on='id'
)
print(result)
输出结果:
id name age gender math chinese
0 1 张三 20.0 1.0 81 91
1 2 李四 21.0 0.0 51 41
2 4 王五 22.0 1.0 61 71
3 3 NaN NaN NaN 99 98
⚠️ 注意事项
- id=3的记录被保留,但左表的列填充NaN
- 适合需要完整数据视图的场景
2.3.3 左连接(left)
💡 知识点:以左表为主
左连接保留左表所有记录,右表没有匹配的填充NaN。
📝 代码示例
python
# 左连接:保留左表所有记录
result = pd.merge(
left=left_tb,
right=right_tb,
how='left',
on='id'
)
print(result)
输出结果:
id name age gender math chinese
0 1 张三 20 1 81 91
1 2 李四 21 0 51 41
2 4 王五 22 1 61 71
🎯 应用场景
- 主表数据完整性:确保主表所有记录都保留
- 补充信息:为主表添加额外信息(有则添加,无则为空)
2.3.4 右连接(right)
💡 知识点:以右表为主
右连接保留右表所有记录,左表没有匹配的填充NaN。
📝 代码示例
python
# 右连接:保留右表所有记录
result = pd.merge(
left=left_tb,
right=right_tb,
how='right',
on='id'
)
print(result)
输出结果:
id name age gender math chinese
0 1 张三 20.0 1.0 81 91
1 2 李四 21.0 0.0 51 41
2 3 NaN NaN NaN 99 98
3 4 王五 22.0 1.0 61 71
💎 最佳实践:选择合适的连接方式
python
✅ 内连接:数据分析时,只关注完整记录
result = pd.merge(left, right, how='inner', on='id')
✅ 左连接:以主表为准,补充额外信息
result = pd.merge(left, right, how='left', on='id')
✅ 外连接:需要完整数据视图,发现数据缺失
result = pd.merge(left, right, how='outer', on='id')
2.4 不同主键名称的合并
💡 知识点:使用left_on和right_on
当两个表的主键列名不同时,需要分别指定左表和右表的主键列名。
📝 代码示例:列名不同的情况
python
# 修改左表的列名:id -> sid
left_tb = left_tb.rename(columns={'id': 'sid'})
# 使用left_on和right_on指定不同的主键列
result = pd.merge(
left=left_tb,
right=right_tb,
left_on='sid', # 左表的主键列
right_on='id', # 右表的主键列
how='outer'
)
print(result)
输出结果:
sid name age gender id math chinese
0 1.0 张三 20.0 1.0 1 81 91
1 2.0 李四 21.0 0.0 2 51 41
2 4.0 王五 22.0 1.0 4 61 71
3 NaN NaN NaN NaN 3 99 98
⚠️ 注意事项
- 结果中会同时保留sid和id两列
- 如果不需要重复列,可以在合并后删除
💎 最佳实践:处理重复列
python
✅ 推荐:合并后删除重复列
result = pd.merge(left, right, left_on='sid', right_on='id', how='outer')
result = result.drop('id', axis=1) # 删除重复的id列
✅ 推荐:合并前统一列名
left_tb = left_tb.rename(columns={'sid': 'id'})
result = pd.merge(left, right, on='id', how='outer')
2.5 使用索引作为主键
💡 知识点:left_index和right_index参数
有时主键不在列中,而是在行索引中,此时需要使用left_index和right_index参数。
📝 代码示例:索引作为主键
python
# 创建一个以索引为主键的表
gender_tb = pd.DataFrame([['女'], ['男']], columns=['性别'])
gender_tb.index = [1, 2] # 设置行索引
# 使用左表的索引和右表的id列进行合并
result = pd.merge(
left=gender_tb,
right=right_tb,
left_index=True, # 使用左表的行索引
right_on='id', # 使用右表的id列
how='inner'
)
print(result)
2.6 实战案例:数据完整性检查
🎯 应用场景:检查用户订单数据
📝 代码示例:准备数据
python
import numpy as np
import pandas as pd
# 用户表
user = pd.DataFrame({
"ID": [1, 2, 3, 4],
"Name": list("abcd"),
"age": np.random.randint(18, 60, 4),
"gender": np.random.randint(0, 2, 4)
})
# 订单表(注意:userid=5不在用户表中)
order = pd.DataFrame({
"orderid": [1, 2, 3, 4],
"userid": [2, 3, 4, 5],
"sales": np.random.randint(10, 100, 4)
})
案例1:查找未下单的用户
💡 知识点:使用isin()方法
python
# 方法1:使用isin()
no_order_users = user[~user.ID.isin(order.userid)]
print("未下单的用户:")
print(no_order_users)
💡 知识点:使用左连接+isnull()
python
# 方法2:使用左连接
df1 = pd.merge(
left=user,
right=order,
left_on='ID',
right_on='userid',
how='left'
)
# 找出orderid为空的记录(即未下单的用户)
no_order_users = df1[df1['orderid'].isnull()]['Name']
print("未下单的用户:")
print(no_order_users)
输出结果:
未下单的用户:
0 a
Name: Name, dtype: object
🔍 对比说明:两种方法的优劣
| 方法 | 优点 | 缺点 |
|---|---|---|
isin() |
代码简洁,性能好 | 只能判断存在性 |
merge + isnull() |
可以获取更多信息 | 代码较长 |
案例2:检查数据约束违规
💡 知识点:发现不符合约束的记录
业务规则:下单的用户必须存在于用户表中。
python
# 方法1:使用isin()
invalid_orders = order[~order.userid.isin(user.ID)]
print("不符合约束的订单:")
print(invalid_orders)
💡 知识点:使用右连接+isnull()
python
# 方法2:使用右连接
df2 = pd.merge(
left=user,
right=order,
left_on='ID',
right_on='userid',
how='right'
)
# 找出ID为空的记录(即用户不存在的订单)
invalid_orders = df2[df2['ID'].isnull()]
print("不符合约束的订单:")
print(invalid_orders)
输出结果:
不符合约束的订单:
ID Name age gender orderid userid sales
3 NaN NaN NaN NaN 4 5 XX
💎 最佳实践:数据质量检查流程
python
✅ 推荐:建立完整的数据检查流程
# 1. 检查主表完整性
missing_users = user[~user.ID.isin(order.userid)]
# 2. 检查外键约束
invalid_orders = order[~order.userid.isin(user.ID)]
# 3. 检查数据一致性
merged = pd.merge(user, order, left_on='ID', right_on='userid', how='outer')
inconsistent = merged[merged['ID'].isnull() | merged['userid'].isnull()]
# 4. 生成数据质量报告
print(f"未下单用户数:{len(missing_users)}")
print(f"无效订单数:{len(invalid_orders)}")
print(f"数据不一致记录数:{len(inconsistent)}")
3. 数据重塑 - stack/unstack
3.1 核心概念
📌 什么是数据重塑
数据重塑(Reshaping)是改变数据的组织形式,在行索引 和列索引之间转换数据,而不改变数据的实际内容。
💡 两种重塑操作
- unstack():将行索引转换为列索引(行变列,数据"展开")
- stack():将列索引转换为行索引(列变行,数据"堆叠")
💡 生活化类比:unstack像是把竖着排列的书横着摆放,stack像是把横着摆放的书竖着排列。
3.2 多级索引基础
💡 知识点:什么是多级索引
多级索引(MultiIndex)允许在一个轴上有多个索引层级,类似于Excel中的分组。
📝 代码示例:创建多级索引
python
import pandas as pd
# 创建学生成绩数据
data = {
'学生': ['小明', '小明', '小红', '小红', '小刚', '小刚'],
'科目': ['数学', '语文', '数学', '语文', '数学', '语文'],
'成绩': [90, 85, 95, 88, 78, 92]
}
df = pd.DataFrame(data)
# 设置多级索引(学生和科目)
multi_index_df = df.set_index(['学生', '科目'])
print(multi_index_df)
输出结果:
成绩
学生 科目
小明 数学 90
语文 85
小红 数学 95
语文 88
小刚 数学 78
语文 92
⚠️ 注意事项
- 多级索引有两个层级:第一级是学生,第二级是科目
- 数据以层级结构组织,便于分组查询
3.3 unstack操作
💡 知识点:将行索引转为列索引
unstack()将最内层的行索引转换为列索引,使数据从"长格式"变为"宽格式"。
📝 代码示例:使用unstack()
python
# 将科目从行索引移动到列索引
unstacked_df = multi_index_df.unstack()
print(unstacked_df)
输出结果:
成绩
科目 数学 语文
学生
小明 90 85
小红 95 88
小刚 78 92
🔍 对比说明:unstack前后的变化
| 特性 | unstack前 | unstack后 |
|---|---|---|
| 行索引 | 学生+科目(两级) | 学生(一级) |
| 列索引 | 成绩(一级) | 成绩+科目(两级) |
| 数据形状 | 6行1列(长格式) | 3行2列(宽格式) |
| 可读性 | 适合存储 | 适合展示 |
🎯 应用场景
- 数据透视:将分类变量转为列
- 报表生成:生成交叉表格式
- 数据可视化:准备宽格式数据
💎 最佳实践
python
✅ 推荐:用于生成报表
# 将长格式数据转为宽格式,便于阅读
report = multi_index_df.unstack()
❌ 避免:对大量类别使用unstack
# 如果科目有100个,会生成100列,不便查看
3.4 stack操作
💡 知识点:将列索引转为行索引
stack()将列索引转换为行索引,使数据从"宽格式"变为"长格式"。
📝 代码示例:使用stack()
python
# 将科目从列索引移动回行索引
stacked_df = unstacked_df.stack()
print(stacked_df)
输出结果:
学生 科目
小明 数学 90
语文 85
小红 数学 95
语文 88
小刚 数学 78
语文 92
Name: 成绩, dtype: int64
🔍 对比说明:stack与unstack的关系
| 操作 | 方向 | 结果 |
|---|---|---|
unstack() |
行→列 | 长格式→宽格式 |
stack() |
列→行 | 宽格式→长格式 |
💡 知识点:stack和unstack互为逆操作
python
# 原始数据
original = multi_index_df
# unstack后再stack,恢复原状
result = multi_index_df.unstack().stack()
# 验证是否相同
print(original.equals(result)) # 输出:True
3.5 reset_index()配合使用
💡 知识点:将索引转为普通列
在数据重塑后,常常需要将索引重置为普通列,便于后续处理。
📝 代码示例:完整的数据重塑流程
python
# 1. 原始数据
data = {
'学生': ['小明', '小明', '小红', '小红', '小刚', '小刚'],
'科目': ['数学', '语文', '数学', '语文', '数学', '语文'],
'成绩': [90, 85, 95, 88, 78, 92]
}
df = pd.DataFrame(data)
# 2. 设置多级索引
multi_index_df = df.set_index(['学生', '科目'])
# 3. unstack展开
unstacked_df = multi_index_df.unstack()
# 4. stack堆叠
stacked_df = unstacked_df.stack()
# 5. 重置索引,将多级索引转为普通列
final_df = stacked_df.reset_index()
print(final_df)
输出结果:
学生 科目 成绩
0 小明 数学 90
1 小明 语文 85
2 小红 数学 95
3 小红 语文 88
4 小刚 数学 78
5 小刚 语文 92
🔧 关键方法总结
| 方法 | 作用 | 使用场景 |
|---|---|---|
set_index() |
将列设为索引 | 准备多级索引 |
unstack() |
行索引→列索引 | 长格式→宽格式 |
stack() |
列索引→行索引 | 宽格式→长格式 |
reset_index() |
索引→普通列 | 恢复普通表格 |
💎 最佳实践:数据重塑工作流
python
✅ 推荐:完整的数据重塑流程
# 1. 设置索引
df_indexed = df.set_index(['分组列1', '分组列2'])
# 2. 重塑数据
df_reshaped = df_indexed.unstack() # 或 stack()
# 3. 重置索引
df_final = df_reshaped.reset_index()
# 4. 清理列名(如果需要)
df_final.columns = ['新列名1', '新列名2', ...]
4. 实战应用场景
4.1 场景总结
🎯 concat适用场景
| 场景 | 方向 | 示例 |
|---|---|---|
| 合并多个时间段数据 | 纵向 | 上半年+下半年销售数据 |
| 合并多个来源数据 | 纵向 | 多个分店的销售记录 |
| 添加新特征 | 横向 | 学生信息+成绩 |
| 合并不同维度数据 | 横向 | 销售数据+市场费用 |
🎯 merge适用场景
| 场景 | 连接方式 | 示例 |
|---|---|---|
| 关联主从表 | inner | 学生表+成绩表 |
| 补充信息 | left | 订单表+用户信息 |
| 数据完整性检查 | outer | 发现缺失数据 |
| 数据质量验证 | right | 检查外键约束 |
🎯 stack/unstack适用场景
| 场景 | 操作 | 示例 |
|---|---|---|
| 生成透视表 | unstack | 学生-科目成绩表 |
| 数据可视化准备 | unstack | 时间序列宽格式 |
| 数据存储 | stack | 宽格式→长格式 |
| 数据分析 | stack | 便于分组统计 |
4.2 综合案例:销售数据分析
🎯 业务场景
某公司需要分析2023年各地区、各产品的销售情况,数据分散在多个表中。
📝 代码示例:完整流程
python
import pandas as pd
import numpy as np
# 1. 准备数据
# 第一季度销售数据
q1_sales = pd.DataFrame({
'region': ['北京', '上海', '广州'],
'product': ['A', 'B', 'C'],
'sales': [100, 150, 200]
})
# 第二季度销售数据
q2_sales = pd.DataFrame({
'region': ['北京', '上海', '广州'],
'product': ['A', 'B', 'C'],
'sales': [120, 160, 210]
})
# 产品信息表
product_info = pd.DataFrame({
'product': ['A', 'B', 'C'],
'category': ['电子', '服装', '食品'],
'cost': [50, 80, 100]
})
# 2. 纵向合并季度数据
all_sales = pd.concat(
[q1_sales, q2_sales],
axis=0,
keys=['Q1', 'Q2'], # 添加季度标识
names=['quarter', 'id']
).reset_index(level=0)
print("合并后的销售数据:")
print(all_sales)
# 3. 关联产品信息
sales_with_info = pd.merge(
left=all_sales,
right=product_info,
on='product',
how='left'
)
print("\n关联产品信息后:")
print(sales_with_info)
# 4. 计算利润
sales_with_info['profit'] = sales_with_info['sales'] - sales_with_info['cost']
# 5. 数据透视(使用unstack)
pivot_data = sales_with_info.set_index(['quarter', 'region', 'product'])
pivot_table = pivot_data['profit'].unstack(level='product')
print("\n利润透视表:")
print(pivot_table)
💡 知识点:keys参数的使用
在concat中使用keys参数可以为合并的数据添加标识,便于后续区分数据来源。
4.3 性能优化建议
🚀 concat性能优化
python
✅ 推荐:一次性合并多个DataFrame
result = pd.concat([df1, df2, df3, df4], axis=0)
❌ 避免:循环中多次concat
result = df1
for df in [df2, df3, df4]:
result = pd.concat([result, df], axis=0) # 每次都创建新对象,效率低
🚀 merge性能优化
python
✅ 推荐:使用索引进行merge
df1.set_index('key', inplace=True)
df2.set_index('key', inplace=True)
result = pd.merge(df1, df2, left_index=True, right_index=True)
✅ 推荐:对大数据集使用inner连接
result = pd.merge(df1, df2, how='inner') # 减少结果集大小
🚀 内存优化
python
✅ 推荐:及时删除不需要的中间结果
result = pd.concat([df1, df2], axis=0)
del df1, df2 # 释放内存
✅ 推荐:使用合适的数据类型
df['id'] = df['id'].astype('int32') # 而不是默认的int64
5. 知识点速查表
5.1 函数对比速查
📊 concat vs merge
| 特性 | concat | merge |
|---|---|---|
| 合并方式 | 物理堆叠 | 逻辑关联 |
| 主要参数 | objs, axis, join | left, right, how, on |
| 匹配依据 | 索引位置 | 主键值 |
| 适用场景 | 同结构数据拼接 | 关系型数据关联 |
| 性能 | 较快 | 较慢(需要匹配) |
5.2 参数速查表
📊 concat参数
| 参数 | 可选值 | 说明 | 优先级 |
|---|---|---|---|
axis |
0, 1 | 0=纵向,1=横向 | ⭐⭐⭐ |
join |
'outer', 'inner' | 并集/交集 | ⭐⭐ |
ignore_index |
True, False | 重置索引 | ⭐⭐ |
verify_integrity |
True, False | 检查索引重复 | ⭐ |
keys |
list | 添加层级标识 | ⭐⭐ |
📊 merge参数
| 参数 | 可选值 | 说明 | 优先级 |
|---|---|---|---|
how |
'inner', 'outer', 'left', 'right' | 连接方式 | ⭐⭐⭐ |
on |
str/list | 主键列名(相同时) | ⭐⭐⭐ |
left_on |
str/list | 左表主键 | ⭐⭐⭐ |
right_on |
str/list | 右表主键 | ⭐⭐⭐ |
left_index |
True, False | 使用左表索引 | ⭐⭐ |
right_index |
True, False | 使用右表索引 | ⭐⭐ |
5.3 常用方法速查
🔧 索引操作方法
| 方法 | 作用 | 返回值 | 优先级 |
|---|---|---|---|
set_index(col) |
将列设为索引 | DataFrame | ⭐⭐⭐ |
reset_index() |
将索引重置为列 | DataFrame | ⭐⭐⭐ |
unstack() |
行索引→列索引 | DataFrame | ⭐⭐ |
stack() |
列索引→行索引 | Series | ⭐⭐ |
🔧 数据检查方法
| 方法 | 作用 | 返回值 | 优先级 |
|---|---|---|---|
isin(values) |
检查值是否在列表中 | bool Series | ⭐⭐⭐ |
isnull() |
检查是否为空 | bool Series | ⭐⭐⭐ |
duplicated() |
检查是否重复 | bool Series | ⭐⭐ |
equals(other) |
比较两个对象是否相同 | bool | ⭐ |
5.4 学习路径建议
⭐ 基础必学(第1周)
- concat纵向堆叠(axis=0)
- concat横向堆叠(axis=1)
- merge内连接(how='inner')
- merge左连接(how='left')
- set_index()和reset_index()
⭐⭐ 进阶提升(第2周)
- concat的join参数(inner/outer)
- concat的ignore_index参数
- merge的四种连接方式对比
- 使用left_on和right_on
- 使用left_index和right_index
- isin()方法进行数据筛选
⭐⭐⭐ 高级技巧(第3周)
- unstack()和stack()数据重塑
- 多级索引的创建和操作
- concat的keys参数
- merge的数据完整性检查
- 综合案例:多表关联分析
- 性能优化技巧
5.5 常见错误与解决方案
❌ 错误1:索引不匹配导致NaN
python
# 问题代码
result = pd.concat([df1, df2], axis=1) # 索引不一致
# 解决方案
df2.index = df1.index # 先对齐索引
result = pd.concat([df1, df2], axis=1)
❌ 错误2:主键列名不一致
python
# 问题代码
result = pd.merge(df1, df2, on='id') # df2的主键是user_id
# 解决方案
result = pd.merge(df1, df2, left_on='id', right_on='user_id')
❌ 错误3:索引重复导致错误
python
# 问题代码
result = pd.concat([df1, df2], axis=0, verify_integrity=True) # 报错
# 解决方案
result = pd.concat([df1, df2], axis=0, ignore_index=True)
❌ 错误4:merge后列名冲突
python
# 问题代码
result = pd.merge(df1, df2, on='id') # 两表都有name列
# 解决方案
result = pd.merge(df1, df2, on='id', suffixes=('_left', '_right'))
5.6 思维导图
Pandas数据处理
│
├─ 堆叠合并(concat)
│ ├─ 横向堆叠(axis=1)
│ │ ├─ 增加列/特征
│ │ ├─ 根据索引匹配
│ │ └─ join参数(outer/inner)
│ │
│ └─ 纵向堆叠(axis=0)
│ ├─ 增加行/记录
│ ├─ 根据列名匹配
│ └─ ignore_index参数
│
├─ 主键合并(merge)
│ ├─ 内连接(inner)- 交集
│ ├─ 外连接(outer)- 并集
│ ├─ 左连接(left)- 保留左表
│ ├─ 右连接(right)- 保留右表
│ │
│ └─ 主键指定方式
│ ├─ on(列名相同)
│ ├─ left_on + right_on(列名不同)
│ └─ left_index + right_index(使用索引)
│
└─ 数据重塑(stack/unstack)
├─ unstack(行→列)
│ ├─ 长格式→宽格式
│ └─ 适合报表展示
│
└─ stack(列→行)
├─ 宽格式→长格式
└─ 适合数据存储
5.7 快速决策树
需要合并数据?
│
├─ 是否需要根据主键关联?
│ │
│ ├─ 是 → 使用merge
│ │ └─ 选择连接方式
│ │ ├─ 只要完整记录 → inner
│ │ ├─ 保留主表 → left
│ │ ├─ 保留从表 → right
│ │ └─ 保留所有 → outer
│ │
│ └─ 否 → 使用concat
│ └─ 选择方向
│ ├─ 增加列 → axis=1
│ └─ 增加行 → axis=0
│
└─ 需要改变数据形状?
│
├─ 长→宽 → unstack()
└─ 宽→长 → stack()
📚 总结
核心要点回顾
-
concat:物理堆叠,适合同结构数据拼接
- 横向(axis=1):增加列,按索引匹配
- 纵向(axis=0):增加行,按列名匹配
-
merge:逻辑关联,适合关系型数据连接
- 四种连接方式:inner/outer/left/right
- 灵活的主键指定:on/left_on+right_on/索引
-
stack/unstack:数据重塑,改变数据组织形式
- unstack:行→列,长格式→宽格式
- stack:列→行,宽格式→长格式
📊 参数组合说明
| 场景 | 参数组合 | 说明 |
|---|---|---|
| 两表列名相同 | on='id' |
最简单的情况 |
| 两表列名不同 | left_on='sid', right_on='id' |
分别指定主键 |
| 左表用索引 | left_index=True, right_on='id' |
左表索引匹配右表列 |
| 右表用索引 | left_on='id', right_index=True |
左表列匹配右表索引 |
| 两表都用索引 | left_index=True, right_index=True |
索引对索引 |