github star gitee star atomgit star PyPI Downloads AI 编程 AI 交流群

大家好,我是正在实战各种AI项目的程序员晚枫。

今天学习数据合并与连接,这是整合多个数据源的核心技能。

无论是关联订单和客户信息,还是拼接多个月份的数据,掌握这些技巧都能轻松搞定。


准备示例数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import pandas as pd

# 员工表
employees = pd.DataFrame({
'员工ID': [1, 2, 3, 4, 5],
'姓名': ['张三', '李四', '王五', '赵六', '钱七'],
'部门ID': [101, 102, 101, 103, 102]
})

# 部门表
departments = pd.DataFrame({
'部门ID': [101, 102, 103, 104],
'部门名': ['技术部', '销售部', '人事部', '财务部'],
'经理': ['张经理', '李经理', '王经理', '赵经理']
})

# 工资表(部分员工)
salaries = pd.DataFrame({
'员工ID': [1, 2, 3, 6],
'基本工资': [15000, 12000, 18000, 10000],
'奖金': [3000, 2000, 4000, 1500]
})

merge:数据库风格的连接

内连接(Inner Join)

只保留两个表都有的键

1
2
3
4
# 员工+部门(内连接)
result = pd.merge(employees, departments, on='部门ID', how='inner')
print(result)
# 只有部门ID在两张表中都存在的记录

左连接(Left Join)

保留左表所有记录,右表没有匹配的填NaN

1
2
3
4
# 员工+工资(左连接)
result = pd.merge(employees, salaries, on='员工ID', how='left')
print(result)
# 所有员工都有,没工资信息的显示NaN

右连接(Right Join)

保留右表所有记录

1
2
result = pd.merge(employees, salaries, on='员工ID', how='right')
# 以工资表为主,员工表没有的显示NaN

外连接(Outer Join)

保留两边所有记录

1
2
result = pd.merge(employees, salaries, on='员工ID', how='outer')
# 所有员工和所有工资记录都保留

连接方式对比

how参数结果说明
inner交集,只保留匹配的记录
left以左表为主,右表补充
right以右表为主,左表补充
outer并集,保留所有记录

多键连接

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 当连接键列名不同时
left_df = pd.DataFrame({'A': [1, 2], 'B': ['a', 'b']})
right_df = pd.DataFrame({'C': [1, 2], 'D': ['x', 'y']})

result = pd.merge(left_df, right_df, left_on='A', right_on='C')

# 多列连接
orders = pd.DataFrame({
'客户ID': [1, 1, 2],
'日期': ['2024-01', '2024-02', '2024-01'],
'金额': [100, 200, 150]
})

customers = pd.DataFrame({
'客户ID': [1, 2],
'日期': ['2024-01', '2024-01'],
'等级': ['VIP', '普通']
})

result = pd.merge(orders, customers, on=['客户ID', '日期'], how='left')

concat:轴向连接

纵向拼接(增加行)

1
2
3
4
5
6
7
8
9
10
11
# 多个月份的数据拼接
df_jan = pd.DataFrame({'产品': ['A', 'B'], '销量': [100, 200]})
df_feb = pd.DataFrame({'产品': ['A', 'B'], '销量': [120, 220]})
df_mar = pd.DataFrame({'产品': ['A', 'B'], '销量': [130, 210]})

# 纵向拼接
quarter_data = pd.concat([df_jan, df_feb, df_mar], ignore_index=True)

# 保留来源信息
quarter_data = pd.concat([df_jan, df_feb, df_mar],
keys=['1月', '2月', '3月'])

横向拼接(增加列)

1
2
3
4
5
df_info = pd.DataFrame({'ID': [1, 2], '姓名': ['张三', '李四']})
df_score = pd.DataFrame({'数学': [90, 85], '英语': [88, 92]})

# 横向拼接
df_full = pd.concat([df_info, df_score], axis=1)

join:基于索引的连接

1
2
3
4
5
6
7
8
9
# 设置索引后使用join
employees_idx = employees.set_index('员工ID')
salaries_idx = salaries.set_index('员工ID')

# 左连接
result = employees_idx.join(salaries_idx, how='left')

# 连接多个表
result = employees_idx.join([salaries_idx, other_table_idx])

实战:完整的数据整合流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# 场景:整合分散在多个表的销售数据

# 1. 读取各表
orders = pd.read_csv('orders.csv') # 订单表
products = pd.read_csv('products.csv') # 产品表
customers = pd.read_csv('customers.csv') # 客户表
regions = pd.read_csv('regions.csv') # 地区表

# 2. 逐步合并
# 订单 + 产品信息
sales = pd.merge(orders, products, on='产品ID', how='left')

