当前位置:首页>python>从零开始学Python数据清洗(十)

从零开始学Python数据清洗(十)

  • 2026-09-05 01:40:51
从零开始学Python数据清洗(十)

数据合并——merge、concat、join

真实世界的数据几乎从不待在同一个表格里。员工信息在A表,部门信息在B表,销售数据按月分散在不同文件中……如果你只会操作单张表,你的分析能力就永远被框死在一个文件里。今天我们来解锁数据合并的三大利器。

文中涉及到的数据文件可以联系我发给你,因为我还不知道怎么在公众号插入文件


一、concat:沿轴拼接,简单直接

pd.concat() 是 pandas 中最基础的拼接函数。想象你把两张纸对齐、拼在一起——上下拼是纵向堆叠,左右拼是横向拼接。

纵向拼接(axis=0):追加行

纵向拼接最常见——比如把第一季度的销售数据和第二季度的销售数据堆在一起。

import pandas as pddf_q1 = pd.read_csv('sales_orders.csv').iloc[:50]   # 前50条模拟Q1df_q2 = pd.read_csv('sales_orders.csv').iloc[50:100] # 第二条50条模拟Q2# 纵向堆叠,重置索引df_all = pd.concat([df_q1, df_q2], axis=0, ignore_index=True)print(f"Q1: {len(df_q1)}条, Q2: {len(df_q2)}条, 合计: {len(df_all)}条")

关键参数解读:

参数
含义
建议
axis=0
纵向(沿行方向)拼接,默认值
拼接多份同结构数据
axis=1
横向(沿列方向)拼接
拼接不同字段
ignore_index=True
重置索引为0,1,2...
推荐开启,避免重复索引
keys=['Q1', 'Q2']
给每份数据打标签
用于区分来源,生成MultiIndex
# 使用keys区分来源df_all = pd.concat([df_q1, df_q2], keys=['Q1', 'Q2'])print(df_all.index[:3])# 输出:MultiIndex([('Q1', 0), ('Q1', 1), ('Q1', 2)])# 恢复成单层索引df_all = df_all.reset_index(drop=True)

注意:纵向拼接时,pandas 会按列名对齐。如果两张表的列不完全一致,缺失的列会自动填充 NaN。这个行为通常是你想要的,但要注意别让意外的不匹配悄悄引入缺失值。

横向拼接(axis=1):追加列

当你需要把两个不同维度的表并在一起时用横向拼接。但要格外小心——它只按索引位置对齐,不做键匹配。

# 假设我们有两张按相同顺序排好的表df_info = pd.DataFrame({'emp_id': ['EMP001', 'EMP002'],                         'name': ['张三', '李四']})df_salary = pd.DataFrame({'salary': [15000, 18000]})df_combined = pd.concat([df_info, df_salary], axis=1)print(df_combined)

横向拼接比较"笨"——它只认位置,不认键。大多数场景下,你需要的是更智能的 merge。


二、merge:pandas 版 SQL JOIN

pd.merge() 是合并操作的灵魂。如果你学过 SQL,这一节你会感觉非常亲切。

四种连接方式

把两张表想象成两个集合的韦恩图(Venn diagram):

连接方式
结果
使用场景
inner
(内连接)
只保留两表都有的键
找交集——"同时存在于两表"
left
(左连接)
保留左表所有行
以左表为基准——"不管有没有匹配到,左表一行不少"
right
(右连接)
保留右表所有行
以右表为基准
outer
(外连接)
保留两表所有行
求并集——"一个不能少"

基本用法

emp = pd.read_csv('employee_data.csv')dept = pd.read_csv('department_data.csv')print(emp.columns.tolist())# ['emp_id', 'name', 'department', 'gender', 'age', 'education', 'hire_date', 'salary']print(dept.columns.tolist())# ['department', 'dept_code', 'manager', 'floor', 'headcount']# 内连接:只保留部门匹配的emp_inner = pd.merge(emp, dept, on='department', how='inner')# 左连接:保留所有员工,匹配不上部门信息的填NaNemp_left = pd.merge(emp, dept, on='department', how='left')# 外连接:所有行都保留emp_outer = pd.merge(emp, dept, on='department', how='outer')

当键名不同时:left_on 和 right_on

两张表的"连接键"不一定同名:

# 假设emp表的列名是dept_name, 而dept表的列名是departmentemp2 = emp.rename(columns={'department': 'dept_name'})result = pd.merge(emp2, dept,                   left_on='dept_name',   # 左表用于连接的列                  right_on='department',  # 右表用于连接的列                  how='left')

多列连接

有时候单列不够——你需要组合键来唯一确定一行:

# 按部门和性别同时匹配(假设另一张表也存在这两列)emp_with_policy = pd.merge(emp, policy_table,                            on=['department', 'gender'],                            how='left')

追踪合并来源:indicator 参数

indicator=True 会在结果中新增一列 _merge,告诉你每一行来自哪里:

merged = pd.merge(emp, dept, on='department', how='outer', indicator=True)print(merged['_merge'].value_counts())# both          95  ← 两表都匹配# left_only      5  ← 只存在于员工表,部门表里没有# right_only     0  ← 只存在于部门表,没有员工# 快速找到没匹配上的员工no_dept = merged[merged['_merge'] == 'left_only']print(no_dept[['emp_id', 'name', 'department']])

