Pandas多表处理:从“头疼事”到“顺手活”的实战指南
接上篇系列文章:
《Pandas 入门一:零基础也能懂!3步安装+10分钟玩转数据读取》
《Pandas 入门二:DataFrame 核心操作,新手也能轻松筛选/修改数据》
《Pandas 入门三:数据清洗必学!缺失值/重复值处理,一步到位不踩坑》
《Pandas 进阶四:数据筛选/分组/聚合,比 Excel 快十倍的操作技巧》
在实际数据分析工作中,单打独斗的数据表可不多见。更常见的情况是,你手头有好几张表,比如「用户表」、「订单表」、「商品表」,需要把它们整合起来,才能回答“哪个用户买了什么商品”这类业务问题。或者,你需要把多个结构相同的Excel文件合并成一个总表。这些场景,都绕不开Pandas的“数据合并、拼接与重塑”功能。
今天这篇文章,就带你彻底吃透Pandas中处理多表联动的几个核心方法:merge、concat、join,以及数据重塑的利器stack和unstack。我们会用最简单的例子和贴近实战的案例,把这些看似复杂的操作,变成你工具箱里的“顺手活”。

一、先搞懂:什么时候用哪种方法?
新手最容易犯迷糊的,就是面对一堆数据表,不知道该用merge还是concat。别急,先记住下面这张简单的“使用场景表”,不用死记硬背,跟着后面的例子走一遍,自然就明白了。

