ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Pandas数据清洗与预处理全指南:缺失值、重复值、异常值处理实战

2026/9/30 10:54:15 拓冰建站 浏览量
Pandas数据清洗与预处理全指南:缺失值、重复值、异常值处理实战 做数据相关工作的人多少都经历过这种场面辛辛苦苦把数据拿回来打开一看日期列里混着2024/03/012024-3-120240301三种写法订单金额有几千个空值同一个客户在同一分钟里下了三遍单商品名称列里的中文字符全变成了乱码。这时候脑子里只有一个念头用Pandas清洗数据赶紧把这些乱七八糟的东西收拾成能用的样子。数据清洗和数据预处理是数据分析流程里最不性感但最决定成败的一步。这篇文章不讲大道理就围绕Pandas库把数据清洗和数据预处理中那些高频操作、踩坑细节以及真实项目里的处理思路完整过一遍。适合刚接手脏数据的分析新人也适合准备大数据开发面试的同学以及所有被各种奇奇怪怪数据折磨过的朋友。1. 数据清洗到底在解决什么问题1.1 数据清洗解决的核心矛盾很多人以为数据清洗就是把空值删掉、把重复行去掉好像是一个机械劳动。实际上数据清洗解决的是四个层面的矛盾可信度、一致性、可用性和性能。可信度指的是数据本身能不能信。空值、重复值、异常值都会让统计结果失真。比如一份订单表里金额为空你直接算平均客单价分母把空值排除掉算出来可能虚高。一致性指的是同一件事在不同行里表达得不一样。最典型的就是日期格式、单位、编码方式不统一还有城市字段里北京和北京市并存。可用性指的是字段本身能不能直接用。很多原始字段是一大坨文本比如地址是广东省深圳市南山区XXX路1号你要分析城市维度就得把它拆出来。性能则是数据类型不合理导致内存爆炸比如把整数存成了字符串1亿行数据白白多占几倍内存。这里可以用一个生活类比从超市买回来的菜不是直接下锅的要摘掉烂叶子、洗掉泥、按菜谱切好配好。Pandas就是那把锋利的菜刀但菜刀不会替你做判断——哪些叶子该扔、哪些该留是清洗者根据业务规则决定的。这个判断才是数据清洗里最值钱的部分。1.2 清洗和预处理的分界线我经常被新手问数据清洗和数据预处理到底有什么区别我的判断标准很粗暴凡是让数据变正确的操作叫清洗凡是让数据更适合算法或分析的操作叫预处理。清洗是删、填、改、去重目标是把错误消除掉。预处理是变换、缩放、编码、构造特征目标是让数据形态更规整。举例来说把字符串10万转成数值100000这是清洗因为它把不规范的表达改正确了然后把所有价格做归一化让特征落在0到1之间这是预处理因为这个操作没有改变正确性只是调整了量纲。实际操作中两者会反复重叠。把字符串转成datetime类型既是在清洗因为日期原来没法比较大小也是为了后续时间序列分析做预处理。一条清洗流程里经常混着预处理的动作这不奇怪。关键是心里要有数这一步到底是在消除错误还是在为建模做准备。逻辑清晰了脚本才不会写着写着变成一团浆糊。1.3 不管项目大小先搭一条清洗流水线不管拿到的是几千行的表格还是几千万行的日志我基本都会走同一条流水线读取数据、概览、结构修复、缺失值处理、重复值处理、异常值处理、派生字段、保存输出。读取之后第一件事不是急着清洗而是先看全貌。df.shape看行列数df.info()看字段类型和空值情况df.describe()看数值分布df.head()看前几行的样子。这一套下来数据长什么样、哪里容易出问题基本心里有数了。概览这一步很多人跳过结果清洗到一半发现列名全是空格或者某个字段被读成了object类型又返工。结构修复的意思是先把列名规范化、把明显的类型错误修正再把清洗动作接上去。我在每个关键步骤后面都会打印一版数据量对比比如去重前10000行去重后9800行去掉200行。这在项目汇报里特别有用也能帮自己快速定位是哪一步把数据改坏了。提示清洗脚本一定要留中间结果。我习惯在每个阶段保存一份带后缀的CSV比如raw、clean1、clean2宁可多占点磁盘也比出了问题找不到原因强得多。2. 动手前的准备安装、数据结构和文件读写2.1 在PyCharm里装Pandas的正确姿势安装Pandas是很多人卡住的第一道坎。其实就一条命令pip install pandas。在PyCharm里最省事的方式是直接打开Terminal窗口执行PyCharm会自动使用当前项目的解释器。实测下来现代Python版本的Pandas都是二进制轮子装包pip会直接下载编译好的文件基本几分钟内就能装完。真正容易出问题的是网络环境如果下载慢或者超时建议换国内镜像源比如在pip后面加 -i 参数指定镜像。装完以后验证方法很简单在Python环境里执行 import pandas as pd然后打印 pd.version能输出版本号就说明成功了。如果用的是Anaconda那Pandas通常已经预装直接import即可。很多教程会让你用PyCharm的Settings - Project - Python Interpreter里点加号搜索安装这条路也可以但对于公司内网和网络受限的环境直接在Terminal里敲命令更可控。2.2 用最顺手的姿势创建Series和DataFramePandas最常用的两个数据结构就是Series和DataFrame。Series可以理解成一列带标签的数据DataFrame就是多行多列的表。刷头歌课程里pandas数据结构创建练习的时候很多人被绕晕其实只需要记住三种最常用的构造方式用字典构造DataFrame、用列表构造DataFrame、用numpy数组构造DataFrame。import pandas as pd # 字典构造每个键成为一列 df1 pd.DataFrame({ 订单号: [A001, A002, A003], 金额: [100, 200, None], 城市: [北京, 上海, 广州] }) # 列表构造列表里套字典或列表 df2 pd.DataFrame([[A001, 100], [A002, 200]], columns[订单号, 金额]) # 从numpy数组构造 import numpy as np df3 pd.DataFrame(np.random.rand(3, 2), columns[x, y])从字典构造最符合业务直觉键就是列名值就是列数据。从列表构造适合数据量小、手动拼接的场景。从numpy构造适合后面要接机器学习的场景。Series则更轻量比如单独取一列出来分析时它本质就是Series。理解了这个后面所有操作都不会跑偏。2.3 CSV、Excel和文本文件读写的坑一次讲完文件读写是数据清洗的入口读写错了后面全白搭。读CSV最常踩的几个坑编码不对导致乱码或报错、分隔符不是逗号、第一行不是列名、某些列被自动读成了object类型。我在处理中文CSV时read_csv里最常用的是这套组合df pd.read_csv( 订单数据.csv, encodingutf-8, # 如果乱码试 gbk/gb18030 dtype{订单号: str}, # 防止前导0被丢掉 parse_dates[下单时间], # 把日期列直接解析成datetime keep_default_naFalse # 避免把空字符串也当成NaN )encoding是重灾区。CSV文件可能是utf-8、gbk、gb18030还有带BOM的。遇到UnicodeDecodeError不要慌逐个试用gb18030的成功率很高因为它覆盖的字符集比gbk更全。订单号这类字段一定要用dtype指定成字符串否则012345会被读成12345前导0直接消失这个错误非常隐蔽。读到Excel文件的坑相对少一点重点是sheet_name参数。默认读第一个sheet如果你要的数据在第二个表里却忘了指定分析结果就会莫名其妙地不对。文本文件用read_table本质是read_csv的变体只是默认分隔符是制表符读日志类数据时经常用到。写文件的坑也有一个很典型to_csv之后Excel打开乱码。解决办法是保存时指定encodingutf-8-sig它会写入带BOM的utf-8Excel就能正常识别了。我踩过好多次后来直接把这个参数写进习惯操作里。3. 清洗三大件缺失值、重复值、异常值的处理3.1 缺失值先搞清楚数据为什么缺处理缺失值的第一步不是fillna也不是dropna而是先统计一下缺哪些列、缺多少、缺的分布有没有规律。用一行代码可以快速看全局df.isna().sum()拿到结果后要判断缺失的机制。数据缺失通常有三种情况完全随机缺失、随机缺失、非随机缺失。完全随机缺失影响相对小删掉或者用均值填充影响都不大。随机缺失跟其他字段有关联比如高收入人群更不愿意填年龄这时候简单填均值就会引入偏差。非随机缺失则是最麻烦的缺失本身就携带业务信息比如某个传感器坏了导致数据断档这种缺失恰恰是需要发现的信号。处理方式上如果缺失行数占总量的比例极低比如5%以内而且这些行对分析目标没有特殊意义直接dropna是成本最低的。如果缺失字段是有业务含义的重要字段比如价格、年龄、评分那就需要考虑fillna。填什么值取决于业务场景连续数值优先考虑中位数或均值趋势性数据用前向填充ffill或后向填充bfill分类字段可以用众数。但要注意fillna只是把洞补上不代表数据变准了它只是让后续的统计计算能跑起来。3.2 重复值去重前先想想业务重复这回事df.duplicated()和df.drop_duplicates()是去重的一对组合拳。但最容易出问题的是重复的定义。完全相同的整行去掉没有争议。但现实中更多是部分字段重复——同一订单出现两次订单号相同但价格字段有一个为空同一用户在同一时间重复提交了表单同一职位在不同渠道被重复采集。处理这类业务重复要用subset参数指定判断依据。比如招聘数据里判断同一岗位在同一天内重复发布依据应该是公司名、职位名、发布日期三个字段的组合df.drop_duplicates(subset[公司名, 职位名, 发布日期], keepfirst, inplaceTrue)keep参数也很关键。keepfirst保留第一次出现的行keeplast保留最后一次。我一般先按时间排序再决定保留哪一条因为业务上最后一次状态往往更接近真实。如果同一条数据在不同渠道被重复采集保留哪份、合并哪些字段都必须在清洗文档里写明。3.3 异常值别上来就删先分类异常值处理是我见过最容易犯错的环节很多人一看到数值离谱就直接删掉结果把真实波动当成噪声清走了。我建议先把异常值分成三类录入错误型、业务特殊型、真实极端型。录入错误型比如金额为负数、年龄为200岁、价格突然变成0这种通常是系统bug或人工误录可以直接修正或删除。业务特殊型比如促销订单里出现1分钱商品虽然不符合常规价格分布但它是真实现象直接删掉会把活动分析结果带偏。真实极端型比如股价暴涨暴跌这种恰恰是分析的重点。筛查异常值的常用方法有两个IQR法和3σ法。IQR法用四分位距把小于Q1-1.5倍IQR或大于Q31.5倍IQR的点标出来。3σ法假设数据近似正态把偏离均值超过3个标准差的值标出来。这两种方法都只是候选名单最终要不要处理得回到业务逻辑判断。我自己处理订单金额异常时会把异常数据单独导出来逐条看而不是直接删。很多次都发现异常背后是真实业务活动比如大客户批发采购、测试订单、退款前的状态。清洗的目标不是把数据洗得好看而是把数据洗得可信。4. 类型转换、正则提取与时间序列预处理4.1 数据类型转换astype和to_datetime怎么配合Pandas读入数据后经常出现数字变字符串的情况。最直接的手段是astype但astype有个毛病遇到无法转换的值会直接报错。比如一列价格里有暂无两个字你执行df[价格].astype(float)整个程序直接崩溃。这时候要用pd.to_numeric配合errorscoerce转不了的值变成NaN后续再单独处理这些NaN。df[价格] pd.to_numeric(df[价格], errorscoerce) df[下单时间] pd.to_datetime(df[下单时间], errorscoerce)to_datetime也是清洗日期数据的主力。它最厉害的地方是能自动识别2024/03/012024-3-120240301这几种常见格式把整列统一成datetime类型。遇到完全解析不了的值errorscoerce可以把它变成NaT也就是缺失时间。这一步做完日期排序、按月聚合、算时间间隔就都顺了。还有一个常被忽略的优化把低基数的字符串列转成category类型。比如城市列就几十个不同取值用category存内存能减少一大截groupby的速度也会提升。这个操作不会改变数据内容属于低成本高收益的预处理。4.2 正则表达式在清洗里的高频用法正则表达式在数据清洗里的地位几乎是万能拆字段工具。Pandas的str系列方法里我用得最多的是str.extract、str.replace、str.contains。比如从地址里提取省和市df[省份] df[地址].str.extract(r(.?省|.?自治区|北京|上海|天津|重庆)) df[城市] df[地址].str.extract(r(.?市))从招聘信息里拆分薪资范围df[月薪下限] df[薪资].str.extract(r(\d)k-(\d)k)[0] df[月薪上限] df[薪资].str.extract(r(\d)k-(\d)k)[1]实际写法上str.extract会把括号里的内容提取成一个新列如果有多个括号就提取成多列。str.replace支持正则替换可以批量清理文本里的特殊符号、全角空格、连续空白符。str.contains则用来筛选包含特定关键词的行比如判断职位名称里是否包含数据。正则写起来容易出错我的经验是先在在线正则工具或Python里对一两行数据反复测试确认无误后再套到整列上。坑主要在中文匹配上中文里有些字符全角和半角混用建议先做统一的字符清洗比如把全角数字转半角、去掉空格再做正则提取。4.3 ewm函数参数到底怎么选搜索热词里专门有pandas中ewm函数参数说明大家在用ewm时确实容易卡住。ewm是指数加权移动平均用于平滑时间序列、突出趋势、减少噪音。它比简单移动平均的加权方式更聪明离当前时刻越近的数据权重越大。ewm函数的主要参数有com、span、halflife、alpha这四者是等价的指定其中任意一个Pandas会自动推算出对应的alpha。alpha是基础加权系数公式是span 2/alpha - 1com (1-alpha)/alphahalflife对应权重衰减到一半所需的时间。实际业务里span最直觉你想让平均窗口大约覆盖最近多少个周期就填多少。比如按天数据想平滑最近7天的影响可以设span7。还有一个参数min_periods很实用它控制窗口里至少要有多少期数据才输出结果。数据开头部分样本太少均线会剧烈跳动设一个min_periods可以避免前几个月被极端值带偏。注意ewm做平滑不等于预测它只是把历史序列变顺为后续的趋势分析提供更稳的输入。我用它处理农产品价格日数据时效果比直接看原始价格曲线清楚很多但周期性的节假日价格波动还是需要结合业务判断不能盲目依赖平滑结果。4.4 编码与标准化把分类字段变成算法能吃的东西进入机器学习前的预处理绕不开分类编码和数值标准化。Pandas里最方便的分类编码是pd.get_dummies把一列城市名拆成一堆0/1列。这种方法简单直观适合类别数量少的场景。类别一多列数爆炸就需要考虑用factorize或者更专业的编码方式。pd.factorize会把每个类别映射成一个整数编号适合对类别间的顺序没有特别要求的情况。如果要排序性质的自定义映射比如把低/中/高映射成1/2/3直接用map或replace更可控。数值标准化我常用的是min-max归一化和z-score标准化。前者把数值缩放到0到1后者把数据变成均值为0、标准差为1。选择标准很简单如果后续算法对数值范围敏感比如聚类、KNN、神经网络就要做标准化如果是树模型标准化一般不是强制要求。注意如果数据里有异常值min-max归一化会被拉偏最好先处理异常值再做归一化。5. 三个真实场景复盘招聘、农产品价格、网约车5.1 招聘数据清洗拆薪资、修城市、去重复职位招聘数据清洗几乎涵盖了所有基础操作是特别好的练手项目。很多实验课程里都有MapReduce综合应用案例——招聘数据清洗这在大数据框架里能做但在数据量不大时用Pandas清洗会更高效逻辑也更直观。拿到一份招聘数据通常先解决几个问题。第一薪资字段往往是一个完整字符串15k-25k或者15-25K·14薪要用正则拆成最低薪资、最高薪资两个数值列再把K换算成真实金额。第二城市字段可能是北京-海淀区这种组合需要split拆出城市或者保留城市-区县作为单独维度。第三同一职位被多渠道重复采集需要按公司、职位、发布日期去重。第四有些字段缺失严重比如学历要求有60%是空的这时候要评估是删行还是把缺失单独作为一个类别。我用Pandas处理这类数据时习惯先做一遍逐字段体检输出每个字段的非空率、唯一值数量、样例。体检报告一出来哪些字段可以放心用、哪些字段要重建一目了然。这个习惯比任何高级算法都管用。5.2 农产品价格数据清洗单位、市场和异常价格农产品价格数据清洗是另一个经典场景对应热搜里农产品价格数据清洗--python和农产品价格数据清洗-spark。这种数据通常来自不同市场的采集系统第一个大坑就是单位不统一有的市场记元/公斤有的记元/斤还有的记元/吨。清洗时要先把单位统一否则算出的平均价完全失真。第二个大坑是价格异常。农产品价格波动剧烈是正常的但清洗时要把录入错误识别出来价格为0、价格为负数、价格突然变成前一天的10倍以上。我的处理方式是先算每天每种产品的价格中位数和标准差把偏离中位数超过一定倍数的记录单独拎出来人工核对。因为节假日、天气灾害导致的价格暴涨是真实信息不能一刀切删掉。日期字段也经常有问题。采集员可能漏填日期导致时间序列断档。此时需要看有没有相邻的市场、同类产品价格可参考或者用前向填充但一定要在报告里标明哪些日期是补的。对农产品价格这种强周期数据做ewm平滑后再看趋势图会干净很多。5.3 网约车数据清洗时间、坐标、轨迹去噪网约车大数据项目基于Spark或MapReduce看起来很大数据但清洗的核心逻辑和Pandas是一样的只是换了个执行引擎。Pandas适合做抽样数据和小规模数据的探索等逻辑跑通了再平移到Spark里去处理全量数据这是很合理的路线。网约车数据清洗绕不开这几个点时间戳格式要统一下单时间、上车时间、下车时间要能被比较先后顺序订单记录里会出现同一订单号重复上报而且不同副本之间经纬度有偏差去重时不能只看订单号还要结合时间戳坐标字段会出现明显的越界值比如经纬度落在城市范围之外这是GPS漂移需要按城市边界过滤。还有一种更隐蔽的问题行程时长和距离算出来的速度异常。比如两分钟开了50公里这种记录大概率是GPS漂移或数据采集错误。我的经验是给速度设业务上下限超过就标记成异常而不是直接删除因为有时候是司机跨城市接单速度上限可以适当放宽。6. 实战里最烦人的几个坑和排查思路6.1 中文乱码与编码打架中文乱码是我见过最多的抱怨。读取时解码错误要么抛异常要么读出满屏锟斤拷。排查思路是先搞清楚文件是什么编码。Windows下Excel另存的CSV默认是gbk很多工具生成的CSV是utf-8还有带BOM和不带BOM的区别。在不确定时我建议用二进制方式打开文件把开头几个字节打印出来判断。写入时也容易出问题。前面提到的to_csv(encodingutf-8-sig)是解决Excel打开乱码的万能钥匙。如果要在Windows命令行里看打印结果不乱码还可以考虑控制台编码设置但更推荐避免在终端打印大量中文把结果写到文件里看。6.2 SettingWithCopyWarning不是报错但比报错更烦Pandas里最常见的警告就是SettingWithCopyWarning。它不是报错但意味着你的赋值操作可能没有作用到原DataFrame上。典型场景是你先用df[df[城市]北京]筛选出一个子集df_sub然后执行df_sub[价格]100这时警告出现而且价格可能根本没有写入原表。根因是链式索引导致的视图和副本歧义。正确做法是筛选时直接加.copy()比如df_sub df[df[城市]北京].copy()。或者用loc在原始DataFrame上直接赋值df.loc[df[城市]北京, 价格]100。我在带新人时经常强调筛选用于查询没问题但要修改数据要么copy出来改要么用loc在原表上改千万别在链式索引的中间结果上赋值。6.3 索引错乱导致数据张冠李戴Pandas的索引是隐形的但一旦错乱数据就会悄悄对不上。最常见的是dropna、drop_duplicates之后索引还保留原来的行号。如果你这时候用索引去对齐另一个DataFrame就会错位。解决方案很简单清洗完成后执行reset_index(dropTrue)把索引变成连续的0到n-1。还有一个更隐蔽的坑concat或merge两个DataFrame时如果索引没有重置会出现大量NaN。合并前后检查一遍索引类型和唯一性能避免半夜调试到崩溃。6.4 数据量大时内存和性能优化Pandas处理几百万行是家常便饭但到了几千万行就开始吃力。我的优化顺序是先看数据类型把数值列从float64降到float32整数列尽量用int32或int64字符串列转category再看是否有用不到的列读文件时直接用usecols只读需要的列最后考虑分块读取用chunksize参数边读边处理。如果数据量真的到了Spark的规模就不要硬用Pandas扛了。Pandas的价值在于快速验证逻辑、做小样本探索而全量清洗可以交给Spark或数据库。记住这个分工能省下大量时间。我自己在实际操作中的一个体会是数据清洗这门手艺60%靠熟练40%靠对业务的理解。同一个空值在不同项目里该填均值还是该删行没有标准答案。那些看起来很琐碎的判断最后决定了一个模型是靠谱还是跑偏。踩过几次坑之后我现在做任何清洗任务都会习惯性地先写一份数据体检摘要把每个字段的非空率、类型、样例统计出来贴到项目文档里。最后再分享一个小技巧把清洗流程封装成一个个小函数用管道思想串起来每一步都留日志。这样不管数据来源怎么换清洗逻辑都能复用项目交接的时候别人接手也轻松得多。