# + 客户信息
sales = pd.merge(sales, customers, on='客户ID', how='left')

# + 地区信息
sales = pd.merge(sales, regions, on='地区ID', how='left')

# 3. 计算字段
sales['总金额'] = sales['单价'] * sales['数量']
sales['利润'] = sales['总金额'] - sales['成本']

# 4. 保存结果
sales.to_csv('sales_report.csv', index=False)

print("数据整合完成!")
print(f"总记录数: {len(sales)}")
print(f"总销售额: {sales['总金额'].sum():,.2f}")

常见问题与解决

问题1:重复列名

1
2
# 自动添加后缀
result = pd.merge(df1, df2, on='key', suffixes=('_左', '_右'))

问题2:数据类型不匹配

1
2
3
# 统一类型后再合并
df1['ID'] = df1['ID'].astype(str)
df2['ID'] = df2['ID'].astype(str)

问题3:内存不足

1
2
3
4
5
6
7
# 分块处理
chunks = []
for chunk in pd.read_csv('large_file.csv', chunksize=10000):
merged = pd.merge(chunk, lookup_table, on='key')
chunks.append(merged)

result = pd.concat(chunks, ignore_index=True)

性能对比:不同合并方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import pandas as pd
import numpy as np

left = pd.DataFrame({'key': np.random.randint(1, 10000, 100000), 'value_l': np.random.randn(100000)})
right = pd.DataFrame({'key': np.random.randint(1, 10000, 50000), 'value_r': np.random.randn(50000)})

# merge
%timeit pd.merge(left, right, on='key', how='inner')

# join(需设置索引)
left_idx = left.set_index('key')
right_idx = right.set_index('key')
%timeit left_idx.join(right_idx, how='inner')

# 典型结果:merge更灵活,join稍快

进阶用法

多条件合并

1
2
3
4
5
6
7
8
# 按多列合并
result = pd.merge(
df_orders, df_customers,
left_on=['customer_id', 'region'],
right_on=['id', 'area'],
how='left',
suffixes=('_order', '_customer')
)

merge的indicator参数

1
2
3
4
5
6
# 查看匹配情况
result = pd.merge(df1, df2, on='id', how='outer', indicator=True)
print(result['_merge'].value_counts())
# both 800 ← 两表都有
# left_only 150 ← 只在左表
# right_only 50 ← 只在右表

合并多个DataFrame

1
2
3
4
5
from functools import reduce

# 一次性合并多个表
dfs = [df_orders, df_customers, df_products, df_payments]
result = reduce(lambda left, right: pd.merge(left, right, on='order_id', how='left'), dfs)

避坑指南

❌ 坑1:合并后行数暴增

1
2
3
4
5
6
7
8
9
# 多对多合并导致笛卡尔积
result = pd.merge(df1, df2, on='key') # 如果key有重复,行数会爆炸

# 检查合并前后行数
print(f"合并前: {len(df1)} + {len(df2)}")
print(f"合并后: {len(result)}")

# 如果行数异常,检查key的唯一性
print(df1['key'].duplicated().sum(), "个重复key在df1")

❌ 坑2:列名冲突

1
2
3
4
5
6
# 两表有同名列
result = pd.merge(df1, df2, on='id')
# 报错!两表都有'value'列

# 解决方案:指定suffixes
result = pd.merge(df1, df2, on='id', suffixes=('_left', '_right'))

实战案例:整合多源电商数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
import pandas as pd
import numpy as np

np.random.seed(42)
n_orders = 5000

# 订单表
orders = pd.DataFrame({
'order_id': range(10001, 10001 + n_orders),
'user_id': np.random.randint(2001, 3000, n_orders),
'product_id': np.random.randint(3001, 3050, n_orders),
'quantity': np.random.randint(1, 5, n_orders),
'order_date': pd.date_range('2025-01-01', periods=n_orders, freq='2H')
})

# 用户表
users = pd.DataFrame({
'user_id': range(2001, 3000),
'name': [f'用户{i}' for i in range(999)],
'city': np.random.choice(['北京', '上海', '广州', '深圳', '成都'], 999),
'vip_level': np.random.choice([0, 1, 2, 3], 999, p=[0.5, 0.3, 0.15, 0.05])
})

# 商品表
products = pd.DataFrame({
'product_id': range(3001, 3050),
'name': [f'商品{i:03d}' for i in range(49)],
'category': np.random.choice(['电子产品', '服装', '食品', '家居'], 49),
'price': np.random.uniform(50, 5000, 49).round(2)
})

# 1. 合并三张表
df = (orders
.merge(users, on='user_id', how='left')
.merge(products, on='product_id', how='left', suffixes=('_order', '_product'))
)