二、实战演练:3个核心方法+代码示例
1. merge():最常用!按“共同字段”合并(重点掌握)
merge堪称多表处理的“主力军”,它的作用和SQL里的JOIN非常相似,核心就是根据两张表共有的某个字段(比如用户ID),把数据关联起来。
先来准备测试数据:
我们用“用户表”和“订单表”举例,两张表通过user_id这个字段关联。
import pandas as pd
# 用户表:包含用户ID、姓名、城市
df_user = pd.DataFrame({
'user_id': [1, 2, 3, 4],
'name': ['小明', '小红', '小刚', '小丽'],
'city': ['北京', '上海', '广州', '深圳']
})
# 订单表:包含订单ID、用户ID、购买商品、金额
df_order = pd.DataFrame({
'order_id': [101, 102, 103, 104, 105],
'user_id': [1, 1, 2, 3, 5], # 注意:user_id=5的用户在用户表里不存在
'product': ['手机', '耳机', '电脑', '平板', '手表'],
'amount': [5999, 1299, 8999, 2999, 1599]
})
print("用户表:")
print(df_user)
print("\n订单表:")
print(df_order)
(1) 内连接(默认方式):只保留“两表都有”的记录
如果你只想分析那些既有用户信息、又有订单记录的数据(比如排除掉没下过单的用户和找不到来源的订单),内连接就是你的首选。
# 内连接:how='inner'(可省略,默认就是inner)
df_inner = pd.merge(df_user, df_order, on='user_id') # on指定共同字段
print("内连接结果:")
print(df_inner)
运行后你会发现,结果里只包含了user_id为1、2、3的记录,完美匹配了用户和订单信息。
(2) 左连接:保留“左表所有记录”,右表没有的填NaN
假设你想保留所有用户信息,包括那些还没下过单的(比如用户小丽,user_id=4),同时关联出他们的订单(如果有的话)。这时左连接就派上用场了,右表缺失的信息会用NaN(空值)填充。
# 左连接:how='left',左表是df_user
df_left = pd.merge(df_user, df_order, on='user_id', how='left')
print("左连接结果:")
print(df_left)
结果中,小丽的order_id、product、amount字段都会显示为NaN,但用户数据本身不会丢失。
(3) 外连接:保留“两表所有记录”,缺失的填NaN
如果你想“一网打尽”,既保留所有用户,也保留所有订单(包括那个user_id=5的未知用户订单),那就得用外连接。
df_outer = pd.merge(df_user, df_order, on='user_id', how='outer')
print("外连接结果:")
print(df_outer)
2. concat():简单粗暴!按行/列直接拼接
如果你的数据是“同结构”的(比如多个Excel文件的表头完全一样),用concat直接拼接效率最高,它不需要指定共同字段。
(1) 按行拼接(纵向合并,最常用)
典型场景:把上半年和下半年的订单表合并成一张全年总表。
# 订单表1(上半年)
df_order1 = pd.DataFrame({
'order_id': [101, 102, 103],
'user_id': [1, 2, 3],
'amount': [5999, 8999, 2999]
})
# 订单表2(下半年)
df_order2 = pd.DataFrame({
'order_id': [104, 105, 106],
'user_id': [4, 5, 1],
'amount': [1599, 3999, 1299]
})
# 按行拼接:axis=0(默认,可省略)
df_order_all = pd.concat([df_order1, df_order2], ignore_index=True) # ignore_index重置索引
print("按行拼接后的所有订单:")
print(df_order_all)
两个表的行会连在一起。ignore_index=True这个参数新手一定要加,它能帮你重置索引,避免出现重复的索引号。
(2) 按列拼接(横向合并)
适合“索引相同、但字段不同”的表拼接。比如,你有一张用户基础信息表,又拿到了一张用户的消费偏好表,两张表的用户顺序(索引)是对应的。
# 用户偏好表(和用户表索引一致)
df_prefer = pd.DataFrame({
'favorite': ['数码', '家电', '图书', '美妆'],
'frequency': [3, 1, 2, 4]
}, index=[0, 1, 2, 3]) # 索引和df_user的行索引对应
# 按列拼接:axis=1
df_user_prefer = pd.concat([df_user, df_prefer], axis=1)
print("按列拼接后的用户信息:")
print(df_user_prefer)
3. join():按“索引”合并(简化版merge)
join可以看作是merge的一个简化版,它默认按照“索引”进行合并,适合两张表的索引已经对齐的场景。新手可以先把merge用熟,再了解join。
# 把用户表的user_id设为索引
df_user_index = df_user.set_index('user_id')
# 把订单表的user_id设为索引
df_order_index = df_order.set_index('user_id')
# 按索引左连接
df_join = df_user_index.join(df_order_index, how='left')
print("按索引join结果:")
print(df_join)
得到的结果和之前用merge做的左连接基本一致,只是省去了写on='user_id'的步骤。
三、数据重塑:stack()和unstack(),解决“宽表/长表”转换
数据分析中,数据的存储格式(宽表或长表)常常会影响后续操作的便利性。stack和unstack就是专门用来做格式转换的“神器”。
准备测试数据(宽表):
df_wide = pd.DataFrame({
'user_id': [1, 2, 3],
'2024-01': [5999, 8999, 2999], # 1月消费
'2024-02': [1299, 0, 1599], # 2月消费
'2024-03': [0, 3999, 999] # 3月消费
})
print("宽表(按月份列展示):")
print(df_wide)
1. stack():宽表转长表(把列“压”成行)
上面的宽表,如果想按月份进行筛选或统计,就不太方便。用stack可以把月份列(2024-01, 2024-02, 2024-03)“压缩”成行,变成“月份+消费金额”的格式。
df_long = df_wide.set_index('user_id').stack() # 先设user_id为索引,再stack
df_long = df_long.reset_index() # 重置索引,变成普通列
df_long.columns = ['user_id', 'month', 'amount'] # 重命名列名
print("长表(按月份行展示):")
print(df_long)
转换后的长表结构,非常适合用来做按月分组、求和等分析。
2. unstack():长表转宽表(把行“拉”成列)
当然,你也可以把长表再转换回宽表格式,便于展示或某些特定计算。
df_wide2 = df_long.pivot_table(index='user_id', columns='month', values='amount', fill_value=0)
df_wide2 = df_wide2.reset_index()
print("长表转回宽表:")
print(df_wide2)
这里的fill_value=0参数很实用,它会把转换过程中产生的NaN值(比如某用户某月没消费)自动填充为0。
四、新手避坑指南(必看!)
掌握了方法,还得知道怎么避开常见的“坑”:
- 合并时字段类型要一致:如果A表的
user_id是整数,B表的user_id是字符串(如‘1’),合并就会失败。记得先用df['user_id'] = df['user_id'].astype(int)统一类型。 - 重复列名处理:如果两表有同名字段但含义不同(比如都有“备注”列),合并后Pandas会自动加上
_x、_y后缀区分。你可以用suffixes=('_用户表', '_订单表')参数自定义后缀,让列名更清晰。 - concat按列拼接时,确保索引对齐:否则会出现大量NaN。新手如果不确定,优先使用merge更稳妥。
- 处理缺失值:合并后出现的NaN,可以根据业务需求选择用
fillna(0)填充为0,或者用dropna()直接删除整行。
五、实战小案例:三表合并分析
最后,我们用一个稍微复杂的场景来串联以上知识:合并“用户表+订单表+商品表”,并做简单分析。
# 商品表
df_product = pd.DataFrame({
'product': ['手机', '耳机', '电脑', '平板', '手表'],
'category': ['数码', '数码', '家电', '数码', '配饰'],
'price': [5999, 1299, 8999, 2999, 1599]
})
# 1. 先合并用户表和订单表(左连接)
df_user_order = pd.merge(df_user, df_order, on='user_id', how='left')
# 2. 再合并商品表(按product字段)
df_final = pd.merge(df_user_order, df_product, on='product', how='left')
# 分析:每个城市的数码类商品消费总额
df_digital = df_final[df_final['category'] == '数码'] # 筛选数码类
city_amount = df_digital.groupby('city')['amount'].sum() # 按城市分组求和
print("各城市数码类商品消费总额:")
print(city_amount)
运行这段代码,你就能轻松得到“北京、上海、广州”等城市在数码产品上的消费总额,真正实现了多表联动分析。
六、总结
回顾一下,这篇文章的核心就是Pandas多表处理的三大件:merge(按字段合并)、concat(按行/列拼接)、stack/unstack(数据重塑)。每个方法我们都配上了可以直接复制运行的代码。
说到底,多表处理的关键在于“明确数据之间的关联方式”——有共同字段就用merge,结构完全相同就用concat,需要转换数据格式就用stack/unstack。理清这个思路,面对再复杂的多表场景,你也能游刃有余。
下一篇,我们将进入“数据转换与计算”的领域,聊聊如何批量处理文本、转换日期格式、以及应用自定义函数,帮你把数据处理效率再提升一个台阶。