最佳实践:合并后立即检查 _merge 列的分布。如果 left_only 比你预期的多,说明你的连接键可能有脏数据。


三、join:基于索引的合并

.join() 是 .merge() 的一个便捷封装,它直接用索引作为连接键。

# 将department设为索引dept_idx = dept.set_index('department')emp_idx = emp.set_index('department')  # 也设索引# 用join做索引级合并result = emp_idx.join(dept_idx, how='left', lsuffix='_emp', rsuffix='_dept')

适用场景:数据天生带有索引,或者你已经通过 groupby 生成了一张索引表。


四、常见合并陷阱与解决方案

这几个坑,初学者几乎必踩。

陷阱一:重复键导致"笛卡尔积爆炸"

如果连接键在右表中不唯一,merge 会产生笛卡尔积——行数突然膨胀。

# 危险:假设dept表中'技术部'出现了两条dept_dup = pd.DataFrame({    'department': ['技术部', '技术部'],    'manager': ['陈经理', '王经理'],    'floor': [8, 9]})emp_tech = emp[emp['department'] == '技术部']result = pd.merge(emp_tech, dept_dup, on='department')print(f"merge后行数: {len(result)}")# 每个员工会匹配两条部门记录,行数翻倍!

解决方案:使用 validate 参数做关系校验。

# 声明"右表键唯一",如果出现重复,merge会直接报错result = pd.merge(emp, dept, on='department', how='left',                   validate='m:1')  # 多对一关系# validate取值:'1:1', '1:m', 'm:1', 'm:m'

陷阱二:键列类型不一致

int 和 str 在人类眼里一样,在 pandas 眼里是两种东西:

# 左表用整数,右表用字符串——永远匹配不上df_a = pd.DataFrame({'id': [1, 2, 3], 'val_a': ['a', 'b', 'c']})df_b = pd.DataFrame({'id': ['1', '2', '3'], 'val_b': ['x', 'y', 'z']})merged = pd.merge(df_a, df_b, on='id', how='inner')print(len(merged))  # 0 —— 全部匹配失败!# 解决:统一类型df_b['id'] = df_b['id'].astype(int)merged = pd.merge(df_a, df_b, on='id', how='inner')print(len(merged))  # 3 —— 正确!

最佳实践:合并前用 df[col].dtype 检查两表键列的类型是否一致。

陷阱三:列名冲突

两张表都存在 gender 列(比如员工表有性别,部门政策表里也有目标性别),merge 会自动加后缀:

# 使用suffixes参数自定义后缀result = pd.merge(emp, other_table, on='department',                   suffixes=('_emp', '_other'))# 列名自动变为 gender_emp 和 gender_other

五、实战练习:完整合并流程

任务1:给员工补上部门和所属经理信息

import pandas as pdemp = pd.read_csv('employee_data.csv')dept = pd.read_csv('department_data.csv')# 步骤1:检查键列类型print(f"emp.department: {emp['department'].dtype}")print(f"dept.department: {dept['department'].dtype}")# 步骤2:检查右表键的唯一性print(f"dept表中department是否唯一: {dept['department'].is_unique}")# 步骤3:左连接,保留所有员工emp_enriched = pd.merge(emp, dept, on='department', how='left',                         validate='m:1', indicator=True)# 步骤4:检查匹配结果print(emp_enriched['_merge'].value_counts())# 步骤5:找出未匹配的员工unmatched = emp_enriched[emp_enriched['_merge'] == 'left_only']print(f"未匹配员工数: {len(unmatched)}")print(unmatched[['emp_id', 'name', 'department']].head())

任务2:合并两个时间段的销售订单

# 模拟:将sales_orders按日期拆成上半年和下半年orders = pd.read_csv('sales_orders.csv')# 先确保日期是datetime类型orders['order_date'] = pd.to_datetime(orders['order_date'])# 拆分h1 = orders[orders['order_date'] < '2024-01-01']h2 = orders[orders['order_date'] >= '2024-01-01']# 纵向拼接,打上来源标签orders_all = pd.concat([h1, h2], keys=['上半年', '下半年'], names=['period', 'old_index'])orders_all = orders_all.reset_index(level='period')print(f"上半年: {len(h1)}条, 下半年: {len(h2)}条")print(f"合并后: {len(orders_all)}条")print(f"\n各时间段订单数:")print(orders_all['period'].value_counts())

小结

函数
适用场景
对齐依据
pd.concat(axis=0)
同结构多表纵向追加
列名对齐
pd.concat(axis=1)
同顺序多表横向拼接
索引位置对齐
pd.merge(how='inner')
取两表交集
指定列作为键
pd.merge(how='left')
以左表为基准
指定列作为键
pd.merge(how='outer')
取两表并集
指定列作为键
.join()
基于索引的合并
索引对齐

合并前三检查:

  1. 1. 键列类型是否一致?(df.dtype)
  2. 2. 键列在右表是否唯一?(df.is_unique)
  3. 3. 预计结果行数是多少?(用 validate 校验关系)

下一篇预告

掌握了数据合并,下一步就是分组聚合与数据透视——用 groupby 汇总各个维度的统计量,用 pivot_table 制作交叉报表。数据分析的"灵魂能力",下篇见。


#数据合并 #merge #concat #join #Pandas #SQL

觉得有用?点赞、在看、转发给一起学Python的朋友。如有疑问,欢迎在留言区交流。

最新文章

随机文章