# 2. 计算订单金额
df['amount'] = df['quantity'] * df['price']

# 3. 各城市销售分析
city_sales = df.groupby('city').agg(
total_amount=('amount', 'sum'),
order_count=('order_id', 'count'),
avg_amount=('amount', 'mean'),
vip_ratio=('vip_level', lambda x: (x > 0).mean())
).round(2)

print("各城市销售分析:")
print(city_sales)

合并方式对比详解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import pandas as pd

left = pd.DataFrame({'key': ['A', 'B', 'C', 'D'], 'value_l': [1, 2, 3, 4]})
right = pd.DataFrame({'key': ['B', 'C', 'D', 'E'], 'value_r': [5, 6, 7, 8]})

# inner:只保留两表都有的key
pd.merge(left, right, on='key', how='inner')
# key value_l value_r
# B 2 5
# C 3 6
# D 4 7

# left:保留左表所有key
pd.merge(left, right, on='key', how='left')
# key value_l value_r
# A 1 NaN
# B 2 5
# C 3 6
# D 4 7

# right:保留右表所有key
# outer:保留所有key

合并性能优化

1
2
3
4
5
6
7
8
# 大表合并前先设置索引,速度更快
df1 = df1.set_index('key')
df2 = df2.set_index('key')
result = df1.join(df2, how='left')

# 或者先筛选再合并,减少数据量
small = df2[df2['year'] == 2025] # 先筛选
result = df1.merge(small, on='key') # 再合并

SQL JOIN vs Pandas merge对照表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# SQL:  SELECT * FROM A INNER JOIN B ON A.id = B.id
# Pandas: pd.merge(A, B, on='id', how='inner')

# SQL: SELECT * FROM A LEFT JOIN B ON A.id = B.id
# Pandas: pd.merge(A, B, on='id', how='left')

# SQL: SELECT * FROM A FULL OUTER JOIN B ON A.id = B.id
# Pandas: pd.merge(A, B, on='id', how='outer')

# SQL: SELECT * FROM A CROSS JOIN B
# Pandas: A.merge(B, how='cross') # Pandas 1.2+

# SQL: SELECT * FROM A UNION ALL SELECT * FROM B
# Pandas: pd.concat([A, B], ignore_index=True)

# SQL: SELECT * FROM A UNION SELECT * FROM B
# Pandas: pd.concat([A, B]).drop_duplicates()

合并性能对比

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import pandas as pd
import numpy as np
import time

# 准备数据
A = pd.DataFrame({'key': np.random.randint(1, 100000, 1000000), 'val_a': np.random.randn(1000000)})
B = pd.DataFrame({'key': np.random.randint(1, 100000, 500000), 'val_b': np.random.randn(500000)})

# 测试不同合并方式
# 1. 直接merge
start = time.time()
result = pd.merge(A, B, on='key', how='inner')
print(f"merge: {time.time()-start:.3f}秒, {len(result)}行")

# 2. 先设索引再join
start = time.time()
result = A.set_index('key').join(B.set_index('key'), how='inner')
print(f"join: {time.time()-start:.3f}秒, {len(result)}行")

# 3. 先筛选再merge
start = time.time()
small_b = B[B['key'].isin(A['key'].unique())]
result = pd.merge(A, small_b, on='key', how='inner')
print(f"筛选+merge: {time.time()-start:.3f}秒, {len(result)}行")

下节预告

下一课我们将学习分组聚合groupby,这是数据分析最强大的功能之一。

👉 继续阅读:Pandas数据变换-分组聚合groupby


💬 加入学习交流群

扫码加入Python学习交流群,和数千名同学一起进步:

👉 点击加入交流群

群里不定期分享:

  • 数据分析实战案例
  • Python学习资料
  • 求职面试经验
  • 行业最新动态

推荐:AI Python数据分析实战营

🎁 限时福利:送《利用Python进行数据分析》实体书

👉 点击了解详情


课程导航

上一篇: Pandas数据变换-行列操作

下一篇: Pandas数据变换-分组聚合groupby


PS:数据合并是日常工作中最常见的操作。理解不同连接方式的差异,选择合适的方法。



📚 推荐教材

主教材《Excel+Python 飞速搞定数据分析与处理(图灵出品)》

💬 联系我

平台账号/链接
微信扫码加好友
微博@程序员晚枫
知乎@程序员晚枫
抖音@程序员晚枫
小红书@程序员晚枫
B 站Python 自动化办公社区

主营业务:AI 编程培训、企业内训、技术咨询

🎓 AI 编程实战课程

想系统学习 AI 编程?程序员晚枫的 AI 编程实战课 帮你从零上手!