🐍 Python Day75:Pandas 数据合并 — 多表关联与透视表
🕐 预计用时:2-3 小时 | 🎯 目标:掌握 merge、concat、join、透视表和交叉表
📖 今日目录
1. merge(类似 SQL JOIN)
import pandas as pd# 订单表orders = pd.DataFrame({ '订单号': [1001, 1002, 1003, 1004, 1005], '客户ID': [1, 2, 1, 3, 2], '金额': [100, 200, 150, 300, 250]})# 客户表customers = pd.DataFrame({ '客户ID': [1, 2, 3, 4], '姓名': ['小明', '小红', '小刚', '小美'], '城市': ['北京', '上海', '广州', '深圳']})# === 内连接(默认)—— 只保留两表都有的 ===pd.merge(orders, customers, on='客户ID')# 订单号 客户ID 金额 姓名 城市# 0 1001 1 100 小明 北京# 1 1002 2 200 小红 上海# 2 1003 1 150 小明 北京# 3 1004 3 300 小刚 广州# 4 1005 2 250 小红 上海# 注意:客户ID=4(小美)没有出现,因为她没有订单# merge 默认 sort=False,保留左表(orders)的行顺序# === 左连接 —— 保留左表所有行 ===pd.merge(orders, customers, on='客户ID', how='left')# === 右连接 —— 保留右表所有行 ===pd.merge(orders, customers, on='客户ID', how='right')# 小美(客户ID=4)也会出现,金额为 NaN# === 外连接 —— 保留所有行 ===pd.merge(orders, customers, on='客户ID', how='outer')# === 列名不同时用 left_on / right_on ===df1 = pd.DataFrame({'客户编号': [1, 2], '金额': [100, 200]})df2 = pd.DataFrame({'id': [1, 2], '姓名': ['小明', '小红']})pd.merge(df1, df2, left_on='客户编号', right_on='id')# === 多列合并 ===pd.merge(df1, df2, on=['列1', '列2'])
| | | |
|---|
| | how='inner' | |
| | how='left' | |
| | how='right' | |
| | how='outer' | |
2. concat(拼接)
import pandas as pd# 纵向拼接(上下堆叠)—— 行数增加df1 = pd.DataFrame({'A': [1, 2], 'B': [3, 4]})df2 = pd.DataFrame({'A': [5, 6], 'B': [7, 8]})pd.concat([df1, df2])# A B# 0 1 3# 1 2 4# 0 5 7# 1 6 8# 重置索引pd.concat([df1, df2], ignore_index=True)# 横向拼接(左右拼接)—— 列数增加df1 = pd.DataFrame({'A': [1, 2]})df2 = pd.DataFrame({'B': [3, 4]})pd.concat([df1, df2], axis=1)# A B# 0 1 3# 1 2 4# 列名不完全一致时df1 = pd.DataFrame({'A': [1, 2], 'B': [3, 4]})df2 = pd.DataFrame({'B': [5, 6], 'C': [7, 8]})pd.concat([df1, df2], ignore_index=True, sort=True)# 缺失的列用 NaN 填充# 标记来源pd.concat([df1, df2], keys=['表1', '表2'])# A B C# 表1 0 1 3 NaN# 1 2 4 NaN# 表2 0 NaN 5 7# 1 NaN 6 8
💡 merge vs concat:• merge:按某列关联(类似 SQL JOIN)→ 左右扩展• concat:简单拼接(上下堆叠或左右拼接)→ 不需要关联键问自己:两张表有没有"共同字段"要关联?有 → 用 merge | 没有 → 用 concat
3. join(索引合并)
import pandas as pd# join 是 merge 的简化版——基于索引合并df1 = pd.DataFrame({'A': [1, 2, 3]}, index=['a', 'b', 'c'])df2 = pd.DataFrame({'B': [4, 5, 6]}, index=['a', 'b', 'd'])df1.join(df2) # 左连接(默认)# A B# a 1 4.0# b 2 5.0# c 3 NaNdf1.join(df2, how='inner') # 内连接# A B# a 1 4# b 2 5df1.join(df2, how='outer') # 外连接# A B# a 1.0 4.0# b 2.0 5.0# c 3.0 NaN# d NaN 6.0# 多表 joindf3 = pd.DataFrame({'C': [7, 8]}, index=['a', 'b'])df1.join([df2, df3])
4. 透视表(pivot_table)
import pandas as pddf = pd.DataFrame({ '日期': ['2026-01', '2026-01', '2026-02', '2026-02', '2026-03', '2026-03'], '部门': ['技术', '市场', '技术', '市场', '技术', '市场'], '城市': ['北京', '上海', '北京', '上海', '北京', '上海'], '薪资': [15000, 12000, 18000, 15000, 16000, 13000]})# === 基本透视表 ===df.pivot_table( values='薪资', # 要聚合的值 index='部门', # 行 aggfunc='mean' # 聚合函数)# 薪资# 部门# 市场 13333# 技术 16333# === 多维透视表 ===df.pivot_table( values='薪资', index='部门', columns='城市', aggfunc='mean', fill_value=0)# 城市 上海 北京# 部门# 市场 13333 0# 技术 0 16333# 注:数据中"市场"只在"上海","技术"只在"北京",交叉组合无数据用 fill_value=0 填充# === 多个聚合函数 ===df.pivot_table( values='薪资', index='部门', aggfunc=['mean', 'sum', 'count'])# === 边际汇总(margins=True)===df.pivot_table( values='薪资', index='部门', columns='城市', aggfunc='sum', margins=True, # 添加行列合计 margins_name='合计')# === 类似 SQL 的分组统计 ===# 每个部门的平均薪资、最高薪资、人数df.pivot_table( values='薪资', index='部门', aggfunc={'薪资': ['mean', 'max', 'count']})
5. 交叉表(crosstab)
import pandas as pd# 交叉表:统计两个分类变量的频次df = pd.DataFrame({ '性别': ['男', '女', '男', '女', '男', '女', '男'], '是否吸烟': ['是', '否', '是', '是', '否', '否', '否'], '年龄': [25, 30, 35, 28, 40, 22, 33]})pd.crosstab(df['性别'], df['是否吸烟'])# 是否吸烟 否 是# 性别# 女 2 1# 男 2 2# 带归一化(比例)pd.crosstab(df['性别'], df['是否吸烟'], normalize='index') # 按行归一化pd.crosstab(df['性别'], df['是否吸烟'], normalize='columns') # 按列归一化# 带边际汇总pd.crosstab(df['性别'], df['是否吸烟'], margins=True)# 带聚合值pd.crosstab( df['性别'], df['是否吸烟'], values=df['年龄'], # 聚合的值 aggfunc='mean' # 平均年龄)
6. melt(宽表转长表)
import pandas as pd# 宽表(人好读)wide = pd.DataFrame({ '姓名': ['小明', '小红'], '语文': [90, 85], '数学': [95, 88], '英语': [88, 92]})# 宽表 → 长表(机器好处理)long = pd.melt( wide, id_vars=['姓名'], # 保持不变的列 var_name='科目', # 列名 → 新列名 value_name='成绩' # 值 → 新列名)# 姓名 科目 成绩# 0 小明 语文 90# 1 小红 语文 85# 2 小明 数学 95# 3 小红 数学 88# 4 小明 英语 88# 5 小红 英语 92# 长表 → 宽表(人好读)wide_again = long.pivot_table( index='姓名', columns='科目', values='成绩').reset_index()
💡 宽表 vs 长表:• 宽表:每列一个变量,人好读 → 适合报表展示• 长表:所有值在一列,机器好处理 → 适合数据分析、可视化melt() = 宽 → 长 | pivot_table() = 长 → 宽
7. 今日练习
🏋️ 练习 1:订单分析
# 创建订单表和商品表:# 1. 用 merge 关联,得到每笔订单的商品名称和单价# 2. 计算每个商品的总销售额# 3. 用 pivot_table 生成 商品×月份 的销售额透视表
🏋️ 练习 2:多表拼接
# 有 3 个月的销售数据(1月、2月、3月各一个 CSV)# 1. 用 concat 合并为一个 DataFrame# 2. 添加"月份"列标记来源# 3. 按月份分组统计总销售额
🏋️ 练习 3:宽表长表转换
# 给定一个宽表格式的成绩单:# 姓名 | 语文 | 数学 | 英语# 1. 用 melt 转为长表# 2. 用 pivot_table 转回宽表# 3. 用 crosstab 统计各等级的人数
8. 今日小结
| |
|---|
| 按列关联(inner/left/right/outer),类似 SQL JOIN |
| 简单拼接(axis=0 上下 / axis=1 左右) |
| |
| 透视表:values + index + columns + aggfunc |
| |
| 宽表转长表:id_vars + var_name + value_name |
🎉 Pandas 阶段完结!Day 73-75 你掌握了 Pandas 三大核心:✅ 数据读写 · 索引筛选 · 缺失值处理✅ 分组聚合 · 数据合并 · 透视表 · 宽长表转换接下来进入 Matplotlib 可视化(Day 76)—— 把数据变成图表,让数据"说话"!