拿到一份原始数据,列名混乱、取值不规范、信息散落在多张表里,这种情况下直接做分析基本不可能出结果。Pandas提供了整套数据重塑工具,从单列映射到多列联动转换,从纵向堆叠到横向对齐合并,覆盖了数据清洗阶段的绝大部分需求。本文结合具体代码,把多列映射、数据转换和DataFrame合并这三个高频操作讲透。

一、多列映射:用map、replace和apply改写数据
映射操作的本质是把列中的原始值按照某个对照关系替换成新值。最常用的是Series.map,它接收一个字典或函数,把列里的每个元素按规则转换。比如把性别编码0和1换成可读文本,把部门缩写换成全称,一行代码就能搞定。
import pandas as pd
df = pd.DataFrame({
'name': ['张三', '李四', '王五'],
'gender': [0, 1, 0],
'dept': ['RD', 'SALES', 'RD']
})
# 用字典做单列映射
df['gender'] = df['gender'].map({0: '男', 1: '女'})
df['dept'] = df['dept'].map({'RD': '研发部', 'SALES': '销售部'})
print(df)
需要注意的是,map遇到字典中不存在的键时会直接填NaN。如果数据里有脏值,比如dept列混进了一个'MKT',映射后这一行就变成缺失值。稳妥的做法是先检查未覆盖的取值,或者改用replace,它对未匹配的值会保持原样不动,不会产生NaN。这两种方法的差异在实际清洗工作中非常关键,很多莫名其妙的缺失值就是map用错了导致的。
当映射逻辑需要同时参考多个列时,map就不够用了,因为map只能作用于单个Series。这时有两个选择:一是用apply配合axis=1逐行处理,二是先用字典构建复合键再映射。例如根据性别和年龄段共同决定保费档位,逐行处理虽然直观,但数据量大时性能会明显下降,因为它本质上是Python级别的循环。
# 多列联动映射:根据性别和职级计算补贴
df['level'] = ['P5', 'P6', 'P7']
subsidy_map = {
('男', '研发部'): 800,
('女', '研发部'): 850,
('男', '销售部'): 1200,
('女', '销售部'): 1250,
}
df['subsidy'] = df.apply(
lambda row: subsidy_map.get((row['gender'], row['dept']), 0),
axis=1
)
print(df)
性能要求高的场景可以换一种思路:先用zip把多列拼成元组Series,再对它做一次map,这样能利用Pandas底层的向量化映射,速度比逐行apply快一个数量级。另外DataFrame.applymap(新版本中叫DataFrame.map)可以对整表每个元素做函数映射,适合批量格式化,比如把所有浮点数保留两位小数。
二、数据转换:类型转换、标准化与自定义函数
转换比映射的范围更广,除了替换值,还包括类型转换、数值标准化、字符串处理等。astype是最基础的类型转换工具,但遇到脏数据很容易报错,比如字符串列里混了一个空串,直接转int会抛异常。此时推荐用pd.to_numeric配合errors='coerce',转换失败的值会自动变成NaN,后续再统一处理缺失值。
df['salary'] = ['15000', '22000', '']
# 直接 astype 会报错,用 to_numeric 更稳妥
df['salary'] = pd.to_numeric(df['salary'], errors='coerce')
df['salary'] = df['salary'].fillna(df['salary'].median())
# 日期转换同样推荐专用函数
df['hire_date'] = pd.to_datetime('2023-0' + df.index.astype(str), format='%Y-%m%d', errors='coerce')
print(df.dtypes)
数值标准化也是常见需求,比如把薪资列缩放到0到1区间,或者做Z-score标准化。这些操作完全可以用向量化表达式完成,避免写循环。向量化写法不仅代码短,而且由NumPy底层执行,处理百万行数据也就是毫秒级的事。对于分组标准化的场景,比如每个部门内部单独做归一化,可以结合groupby加transform,transform返回的结果形状与原Series一致,可以直接赋值回新列。
# 分组内归一化:每个部门内部比较薪资才有意义
df['salary_norm'] = df.groupby('dept')['salary'].transform(
lambda s: (s - s.min()) / (s.max() - s.min())
)
print(df)
自定义函数转换时有个容易忽略的坑:apply处理单列时,如果函数里返回了不同类型的值,结果可能变成object类型,后续数值计算会出错。建议在函数末尾显式用float或int包一层返回值,保证类型一致。此外,如果只是简单条件赋值,优先考虑np.where或np.select,它们比apply快得多,代码也更清晰。
三、DataFrame合并:merge、join与concat的选择
合并操作分两个方向:横向按键对齐用merge,纵向堆叠用concat。merge类似SQL的JOIN,通过一个或多个键把两张表横向拼接。最常见的坑是合并后行数暴涨,这通常是因为连接键存在重复,产生了笛卡尔积。合并前用duplicated检查键的唯一性,是避免这类问题的有效手段。
emp = pd.DataFrame({
'emp_id': [101, 102, 103],
'name': ['张三', '李四', '王五']
})
salary = pd.DataFrame({
'emp_id': [101, 102, 104],
'salary': [15000, 22000, 18000]
})
# 默认内连接,只保留两表都有的键
result = pd.merge(emp, salary, on='emp_id', how='inner')
print(result)
# 左连接保留左表全部行,右表缺失则填NaN
result_left = pd.merge(emp, salary, on='emp_id', how='left')
print(result_left)
how参数有inner、left、right、outer四种模式,选择依据是业务上想保留哪部分数据。核对数据时建议合并前后各做一次行数统计,left连接的结果行数应该等于左表行数,如果变多了,几乎可以断定右表的键有重复。还要注意键名不一致的情况,此时用left_on和right_on分别指定,合并后再drop掉多余的列。
concat处理的是另一个维度的问题,把多张结构相同或相近的表上下拼接,比如把12个月的月度数据合成年度表。默认纵向拼接时要求列名对齐,列名不一致的列会自动补NaN。如果设置ignore_index=True会重建索引,避免出现重复索引标签。横向拼接时用axis=1,此时按索引对齐,因此拼接前务必确认索引含义,必要的话先reset_index。
q1 = pd.DataFrame({'name': ['张三', '李四'], 'score': [88, 92]})
q2 = pd.DataFrame({'name': ['王五'], 'score': [79]})
# 纵向堆叠并重建索引
year = pd.concat([q1, q2], ignore_index=True)
print(year)
# 多列键合并,类似SQL的复合主键关联
info = pd.DataFrame({
'dept': ['研发部', '研发部', '销售部'],
'level': ['P5', 'P6', 'P5'],
'bonus': [1000, 2000, 1500]
})
final = pd.merge(year.assign(dept=['研发部', '销售部', '研发部']),
info, on=['dept', 'level'], how='left')
print(final)
还有个实用技巧:合并后可以用indicator=True参数生成一个_merge列,标记每行数据来自左表、右表还是两边都有,做数据对账时特别好用。至于join方法,它本质上是merge的便捷封装,默认按索引连接,适合索引本身就有业务含义的场景。
四、常见坑点与实操建议
第一,映射产生的NaN要警惕。map遇到未知键会静默填NaN,不会报错,问题往往到分析阶段才暴露。建议映射后立即用isna().sum()检查缺失情况,或者干脆在字典里加一个兜底逻辑。第二,合并后索引重复是隐蔽的bug来源,尤其是concat之后继续做loc筛选,重复索引会返回意外的多行结果,养成拼接后重置索引的习惯。
第二,大数据量下尽量少用逐行apply。apply加lambda的写法方便但性能差,能换成向量化运算就换,能换成分组transform就别手写循环。第三,链式操作虽然优雅,但中间步骤出错时不好定位,关键流程建议分步执行并打印中间结果,确认无误后再合并成链式调用。
总的来说,多列映射负责改值,数据转换负责规范结构,合并操作负责把分散的信息聚合到一起,三者配合使用就能把绝大多数原始数据整理成可直接分析的标准DataFrame。掌握这些方法后,再配合groupby和透视表,基本可以应对日常数据清洗的全部需求。
Pandas数据重塑DataFrame合并多列映射修改时间:2026-09-04 22:44:54