真实世界的数据几乎从不待在同一个表格里。员工信息在A表,部门信息在B表,销售数据按月分散在不同文件中……如果你只会操作单张表,你的分析能力就永远被框死在一个文件里。今天我们来解锁数据合并的三大利器。
文中涉及到的数据文件可以联系我发给你,因为我还不知道怎么在公众号插入文件
pd.concat() 是 pandas 中最基础的拼接函数。想象你把两张纸对齐、拼在一起——上下拼是纵向堆叠,左右拼是横向拼接。
纵向拼接最常见——比如把第一季度的销售数据和第二季度的销售数据堆在一起。
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 | ||
keys=['Q1', 'Q2'] |
# 使用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。这个行为通常是你想要的,但要注意别让意外的不匹配悄悄引入缺失值。
当你需要把两个不同维度的表并在一起时用横向拼接。但要格外小心——它只按索引位置对齐,不做键匹配。
# 假设我们有两张按相同顺序排好的表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。
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')两张表的"连接键"不一定同名:
# 假设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=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() 是 .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_otherimport 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())# 模拟:将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() |
合并前三检查:
df.dtype)df.is_unique)validate 校验关系)掌握了数据合并,下一步就是分组聚合与数据透视——用 groupby 汇总各个维度的统计量,用 pivot_table 制作交叉报表。数据分析的"灵魂能力",下篇见。
#数据合并 #merge #concat #join #Pandas #SQL
觉得有用?点赞、在看、转发给一起学Python的朋友。如有疑问,欢迎在留言区交流。