python

关注公众号 jb51net

关闭
首页 > 脚本专栏 > python > Pandas数据合并

Pandas多源数据整合之三大数据合并方法(Merge,Join与Concat )详解

作者:小庄-Python办公

本文介绍了Pandas中三种数据整合方法,即merge、join和concat,重点讲解了merge函数的四种连接方式(内连接、左连接、右连接、外连接)及其参数使用,希望对大家有所帮助

本节学习目标

为什么学这个?

在真实工作环境中,数据很少只存在于一张表中。以电商系统为例:

当你想回答"2024年购买电子产品最多的用户是谁"这个问题时,你需要同时关联这三张表

这正是本节的重点:多源数据整合

Pandas 提供了三种主要的数据合并方式:

打个比方:如果说 groupby 是把数据"拆分再汇总",那么 merge/join/concat 就是把散落各处的数据"拼起来"。前者是拆解分析,后者是整合重组,两者同等重要。

核心知识点讲解

merge:基于列值的关联

merge 是 Pandas 中最常用的数据关联方法,它与 SQL 数据库中的 JOIN 操作非常类似。

1. 准备示例数据

import pandas as pd
import numpy as np

# 订单表
orders = pd.DataFrame({
    '订单ID': [1001, 1002, 1003, 1004, 1005],
    '用户ID': [1, 2, 3, 1, 4],
    '金额': [500, 300, 800, 200, 1500],
    '日期': ['2024-01-15', '2024-02-20', '2024-03-10', '2024-04-05', '2024-05-18']
})

# 用户表
users = pd.DataFrame({
    '用户ID': [1, 2, 3, 5],
    '姓名': ['张三', '李四', '王五', '赵六'],
    '城市': ['北京', '上海', '广州', '深圳']
})

# 产品表
products = pd.DataFrame({
    '订单ID': [1001, 1002, 1003, 1004, 1005],
    '产品名': ['手机', '耳机', '电脑', '充电器', '手表'],
    '品类': ['电子产品', '配件', '电子产品', '配件', '饰品']
})

print("订单表:")
print(orders)
print("\n用户表:")
print(users)
print("\n产品表:")
print(products)

2. 内连接(inner join)

内连接只保留两个表中都有的匹配行。

# 内连接:只保留有用户信息的订单
result = pd.merge(orders, users, on='用户ID', how='inner')
print("内连接(只保留有用户的订单):")
print(result)

# 注意:用户ID=4 的订单被丢弃了(因为用户表中没有ID=4的用户)
# 用户ID=5 的赵六也被丢弃了(因为订单表中没有他的订单)

Venn 图理解:inner join 就像两个圆圈的交集部分

3. 左连接(left join)

左连接保留左表的所有行,右表中没有匹配的填充 NaN。

# 左连接:保留所有订单,匹配不到的用户信息为 NaN
result = pd.merge(orders, users, on='用户ID', how='left')
print("左连接(保留所有订单):")
print(result)

# 订单ID=1005 的订单保留了,但用户信息(姓名、城市)为 NaN
# 因为用户表中没有用户ID=4的记录

左连接是最常用的连接方式,因为它保证了主表(左表)的数据完整性。

4. 右连接(right join)

右连接保留右表的所有行,左表中没有匹配的填充 NaN。

# 右连接:保留所有用户,没有订单的用户订单信息为 NaN
result = pd.merge(orders, users, on='用户ID', how='right')
print("右连接(保留所有用户):")
print(result)

# 赵六(用户ID=5)保留了,但订单信息为 NaN
# 因为他没有下过订单

5. 外连接(outer join)

外连接保留两个表的所有行,没有匹配的填充 NaN。

# 外连接:保留所有订单和所有用户
result = pd.merge(orders, users, on='用户ID', how='outer')
print("外连接(保留所有):")
print(result)

6. merge 参数详解

# ===== on / left_on / right_on =====

# on=:两个表的关联列名相同
result = pd.merge(orders, users, on='用户ID')

# left_on / right_on:关联列名不同
users2 = pd.DataFrame({
    'ID': [1, 2, 3, 5],  # 列名不同
    '姓名': ['张三', '李四', '王五', '赵六'],
    '城市': ['北京', '上海', '广州', '深圳']
})

result = pd.merge(
    orders, users2,
    left_on='用户ID',    # 左表的关联列
    right_on='ID'        # 右表的关联列
)
print("不同列名关联:")
print(result)

