第七章:数据合并与连接
📋 章节概述
本章将学习 Pandas 中数据合并与连接的核心技术。掌握这些方法后,你将能够灵活地整合多个数据源,完成复杂的数据关联分析。
🎯 学习目标
- 掌握 merge() 方法进行数据库风格的表连接
- 掌握 join() 方法进行基于索引的表连接
- 掌握 concat() 方法进行数据拼接
- 理解各种连接方式(内连接、外连接、左连接、右连接)
- 学会处理合并时的重复列名和索引问题
- 掌握实际业务场景中的多表关联技巧
📊 知识结构图
数据合并与连接
merge
基于列
join
基于索引
concat
数据拼接
inner内连接
left左连接
right右连接
outer外连接
多键合并
单索引join
多索引join
纵向拼接
axis=0
横向拼接
axis=1
层次化索引
🏢 实战场景
本章使用"电商公司数据整合"案例:
- 客户表:客户基本信息(ID、姓名、城市、会员等级)
- 订单表:订单信息(订单号、客户ID、金额、日期)
- 商品表:商品信息(商品ID、名称、类别、单价)
- 订单明细表:订单与商品的关联(多对多关系)
- 销售目标表:月度销售目标
python
import pandas as pd
import numpy as np
import warnings
warnings.filterwarnings('ignore')
# 设置显示选项
pd.set_option('display.max_columns', None)
pd.set_option('display.width', None)
准备示例数据 - 电商公司数据表
python
np.random.seed(2026)
# 1. 客户表
df_customers = pd.DataFrame({
'客户ID': ['C001', 'C002', 'C003', 'C004', 'C005', 'C006', 'C007', 'C008'],
'姓名': ['张伟', '李娜', '王强', '刘洋', '陈静', '赵敏', '周杰', '吴刚'],
'性别': ['男', '女', '男', '男', '女', '女', '男', '男'],
'年龄': [28, 32, 25, 35, 29, 31, 27, 33],
'城市': ['北京', '上海', '广州', '深圳', '北京', '上海', '广州', '深圳'],
'会员等级': ['金牌', '银牌', '铜牌', '金牌', '银牌', '铜牌', '金牌', '银牌'],
'注册日期': pd.to_datetime(['2023-01-15', '2023-03-20', '2023-06-10',
'2023-08-05', '2023-09-12', '2023-11-18',
'2024-01-08', '2024-02-25'])
})
# 2. 订单表
df_orders = pd.DataFrame({
'订单号': ['ORD001', 'ORD002', 'ORD003', 'ORD004', 'ORD005',
'ORD006', 'ORD007', 'ORD008', 'ORD009', 'ORD010'],
'客户ID': ['C001', 'C002', 'C001', 'C003', 'C002',
'C004', 'C005', 'C001', 'C006', 'C007'],
'订单金额': [1250.00, 3680.50, 520.00, 1890.00, 2450.00,
999.00, 4560.00, 780.00, 2340.00, 1560.00],
'订单日期': pd.to_datetime(['2026-01-05', '2026-01-08', '2026-01-12',
'2026-01-15', '2026-01-20', '2026-02-03',
'2026-02-10', '2026-02-18', '2026-02-25',
'2026-03-05']),
'订单状态': ['已完成', '已完成', '已取消', '已完成', '已完成',
'运输中', '已完成', '已完成', '待发货', '已完成']
})
# 3. 商品表
df_products = pd.DataFrame({
'商品ID': ['P001', 'P002', 'P003', 'P004', 'P005', 'P006', 'P007', 'P008'],
'商品名称': ['iPhone 16 Pro', 'MacBook Air', 'AirPods Pro', 'iPad Air',
'小米14', '华为Mate60', '戴森吸尘器', '索尼耳机'],
'类别': ['手机', '电脑', '配件', '平板', '手机', '手机', '家电', '配件'],
'单价': [8999, 9499, 1999, 4799, 3999, 6999, 2999, 1299],
'库存': [100, 50, 200, 80, 150, 80, 30, 100]
})
# 4. 订单明细表(订单与商品的多对多关系)
df_order_items = pd.DataFrame({
'订单号': ['ORD001', 'ORD001', 'ORD002', 'ORD003', 'ORD004',
'ORD005', 'ORD005', 'ORD006', 'ORD007', 'ORD008',
'ORD009', 'ORD010'],
'商品ID': ['P001', 'P003', 'P002', 'P005', 'P001',
'P004', 'P006', 'P003', 'P002', 'P007',
'P008', 'P005'],
'数量': [1, 2, 1, 1, 1, 1, 1, 3, 2, 1, 2, 1],
'折扣': [0.95, 1.0, 0.90, 1.0, 0.95, 1.0, 0.85, 1.0, 0.90, 0.80, 1.0, 0.95]
})
# 5. 销售目标表
df_targets = pd.DataFrame({
'月份': ['2026-01', '2026-02', '2026-03', '2026-04'],
'目标金额': [50000, 60000, 55000, 70000],
'实际完成': [48500, 62000, 0, 0]
})
print("数据表预览:")
print(f"\n1. 客户表: {df_customers.shape}")
print(f"2. 订单表: {df_orders.shape}")
print(f"3. 商品表: {df_products.shape}")
print(f"4. 订单明细表: {df_order_items.shape}")
print(f"5. 销售目标表: {df_targets.shape}")
数据表预览:
1. 客户表: (8, 7)
2. 订单表: (10, 5)
3. 商品表: (8, 5)
4. 订单明细表: (12, 4)
5. 销售目标表: (4, 3)
python
# 查看客户表
df_customers
| | 客户ID | 姓名 | 性别 | 年龄 | 城市 | 会员等级 | 注册日期 |
| 0 | C001 | 张伟 | 男 | 28 | 北京 | 金牌 | 2023-01-15 |
| 1 | C002 | 李娜 | 女 | 32 | 上海 | 银牌 | 2023-03-20 |
| 2 | C003 | 王强 | 男 | 25 | 广州 | 铜牌 | 2023-06-10 |
| 3 | C004 | 刘洋 | 男 | 35 | 深圳 | 金牌 | 2023-08-05 |
| 4 | C005 | 陈静 | 女 | 29 | 北京 | 银牌 | 2023-09-12 |
| 5 | C006 | 赵敏 | 女 | 31 | 上海 | 铜牌 | 2023-11-18 |
| 6 | C007 | 周杰 | 男 | 27 | 广州 | 金牌 | 2024-01-08 |
| 7 | C008 | 吴刚 | 男 | 33 | 深圳 | 银牌 | 2024-02-25 |
|---|
python
# 查看订单表
df_orders
| | 订单号 | 客户ID | 订单金额 | 订单日期 | 订单状态 |
| 0 | ORD001 | C001 | 1250.0 | 2026-01-05 | 已完成 |
| 1 | ORD002 | C002 | 3680.5 | 2026-01-08 | 已完成 |
| 2 | ORD003 | C001 | 520.0 | 2026-01-12 | 已取消 |
| 3 | ORD004 | C003 | 1890.0 | 2026-01-15 | 已完成 |
| 4 | ORD005 | C002 | 2450.0 | 2026-01-20 | 已完成 |
| 5 | ORD006 | C004 | 999.0 | 2026-02-03 | 运输中 |
| 6 | ORD007 | C005 | 4560.0 | 2026-02-10 | 已完成 |
| 7 | ORD008 | C001 | 780.0 | 2026-02-18 | 已完成 |
| 8 | ORD009 | C006 | 2340.0 | 2026-02-25 | 待发货 |
| 9 | ORD010 | C007 | 1560.0 | 2026-03-05 | 已完成 |
|---|
python
# 查看商品表
df_products
| | 商品ID | 商品名称 | 类别 | 单价 | 库存 |
| 0 | P001 | iPhone 16 Pro | 手机 | 8999 | 100 |
| 1 | P002 | MacBook Air | 电脑 | 9499 | 50 |
| 2 | P003 | AirPods Pro | 配件 | 1999 | 200 |
| 3 | P004 | iPad Air | 平板 | 4799 | 80 |
| 4 | P005 | 小米14 | 手机 | 3999 | 150 |
| 5 | P006 | 华为Mate60 | 手机 | 6999 | 80 |
| 6 | P007 | 戴森吸尘器 | 家电 | 2999 | 30 |
| 7 | P008 | 索尼耳机 | 配件 | 1299 | 100 |
|---|
7.1 merge() - 数据库风格的表连接
merge() 是最常用的表连接方法,基于指定列进行关联。
merge连接
inner
内连接
left
左连接
right
右连接
outer
外连接
两表键都存在的记录
交集
保留左表所有记录
右表缺失填NaN
保留右表所有记录
左表缺失填NaN
两表所有记录
并集
7.1.1 基础合并 - 默认内连接 (inner)
python
# 将订单表与客户表合并(内连接)
print("将订单表与客户表合并(内连接):")
print("说明:只保留两表中都有的客户ID的记录")
merged_inner = pd.merge(df_orders, df_customers, on='客户ID')
print(f"合并后形状: {merged_inner.shape}")
merged_inner[['订单号', '客户ID', '姓名', '订单金额', '城市', '会员等级']]
将订单表与客户表合并(内连接):
说明:只保留两表中都有的客户ID的记录
合并后形状: (10, 11)
| | 订单号 | 客户ID | 姓名 | 订单金额 | 城市 | 会员等级 |
| 0 | ORD001 | C001 | 张伟 | 1250.0 | 北京 | 金牌 |
| 1 | ORD002 | C002 | 李娜 | 3680.5 | 上海 | 银牌 |
| 2 | ORD003 | C001 | 张伟 | 520.0 | 北京 | 金牌 |
| 3 | ORD004 | C003 | 王强 | 1890.0 | 广州 | 铜牌 |
| 4 | ORD005 | C002 | 李娜 | 2450.0 | 上海 | 银牌 |
| 5 | ORD006 | C004 | 刘洋 | 999.0 | 深圳 | 金牌 |
| 6 | ORD007 | C005 | 陈静 | 4560.0 | 北京 | 银牌 |
| 7 | ORD008 | C001 | 张伟 | 780.0 | 北京 | 金牌 |
| 8 | ORD009 | C006 | 赵敏 | 2340.0 | 上海 | 铜牌 |
| 9 | ORD010 | C007 | 周杰 | 1560.0 | 广州 | 金牌 |
|---|
python
# 显式指定 how='inner'
print("显式指定 how='inner':")
merged_inner_explicit = pd.merge(df_orders, df_customers, on='客户ID', how='inner')
print(f"结果与默认相同,形状: {merged_inner_explicit.shape}")
显式指定 how='inner':
结果与默认相同,形状: (10, 11)
7.1.2 左连接 (left) - 保留左表所有记录
python
# 创建一条客户ID在客户表中不存在的订单用于演示
df_orders_extra = pd.concat([df_orders, pd.DataFrame({
'订单号': ['ORD011'],
'客户ID': ['C999'], # 不存在的客户
'订单金额': [1000.00],
'订单日期': pd.to_datetime(['2026-03-10']),
'订单状态': ['已完成']
})], ignore_index=True)
# 左连接:保留所有订单,即使客户信息缺失
print("左连接:保留所有订单,即使客户信息缺失")
merged_left = pd.merge(df_orders_extra, df_customers, on='客户ID', how='left')
print(f"合并后形状: {merged_left.shape}")
print("注意:ORD011 的客户信息为 NaN")
merged_left[['订单号', '客户ID', '姓名', '订单金额']].tail(3)
左连接:保留所有订单,即使客户信息缺失
合并后形状: (11, 11)
注意:ORD011 的客户信息为 NaN
| | 订单号 | 客户ID | 姓名 | 订单金额 |
| 8 | ORD009 | C006 | 赵敏 | 2340.0 |
| 9 | ORD010 | C007 | 周杰 | 1560.0 |
| 10 | ORD011 | C999 | NaN | 1000.0 |
|---|
7.1.3 右连接 (right) - 保留右表所有记录
python
# 右连接:保留所有客户,即使他们没有订单
print("右连接:保留所有客户,即使他们没有订单")
merged_right = pd.merge(df_orders, df_customers, on='客户ID', how='right')
print(f"合并后形状: {merged_right.shape}")
print("注意:C008 客户没有订单记录,订单信息为 NaN")
merged_right[['订单号', '客户ID', '姓名', '订单金额']].tail(5)
右连接:保留所有客户,即使他们没有订单
合并后形状: (11, 11)
注意:C008 客户没有订单记录,订单信息为 NaN
| | 订单号 | 客户ID | 姓名 | 订单金额 |
| 6 | ORD006 | C004 | 刘洋 | 999.0 |
| 7 | ORD007 | C005 | 陈静 | 4560.0 |
| 8 | ORD009 | C006 | 赵敏 | 2340.0 |
| 9 | ORD010 | C007 | 周杰 | 1560.0 |
| 10 | NaN | C008 | 吴刚 | NaN |
|---|
7.1.4 外连接 (outer) - 保留两表所有记录
python
# 外连接:保留所有订单和所有客户
print("外连接:保留所有订单和所有客户")
merged_outer = pd.merge(df_orders_extra, df_customers, on='客户ID', how='outer')
print(f"合并后形状: {merged_outer.shape}")
print("包含:所有订单 + 所有客户(包括没有订单的C008)")
merged_outer[['订单号', '客户ID', '姓名', '订单金额']].tail(6)
外连接:保留所有订单和所有客户
合并后形状: (12, 11)
包含:所有订单 + 所有客户(包括没有订单的C008)
| | 订单号 | 客户ID | 姓名 | 订单金额 |
| 6 | ORD006 | C004 | 刘洋 | 999.0 |
| 7 | ORD007 | C005 | 陈静 | 4560.0 |
| 8 | ORD009 | C006 | 赵敏 | 2340.0 |
| 9 | ORD010 | C007 | 周杰 | 1560.0 |
| 10 | NaN | C008 | 吴刚 | NaN |
| 11 | ORD011 | C999 | NaN | 1000.0 |
|---|
7.1.5 连接方式对比总结
| 连接方式 | 说明 | 保留记录 |
|---|---|---|
inner |
内连接 | 两表键都存在的记录(交集) |
left |
左连接 | 左表所有记录,右表缺失填 NaN |
right |
右连接 | 右表所有记录,左表缺失填 NaN |
outer |
外连接 | 两表所有记录(并集),缺失填 NaN |
inner
left
right
outer
左表
交集
两表共有
左表全部
+右表匹配
右表全部
+左表匹配
并集
两表全部
7.1.6 多键合并
python
# 使用多个键进行合并
print("使用多个键进行合并:")
print("创建带有年月列的订单表和目标表:")
df_orders['年月'] = df_orders['订单日期'].dt.to_period('M').astype(str)
df_monthly_sales = df_orders.groupby('年月')['订单金额'].sum().reset_index()
df_monthly_sales.columns = ['月份', '实际销售额']
print("月度销售额:")
df_monthly_sales
使用多个键进行合并:
创建带有年月列的订单表和目标表:
月度销售额:
| | 月份 | 实际销售额 |
| 0 | 2026-01 | 9790.5 |
| 1 | 2026-02 | 8679.0 |
| 2 | 2026-03 | 1560.0 |
|---|
python
# 合并销售目标和实际销售额
print("合并销售目标和实际销售额:")
merged_targets = pd.merge(df_targets, df_monthly_sales, on='月份', how='left')
merged_targets
合并销售目标和实际销售额:
| | 月份 | 目标金额 | 实际完成 | 实际销售额 |
| 0 | 2026-01 | 50000 | 48500 | 9790.5 |
| 1 | 2026-02 | 60000 | 62000 | 8679.0 |
| 2 | 2026-03 | 55000 | 0 | 1560.0 |
| 3 | 2026-04 | 70000 | 0 | NaN |
|---|
7.1.7 处理重复列名
python
# 两表有相同列名时的处理
print("两表有相同列名时的处理:")
df_customers_copy = df_customers.copy()
df_customers_copy['备注'] = 'VIP客户'
df_orders_copy = df_orders.copy()
df_orders_copy['备注'] = '正常订单'
print("使用 suffixes 参数处理重复列名:")
merged_suffix = pd.merge(df_orders_copy[['订单号', '客户ID', '备注']],
df_customers_copy[['客户ID', '姓名', '备注']],
on='客户ID',
suffixes=('_订单', '_客户'))
merged_suffix.head()
两表有相同列名时的处理:
使用 suffixes 参数处理重复列名:
| | 订单号 | 客户ID | 备注_订单 | 姓名 | 备注_客户 |
| 0 | ORD001 | C001 | 正常订单 | 张伟 | VIP客户 |
| 1 | ORD002 | C002 | 正常订单 | 李娜 | VIP客户 |
| 2 | ORD003 | C001 | 正常订单 | 张伟 | VIP客户 |
| 3 | ORD004 | C003 | 正常订单 | 王强 | VIP客户 |
| 4 | ORD005 | C002 | 正常订单 | 李娜 | VIP客户 |
|---|
python
# 使用 indicator 参数显示记录来源
print("使用 indicator 参数显示记录来源:")
merged_indicator = pd.merge(df_orders, df_customers,
on='客户ID',
how='outer',
indicator=True)
merged_indicator[['订单号', '客户ID', '姓名', '_merge']].tail(6)
使用 indicator 参数显示记录来源:
| | 订单号 | 客户ID | 姓名 | _merge |
| 5 | ORD004 | C003 | 王强 | both |
| 6 | ORD006 | C004 | 刘洋 | both |
| 7 | ORD007 | C005 | 陈静 | both |
| 8 | ORD009 | C006 | 赵敏 | both |
| 9 | ORD010 | C007 | 周杰 | both |
| 10 | NaN | C008 | 吴刚 | right_only |
|---|
7.2 join() - 基于索引的表连接
join() 适用于索引已设置好的数据,默认左连接。
set_index
join
DataFrame
设置索引
基于索引连接
单索引join
多索引join
7.2.1 设置索引后使用 join()
python
# 将客户ID设为索引后使用 join()
print("将客户ID设为索引后使用 join():")
df_orders_idx = df_orders.set_index('客户ID')
df_customers_idx = df_customers.set_index('客户ID')
print("订单表(索引为客户ID):")
print(df_orders_idx[['订单号', '订单金额']].head())
joined = df_orders_idx.join(df_customers_idx[['姓名', '城市', '会员等级']],
how='left')
print("\njoin() 结果:")
joined[['订单号', '订单金额', '姓名', '城市']].head()
将客户ID设为索引后使用 join():
订单表(索引为客户ID):
订单号 订单金额
客户ID
C001 ORD001 1250.0
C002 ORD002 3680.5
C001 ORD003 520.0
C003 ORD004 1890.0
C002 ORD005 2450.0
join() 结果:
| | 订单号 | 订单金额 | 姓名 | 城市 |
| 客户ID | | | | |
| C001 | ORD001 | 1250.0 | 张伟 | 北京 |
| C002 | ORD002 | 3680.5 | 李娜 | 上海 |
| C001 | ORD003 | 520.0 | 张伟 | 北京 |
| C003 | ORD004 | 1890.0 | 王强 | 广州 |
| C002 | ORD005 | 2450.0 | 李娜 | 上海 |
|---|
7.2.2 join() 与 merge() 对比
| 特性 | join() | merge() |
|---|---|---|
| 连接依据 | 基于索引 | 基于指定列 |
| 默认连接方式 | left(左连接) | inner(内连接) |
| 多列连接 | 支持多索引 | 支持多列 |
| 适用场景 | 索引已设置好的数据 | 通用场景,更灵活 |
| 性能 | 通常更快(索引已排序) | 需要额外哈希操作 |
python
# 多索引 join 示例
print("多索引 join 示例:")
df_orders_multi = df_orders.set_index(['客户ID', '订单号'])
df_customers_multi = df_customers.set_index('客户ID')
joined_multi = df_orders_multi.join(df_customers_multi[['姓名', '城市']])
print("多索引 join 结果(前5行):")
joined_multi[['订单金额', '姓名', '城市']].head()
多索引 join 示例:
多索引 join 结果(前5行):
| | | 订单金额 | 姓名 | 城市 |
| 客户ID | 订单号 | | | |
| C001 | ORD001 | 1250.0 | 张伟 | 北京 |
| C002 | ORD002 | 3680.5 | 李娜 | 上海 |
| C001 | ORD003 | 520.0 | 张伟 | 北京 |
| C003 | ORD004 | 1890.0 | 王强 | 广州 |
| C002 | ORD005 | 2450.0 | 李娜 | 上海 |
|---|
7.3 concat() - 数据拼接
concat() 用于将多个 DataFrame 堆叠或并排拼接。
axis=0
axis=1
concat拼接
纵向拼接
增加行
横向拼接
增加列
ignore_index
重置索引
keys
层次化索引
7.3.1 纵向拼接(行方向,axis=0)
python
# 纵向拼接多个DataFrame
print("纵向拼接多个DataFrame:")
print("模拟两个月份的订单数据:")
df_jan = df_orders[df_orders['订单日期'] < '2026-02-01'].copy()
df_feb = df_orders[(df_orders['订单日期'] >= '2026-02-01') &
(df_orders['订单日期'] < '2026-03-01')].copy()
print(f" 1月订单数: {len(df_jan)}")
print(f" 2月订单数: {len(df_feb)}")
df_combined = pd.concat([df_jan, df_feb], axis=0)
print(f" 合并后订单数: {len(df_combined)}")
df_combined[['订单号', '订单日期', '订单金额']]
纵向拼接多个DataFrame:
模拟两个月份的订单数据:
1月订单数: 5
2月订单数: 4
合并后订单数: 9
| | 订单号 | 订单日期 | 订单金额 |
| 0 | ORD001 | 2026-01-05 | 1250.0 |
| 1 | ORD002 | 2026-01-08 | 3680.5 |
| 2 | ORD003 | 2026-01-12 | 520.0 |
| 3 | ORD004 | 2026-01-15 | 1890.0 |
| 4 | ORD005 | 2026-01-20 | 2450.0 |
| 5 | ORD006 | 2026-02-03 | 999.0 |
| 6 | ORD007 | 2026-02-10 | 4560.0 |
| 7 | ORD008 | 2026-02-18 | 780.0 |
| 8 | ORD009 | 2026-02-25 | 2340.0 |
|---|
python
# 使用 ignore_index 重置索引
print("使用 ignore_index 重置索引:")
df_combined_reset = pd.concat([df_jan, df_feb], axis=0, ignore_index=True)
print(f"重置索引后,索引范围: 0 到 {len(df_combined_reset)-1}")
df_combined_reset[['订单号', '订单日期']].head()
使用 ignore_index 重置索引:
重置索引后,索引范围: 0 到 8
| | 订单号 | 订单日期 |
| 0 | ORD001 | 2026-01-05 |
| 1 | ORD002 | 2026-01-08 |
| 2 | ORD003 | 2026-01-12 |
| 3 | ORD004 | 2026-01-15 |
| 4 | ORD005 | 2026-01-20 |
|---|
python
# 使用 keys 参数创建层次化索引
print("使用 keys 参数创建层次化索引:")
df_with_keys = pd.concat([df_jan, df_feb], axis=0, keys=['1月', '2月'])
print("层次化索引结果:")
df_with_keys[['订单号', '订单金额']].head()
使用 keys 参数创建层次化索引:
层次化索引结果:
| | | 订单号 | 订单金额 |
| 1月 | 0 | ORD001 | 1250.0 |
| 1月 | 1 | ORD002 | 3680.5 |
| 1月 | 2 | ORD003 | 520.0 |
| 1月 | 3 | ORD004 | 1890.0 |
| 1月 | 4 | ORD005 | 2450.0 |
|---|
7.3.2 横向拼接(列方向,axis=1)
python
# 横向拼接(需要相同索引)
print("横向拼接(需要相同索引):")
print("创建两个具有相同索引的DataFrame:")
df_sales = pd.DataFrame({
'销售额': [1000, 2000, 1500, 3000]
}, index=['北京', '上海', '广州', '深圳'])
df_profit = pd.DataFrame({
'利润': [200, 400, 300, 600]
}, index=['北京', '上海', '广州', '深圳'])
df_side_by_side = pd.concat([df_sales, df_profit], axis=1)
print("横向拼接结果:")
df_side_by_side
横向拼接(需要相同索引):
创建两个具有相同索引的DataFrame:
横向拼接结果:
| | 销售额 | 利润 |
| 北京 | 1000 | 200 |
| 上海 | 2000 | 400 |
| 广州 | 1500 | 300 |
| 深圳 | 3000 | 600 |
|---|
python
# 横向拼接时索引不一致的处理
print("横向拼接时索引不一致的处理:")
df_sales_new = pd.DataFrame({
'销售额': [1200, 2200]
}, index=['北京', '杭州'])
df_side_join = pd.concat([df_sales, df_sales_new], axis=1)
print("索引不一致时,缺失值填NaN:")
df_side_join
横向拼接时索引不一致的处理:
索引不一致时,缺失值填NaN:
| | 销售额 | 销售额 |
| 北京 | 1000.0 | 1200.0 |
| 上海 | 2000.0 | NaN |
| 广州 | 1500.0 | NaN |
| 深圳 | 3000.0 | NaN |
| 杭州 | NaN | 2200.0 |
|---|
7.3.3 concat() 与 merge() 对比
| 特性 | concat() | merge() |
|---|---|---|
| 主要用途 | 数据拼接/堆叠 | 表连接/关联 |
| 连接依据 | 基于索引对齐 | 基于指定键连接 |
| 方向 | axis=0纵向/axis=1横向 | 列方向扩展 |
| 表数量 | 可同时拼接多个表 | 一次合并两个表 |
| 适用场景 | 相同结构数据堆叠 | 不同结构数据关联 |
7.4 实战场景:多表关联分析
商品ID
订单号
客户ID
订单明细表
商品表
订单表
客户表
7.4.1 订单明细与商品表关联
python
# 订单明细与商品表关联
print("订单明细与商品表关联:")
items_with_product = pd.merge(df_order_items, df_products, on='商品ID', how='left')
items_with_product['小计'] = items_with_product['单价'] * items_with_product['数量'] * items_with_product['折扣']
items_with_product[['订单号', '商品名称', '单价', '数量', '折扣', '小计']]
订单明细与商品表关联:
| | 订单号 | 商品名称 | 单价 | 数量 | 折扣 | 小计 |
| 0 | ORD001 | iPhone 16 Pro | 8999 | 1 | 0.95 | 8549.05 |
| 1 | ORD001 | AirPods Pro | 1999 | 2 | 1.00 | 3998.00 |
| 2 | ORD002 | MacBook Air | 9499 | 1 | 0.90 | 8549.10 |
| 3 | ORD003 | 小米14 | 3999 | 1 | 1.00 | 3999.00 |
| 4 | ORD004 | iPhone 16 Pro | 8999 | 1 | 0.95 | 8549.05 |
| 5 | ORD005 | iPad Air | 4799 | 1 | 1.00 | 4799.00 |
| 6 | ORD005 | 华为Mate60 | 6999 | 1 | 0.85 | 5949.15 |
| 7 | ORD006 | AirPods Pro | 1999 | 3 | 1.00 | 5997.00 |
| 8 | ORD007 | MacBook Air | 9499 | 2 | 0.90 | 17098.20 |
| 9 | ORD008 | 戴森吸尘器 | 2999 | 1 | 0.80 | 2399.20 |
| 10 | ORD009 | 索尼耳机 | 1299 | 2 | 1.00 | 2598.00 |
| 11 | ORD010 | 小米14 | 3999 | 1 | 0.95 | 3799.05 |
|---|
7.4.2 订单与客户信息关联
python
# 订单与客户信息关联
print("订单与客户信息关联:")
orders_with_customer = pd.merge(df_orders, df_customers[['客户ID', '姓名', '城市', '会员等级']],
on='客户ID', how='left')
orders_with_customer[['订单号', '客户ID', '姓名', '城市', '会员等级', '订单金额']]
订单与客户信息关联:
| | 订单号 | 客户ID | 姓名 | 城市 | 会员等级 | 订单金额 |
| 0 | ORD001 | C001 | 张伟 | 北京 | 金牌 | 1250.0 |
| 1 | ORD002 | C002 | 李娜 | 上海 | 银牌 | 3680.5 |
| 2 | ORD003 | C001 | 张伟 | 北京 | 金牌 | 520.0 |
| 3 | ORD004 | C003 | 王强 | 广州 | 铜牌 | 1890.0 |
| 4 | ORD005 | C002 | 李娜 | 上海 | 银牌 | 2450.0 |
| 5 | ORD006 | C004 | 刘洋 | 深圳 | 金牌 | 999.0 |
| 6 | ORD007 | C005 | 陈静 | 北京 | 银牌 | 4560.0 |
| 7 | ORD008 | C001 | 张伟 | 北京 | 金牌 | 780.0 |
| 8 | ORD009 | C006 | 赵敏 | 上海 | 铜牌 | 2340.0 |
| 9 | ORD010 | C007 | 周杰 | 广州 | 金牌 | 1560.0 |
|---|
7.4.3 完整的多表关联(星型模型)
python
# 完整的多表关联(星型模型)
print("完整的多表关联(星型模型):")
# 步骤1:订单明细 + 商品信息
step1 = pd.merge(df_order_items, df_products[['商品ID', '商品名称', '类别', '单价']],
on='商品ID', how='left')
# 步骤2:关联订单信息
step2 = pd.merge(step1, df_orders[['订单号', '客户ID', '订单日期']],
on='订单号', how='left')
# 步骤3:关联客户信息
full_data = pd.merge(step2, df_customers[['客户ID', '姓名', '城市', '会员等级']],
on='客户ID', how='left')
# 计算实际金额
full_data['实际金额'] = full_data['单价'] * full_data['数量'] * full_data['折扣']
print("完整关联结果:")
full_data[['订单号', '商品名称', '类别', '单价', '数量', '折扣', '实际金额', '姓名', '城市']].head(10)
完整的多表关联(星型模型):
完整关联结果:
| | 订单号 | 商品名称 | 类别 | 单价 | 数量 | 折扣 | 实际金额 | 姓名 | 城市 |
| 0 | ORD001 | iPhone 16 Pro | 手机 | 8999 | 1 | 0.95 | 8549.05 | 张伟 | 北京 |
| 1 | ORD001 | AirPods Pro | 配件 | 1999 | 2 | 1.00 | 3998.00 | 张伟 | 北京 |
| 2 | ORD002 | MacBook Air | 电脑 | 9499 | 1 | 0.90 | 8549.10 | 李娜 | 上海 |
| 3 | ORD003 | 小米14 | 手机 | 3999 | 1 | 1.00 | 3999.00 | 张伟 | 北京 |
| 4 | ORD004 | iPhone 16 Pro | 手机 | 8999 | 1 | 0.95 | 8549.05 | 王强 | 广州 |
| 5 | ORD005 | iPad Air | 平板 | 4799 | 1 | 1.00 | 4799.00 | 李娜 | 上海 |
| 6 | ORD005 | 华为Mate60 | 手机 | 6999 | 1 | 0.85 | 5949.15 | 李娜 | 上海 |
| 7 | ORD006 | AirPods Pro | 配件 | 1999 | 3 | 1.00 | 5997.00 | 刘洋 | 深圳 |
| 8 | ORD007 | MacBook Air | 电脑 | 9499 | 2 | 0.90 | 17098.20 | 陈静 | 北京 |
| 9 | ORD008 | 戴森吸尘器 | 家电 | 2999 | 1 | 0.80 | 2399.20 | 张伟 | 北京 |
|---|
7.4.4 分组统计与报表生成
python
# 分组统计与报表生成
print("分组统计与报表生成:")
# 按城市统计销售额
city_sales = full_data.groupby('城市')['实际金额'].sum().reset_index()
city_sales.columns = ['城市', '销售额']
city_sales = city_sales.sort_values('销售额', ascending=False)
print("按城市统计销售额:")
city_sales
分组统计与报表生成:
按城市统计销售额:
| | 城市 | 销售额 |
| 1 | 北京 | 36043.45 |
| 0 | 上海 | 21895.25 |
| 2 | 广州 | 12348.10 |
| 3 | 深圳 | 5997.00 |
|---|
python
# 按商品类别统计
print("按商品类别统计销售额:")
category_sales = full_data.groupby('类别')['实际金额'].sum().reset_index()
category_sales.columns = ['类别', '销售额']
category_sales = category_sales.sort_values('销售额', ascending=False)
category_sales
按商品类别统计销售额:
| | 类别 | 销售额 |
| 2 | 手机 | 30845.3 |
| 3 | 电脑 | 25647.3 |
| 4 | 配件 | 12593.0 |
| 1 | 平板 | 4799.0 |
| 0 | 家电 | 2399.2 |
|---|
7.5 最佳实践与性能优化
最佳实践
选择合适方法
处理重复列名
验证合并关系
性能优化
有共同列用merge
索引已设置用join
多表堆叠用concat
设置索引
先排序再合并
只选需要的列
7.5.1 方法选择建议
python
# 方法选择建议总结
print("方法选择建议:")
print("""
1. 两表有共同列 → 使用 merge()
pd.merge(df1, df2, on='共同列', how='left')
2. 索引已设置好 → 使用 join()
df1.set_index('键').join(df2.set_index('键'))
3. 多表堆叠/拼接 → 使用 concat()
pd.concat([df1, df2, df3], axis=0)
4. 需要验证关系 → 使用 merge() + validate
pd.merge(df1, df2, on='键', validate='one_to_many')
""")
方法选择建议:
1. 两表有共同列 → 使用 merge()
pd.merge(df1, df2, on='共同列', how='left')
2. 索引已设置好 → 使用 join()
df1.set_index('键').join(df2.set_index('键'))
3. 多表堆叠/拼接 → 使用 concat()
pd.concat([df1, df2, df3], axis=0)
4. 需要验证关系 → 使用 merge() + validate
pd.merge(df1, df2, on='键', validate='one_to_many')
7.5.2 性能优化技巧
python
# 性能优化示例
print("性能优化示例(基于索引的 join 更快):")
# 设置索引
df_orders_idx = df_orders.set_index('客户ID')
df_customers_idx = df_customers.set_index('客户ID')
# 使用 join 替代 merge
result = df_orders_idx.join(df_customers_idx[['姓名', '城市', '会员等级']])
print("使用 join 的结果:")
result[['订单号', '订单金额', '姓名', '城市']].head()
性能优化示例(基于索引的 join 更快):
使用 join 的结果:
| | 订单号 | 订单金额 | 姓名 | 城市 |
| 客户ID | | | | |
| C001 | ORD001 | 1250.0 | 张伟 | 北京 |
| C002 | ORD002 | 3680.5 | 李娜 | 上海 |
| C001 | ORD003 | 520.0 | 张伟 | 北京 |
| C003 | ORD004 | 1890.0 | 王强 | 广州 |
| C002 | ORD005 | 2450.0 | 李娜 | 上海 |
|---|
本章小结
学习内容回顾
1. merge() - 数据库风格的表连接
| 参数 | 说明 | 示例 |
|---|---|---|
on |
指定连接键 | on='客户ID' |
how |
连接方式 | how='left' |
suffixes |
重复列后缀 | suffixes=('_左', '_右') |
indicator |
显示记录来源 | indicator=True |
2. join() - 基于索引的表连接
- 适用于索引已设置好的数据
- 默认左连接,性能通常优于 merge()
- 支持多索引连接
3. concat() - 数据拼接
| 参数 | 说明 | 示例 |
|---|---|---|
axis |
拼接方向 | axis=0纵向/axis=1横向 |
ignore_index |
重置索引 | ignore_index=True |
keys |
层次化索引 | keys=['1月', '2月'] |
4. 连接方式选择
| 方式 | 保留记录 | 适用场景 |
|---|---|---|
inner |
交集 | 只关心匹配的数据 |
left |
左表全部 | 保留主表所有记录 |
right |
右表全部 | 保留参考表所有记录 |
outer |
并集 | 保留所有数据 |
下章预告
第八章:数据重塑
将学习数据重塑的高级操作,包括:
- pivot 数据透视
- melt 数据融合
- stack/unstack 层次化索引
- pivot_table 透视表
课后练习
练习1:使用 merge() 完成以下操作:
- 将订单表与客户表进行内连接、左连接、右连接、外连接,观察结果差异
- 使用 suffixes 处理两表中可能存在的重复列名
- 使用 indicator 参数查看记录来源
练习2:使用 join() 完成以下操作:
- 将订单表和客户表分别设置索引后使用 join()
- 比较 join() 和 merge() 的结果差异
练习3:使用 concat() 完成以下操作:
- 将多个月份的订单数据纵向拼接
- 使用 keys 参数创建层次化索引
- 使用 ignore_index 重置索引
练习4:多表关联实战:
- 创建客户表、订单表、商品表、订单明细表
- 实现完整的星型模型关联
- 生成按城市、按商品类别的销售报表
练习5:性能优化实践:
- 对比 merge() 和 join() 在大数据集上的性能
- 测试设置索引对性能的影响