Pandas数据分析 - 第七章:数据合并与连接

第七章:数据合并与连接

📋 章节概述

本章将学习 Pandas 中数据合并与连接的核心技术。掌握这些方法后,你将能够灵活地整合多个数据源,完成复杂的数据关联分析。

🎯 学习目标

  1. 掌握 merge() 方法进行数据库风格的表连接
  2. 掌握 join() 方法进行基于索引的表连接
  3. 掌握 concat() 方法进行数据拼接
  4. 理解各种连接方式(内连接、外连接、左连接、右连接)
  5. 学会处理合并时的重复列名和索引问题
  6. 掌握实际业务场景中的多表关联技巧

📊 知识结构图

数据合并与连接
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() 在大数据集上的性能
  • 测试设置索引对性能的影响
相关推荐
gushinghsjj2 小时前
元数据管理包含哪些?元数据管理如何支持数据分析?
数据库·oracle·数据分析
编程界一哥3 小时前
2026最新:原神PC启动提示缺失msvcp140.dll,安全修复工具哪家强?
数据挖掘
qyr67893 小时前
全球蜂窝分布式天线系统市场报告2026-2032
大数据·人工智能·数据分析·市场报告·蜂窝分布式天线系统
泰迪智能科技013 小时前
分享|大数据挖掘建模平台赋能企业智能决策与数字化转型
人工智能·数据挖掘
新知图书3 小时前
微软Power BI主要架构
数据分析·power bi·商务数据分析
企业架构师老王4 小时前
2026电力能源巡检进化论:如何基于企业级AI Agent构建非侵入式数据分析架构?
人工智能·ai·数据分析·能源
Data-Miner4 小时前
用AI数据分析工具重构工作流:从“写公式“到“说人话“
重构·数据挖掘·数据分析
m0_6948455713 小时前
Dify部署教程:从AI原型到生产系统的一站式方案
服务器·人工智能·python·数据分析·开源
MediaTea14 小时前
Pandas 应用实例:多工具协同与数据可视化
信息可视化·pandas