# ===== suffixes:处理重名列 =====
orders2 = pd.DataFrame({
    '订单ID': [1001, 1002],
    '金额': [500, 300],
    '更新时间': ['2024-01-15', '2024-02-20']  # 与右表有重名列
})

users3 = pd.DataFrame({
    '订单ID': [1001, 1002],
    '金额': [550, 320],  # 重名列
    '更新时间': ['2024-01-16', '2024-02-21']  # 重名列
})

result = pd.merge(
    orders2, users3,
    on='订单ID',
    how='left',
    suffixes=('_订单', '_用户')  # 自定义后缀
)
print("重名列处理:")
print(result)

# ===== indicator:显示匹配来源 =====
result = pd.merge(orders, users, on='用户ID', how='outer', indicator=True)
print("匹配来源:")
print(result)
# _merge 列会显示 "both"、"left_only"、"right_only"

7. 多表关联

# 三表关联:先关联 orders + users,再关联 products
result = pd.merge(orders, users, on='用户ID', how='left')
result = pd.merge(result, products, on='订单ID', how='left')
print("三表关联结果:")
print(result)

# 链式写法
result = (orders
          .merge(users, on='用户ID', how='left')
          .merge(products, on='订单ID', how='left'))
print("\n链式写法:")
print(result)

join:基于索引的关联

join 是基于索引进行关联的方法。它本质上是 merge 的便捷版本,当你的关联键是索引时,使用 join 更简洁。

# 设置索引
orders_idx = orders.set_index('用户ID')
users_idx = users.set_index('用户ID')

print("orders 索引:")
print(orders_idx)
print("\nusers 索引:")
print(users_idx)

# ===== 左连接(join 默认就是左连接)=====
result = orders_idx.join(users_idx, lsuffix='_订单', rsuffix='_用户')
print("join 左连接:")
print(result)

# ===== 指定连接方式 =====
result = orders_idx.join(users_idx, how='inner', lsuffix='_订单', rsuffix='_用户')
print("\njoin 内连接:")
print(result)

# join 的等价 merge 写法:
# result = pd.merge(orders, users, left_index=True, right_index=True, how='left')

concat:轴向拼接

concat 用于将多个 DataFrame 按轴方向拼接——可以是上下拼接(纵向),也可以是左右拼接(横向)。

1. 纵向拼接(axis=0,默认)

# 两个结构相同的 DataFrame
df1 = pd.DataFrame({
    '姓名': ['张三', '李四'],
    '年龄': [25, 30],
    '城市': ['北京', '上海']
})

df2 = pd.DataFrame({
    '姓名': ['王五', '赵六'],
    '年龄': [28, 35],
    '城市': ['广州', '深圳']
})

# 上下拼接
result = pd.concat([df1, df2])
print("上下拼接:")
print(result)

# 忽略原索引
result = pd.concat([df1, df2], ignore_index=True)
print("\n忽略原索引:")
print(result)

# 添加来源标记
result = pd.concat([df1, df2], keys=['表1', '表2'])
print("\n带来源标记:")
print(result)

2. 横向拼接(axis=1)

# 两个结构不同的 DataFrame
df_a = pd.DataFrame({
    'A': [1, 2, 3],
    'B': [4, 5, 6]
})

df_b = pd.DataFrame({
    'C': [7, 8, 9],
    'D': [10, 11, 12]
})

# 左右拼接
result = pd.concat([df_a, df_b], axis=1)
print("左右拼接:")
print(result)

3. concat 的数据对齐行为

# concat 会按索引对齐
df_x = pd.DataFrame({'A': [1, 2]}, index=[0, 1])
df_y = pd.DataFrame({'B': [3, 4]}, index=[1, 2])

result = pd.concat([df_x, df_y], axis=1)
print("索引对齐拼接:")
print(result)
# 输出:
#      A    B
# 0  1.0  NaN
# 1  2.0  3.0
# 2  NaN  4.0

# inner:只保留共有的索引
result = pd.concat([df_x, df_y], axis=1, join='inner')
print("\ninner 对齐:")
print(result)
# 只保留索引 1

对比与选择指南

方法适用场景关联依据示例
merge表关联(类似 SQL JOIN)列值merge(df1, df2, on='id')
join基于索引的关联索引df1.join(df2)
concat数据追加或并排拼接轴方向concat([df1, df2])

快速决策

数据对齐与常见问题

1. 重复列名处理

# 关联后可能出现重复列
df1 = pd.DataFrame({'ID': [1, 2], 'name': ['A', 'B'], 'score': [90, 85]})
df2 = pd.DataFrame({'ID': [1, 2], 'name': ['A', 'B'], 'grade': ['A', 'B']})

result = pd.merge(df1, df2, on='ID', suffixes=('_1', '_2'))
print(result)

2. 一对多关联

# 一个用户对应多个订单(一对多)
users = pd.DataFrame({
    '用户ID': [1, 2, 3],
    '姓名': ['张三', '李四', '王五']
})

orders = pd.DataFrame({
    '订单ID': [101, 102, 103, 104],
    '用户ID': [1, 1, 2, 3],  # 用户1有两个订单
    '金额': [500, 300, 800, 200]
})

result = pd.merge(users, orders, on='用户ID', how='left')
print("一对多关联:")
print(result)
# 张三会出现两行,分别对应两个订单

3. 多对多关联

# 多对多关联会产生笛卡尔积
df1 = pd.DataFrame({'key': ['A', 'A', 'B'], 'val1': [1, 2, 3]})
df2 = pd.DataFrame({'key': ['A', 'A', 'B'], 'val2': [4, 5, 6]})

result = pd.merge(df1, df2, on='key')
print("多对多关联(笛卡尔积):")
print(result)
# A-A 组合产生 2×2=4 行
# 注意:Pandas 3.x 中多对多关联需要显式指定 validate 参数

实战练习

练习 1:三表关联查询

题目:关联学生、课程、成绩三张表。

# 参考答案
import pandas as pd

students = pd.DataFrame({
    '学号': [101, 102, 103, 104],
    '姓名': ['小明', '小红', '小刚', '小丽'],
    '班级': ['一班', '二班', '一班', '三班']
})

courses = pd.DataFrame({
    '课程号': ['C01', 'C02', 'C03'],
    '课程名': ['语文', '数学', '英语'],
    '学分': [4, 5, 4]
})

grades = pd.DataFrame({
    '学号': [101, 101, 102, 102, 103, 103, 104, 104],
    '课程号': ['C01', 'C02', 'C01', 'C03', 'C02', 'C03', 'C01', 'C02'],
    '成绩': [85, 90, 92, 88, 78, 82, 96, 85]
})

# 1. 关联学生 + 成绩
result = students.merge(grades, on='学号', how='left')
result = result.merge(courses, on='课程号', how='left')
print("学生-课程-成绩关联:")
print(result)

# 2. 计算每个学生的平均分
avg = result.groupby(['学号', '姓名'])['成绩'].mean().reset_index()
avg.columns = ['学号', '姓名', '平均分']
print("\n学生平均分:")
print(avg.round(1))

练习 2:数据拼接实践

题目:将多个季度的数据文件合并。

# 参考答案
import pandas as pd
import numpy as np

# 模拟四个季度的数据
q1 = pd.DataFrame({
    '季度': ['Q1'] * 3,
    '产品': ['A', 'B', 'C'],
    '销售额': [1000, 2000, 1500]
})

q2 = pd.DataFrame({
    '季度': ['Q2'] * 3,
    '产品': ['A', 'B', 'C'],
    '销售额': [1200, 2200, 1600]
})

q3 = pd.DataFrame({
    '季度': ['Q3'] * 3,
    '产品': ['A', 'B', 'C'],
    '销售额': [1100, 2100, 1700]
})

q4 = pd.DataFrame({
    '季度': ['Q4'] * 3,
    '产品': ['A', 'B', 'C'],
    '销售额': [1300, 2300, 1800]
})

# 纵向拼接
df = pd.concat([q1, q2, q3, q4], ignore_index=True)
print("年度合并数据:")
print(df)

# 计算年度汇总
summary = df.groupby('产品')['销售额'].agg(['sum', 'mean']).reset_index()
summary.columns = ['产品', '年销售额', '季均销售额']
print("\n年度汇总:")
print(summary)

本节总结

本节我们学习了 Pandas 的三种数据整合方法:

merge —— 基于列值的表关联:

join —— 基于索引的关联:

concat —— 轴向拼接:

到此这篇关于Pandas多源数据整合之三大数据合并方法(Merge,Join与Concat )详解的文章就介绍到这了,更多相关Pandas数据合并内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文