想象你是餐厅里掌勺十年的大厨,明天要办一场百人宴会。清晨五点,后厨采购员送来一堆食材:几根叶子发黄的青菜、两颗蒸煮后长得几乎一模一样的土豆——仔细看才发现是买重了、一个已经发芽的洋葱、还有长短参差需要重新切配的胡萝卜。你敢把这一堆东西直接倒进锅里吗?当然不敢。你会先把食材倒在案板上,挑出烂叶、捡出发芽的、把买重的归拢、把大小不一的重新切配,最后按宴席菜单的顺序摆盘备菜。数据科学的世界里,你打交道的「食材」就是数据集——来自业务系统、日志、第三方接口的原始数据,往往充满了缺失、重复、异常和格式混乱。无论你的 AI 模型多么精巧,喂给它这样的脏数据,它做出的决策就像用了变质食材的菜肴,轻则味道古怪,重则让人食物中毒。这篇文章的系统方法,就是教你像一位真正的后厨大厨一样,为数据「备菜」:识别脏数据、设计清洗方案、动手处理、验证效果、记录全过程,让一团乱麻的原始数据变成下游分析和模型训练放心使用的干净佳肴。

岗位关联

掌握脏数据识别与清洗,你将成为团队里「数据靠得住」的那个人——无论是数据仓库建设、模型训练准备还是报表治理,干净的数据是一切分析可信的地基。

目录
  1. 可视化演示
  2. 脏数据类型辨识
  3. 缺失值处理
  4. 重复数据去重
  5. 异常值检测
  6. 格式统一清洗
  7. 清洗流程设计
  8. 效果验证评估
  9. 日志与可追溯
  10. AI 应用场景
  11. Python 演示
  12. 常见误区
  13. 面试题

可视化演示

脏数据识别与清洗是发现并处理数据集中错误、异常、重复、格式混乱数据的方法。你将学到脏数据类型识别、规则与统计检... 切换模式查看不同维度。

本节配有交互动效(进入视口后自动加载,约 3KB)。

脏数据类型体:核心原理 动态展示关键流程与数据流动 交互控件调节参数实时渲染 f(x) = transform(x) 核心概念的可视化呈现

脏数据类型辨识

大厨拿到食材后做的第一件事,就是把所有食材倒在案板上分门别类检查:哪些叶子蔫了、哪些土豆发芽、哪些标签贴重了。这一步在数据领域叫「数据剖析」,而食材的各种问题恰好对应脏数据的四大类型——它们是数据世界的四种「腐败形态」。

第一类是缺失值,好比买来的一袋萝卜缺了几根,某字段没有值,比如用户注册时没填手机号;第二类是重复记录,好比两捆葱长得一模一样其实是买重了,同一行数据出现了多次;第三类是异常值,好比一箱苹果里混进一个烂透了的,比如年龄字段出现 200;第四类是格式混乱,好比胡萝卜长短参差,同一种数据有多种写法,日期有的写 2024-01-01,有的写 2024/1/1。

要衡量一袋食材整体脏不脏,光看单根萝卜不够。业内用「脏数据比率」来量化:先逐条记录按四类规则打标,只要命中任一问题就计为脏数据,再除以总记录数。这个指标就像大厨给食材拍一张「体检报告」,一眼看出整批货的污染程度。

识别完问题,就像大厨把烂叶子挑到一边。接下来就是对症下药:缺失的补齐、重复的去掉、异常的修正、格式的统一。而这一切,都从分类检查开始。

Dr=NdirtyNtotal×100%D_r = \displaystyle \frac{N_{dirty}}{N_{total}} \times 100\%
这个公式衡量整张表的脏数据占比。一般超过 10% 就要警惕,超过 30% 基本不能直接用于建模,需要先做一次系统清洗。
  1. N_{dirty}(脏数据条数)统计时,只要一条记录存在缺失、重复、异常、格式问题中的任意一种,就计入脏数据。这个数越大说明问题越多。
    Ndirty=存在任一问题(缺失、重复、异常、格式错误)的记录数N_{\text{dirty}} = \text{存在任一问题(缺失、重复、异常、格式错误)的记录数}
  2. N_{total}(总记录条数)数据集总的行数,是计算占比的分母。注意要用清洗前的原始总数,清洗后再算会低估污染程度。
    Ntotal=数据集原始总行数N_{\text{total}} = \text{数据集原始总行数}
  3. ×100%(换算百分数)把比例放大成百分数,方便在团队沟通和报告中横向对比不同数据源的质量。
    Dr=NdirtyNtotal×100%D_r = \frac{N_{\text{dirty}}}{N_{\text{total}}} \times 100\%

缺失值处理

大厨发现有几根胡萝卜缺了半截,扔掉可惜,不扔又影响摆盘。数据里的「缺斤少两」就是缺失值。处理缺失值不能一删了之,首先要判断它属于哪种「缺法」。统计上把缺失分为三种机制:完全随机缺失(MCAR)像买菜时碰巧掉了一根;随机缺失(MAR)像只有贵的食材才容易缺货——缺失跟其他字段的值有关;非随机缺失(MNAR)则像坏食材被人故意藏起来——缺失本身就跟缺失的值有关。

对 MCAR 可以放心删除,因为剩下的数据仍然随机且有代表性;对 MAR 和 MNAR,直接删除会引入偏差,需要做填充或建模估计。填充策略分三档:均值或中位数填充适合数值列且分布对称时用,众数填充适合分类列,插值则适合时间序列——它能利用前后数值的趋势,像大厨用旁边的食材拼出完整的摆盘一样自然。

删除策略也不是不能用,但要满足「缺失比例低且完全随机」的条件,比如缺失率低于 5% 的字段。否则,粗暴删除会让数据集「瘦身减料」,反而损害下游模型的稳定性。

像大厨不会拿缺半截的胡萝卜做刺身一样,缺失值处理讲究「分锅烹制」:先判断缺失机制,再决定删除、填充还是插值。缺得随机、缺得少,大胆删;缺得有规律、缺得多,想办法补。

缺失率=NmissingNtotal×100%\text{缺失率} = \displaystyle \frac{N_{missing}}{N_{total}} \times 100\%
缺失率是评估一列「缺货」严重程度的度量。缺失率越高,越不能简单删除,而要动用填充、插值甚至专门的建模方法。
  1. N_{missing}(缺失值个数)该列中为空值的单元格数量,空值包括 NULL、NaN 和空字符串三种常见形式。
    Nmissing=该字段为空的记录数N_{\text{missing}} = \text{该字段为空的记录数}
  2. N_{total}(总记录数)整张表的总行数,与脏数据比率公式中的分母含义一致。
    Ntotal=字段所属表的总行数N_{\text{total}} = \text{字段所属表的总行数}
  3. ×100%(转百分比)将缺失率表述成百分数,便于设定阈值,比如超过 30% 就考虑整列剔除。
    缺失率=NmissingNtotal×100%\text{缺失率} = \frac{N_{\text{missing}}}{N_{\text{total}}} \times 100\%

重复数据去重

采购员买菜买重了的情况时有发生——两捆外表完全没有差别的葱,其实是同一次采购的重复记录。数据里这种「一模一样的复制粘贴」叫完全重复;还有一种更隐蔽的,像两棵看起来一样但粗细略有差异的白菜,叫相似重复,比如一人手机号变了、地址没变,两条记录大部分字段相同但又有细微差别。

去重的第一步是定义规则:完全重复可以直接按全部字段精确匹配删除;相似重复则需要借助编辑距离、SimHash 等相似度算法,计算记录间的「长得像不像」,超过阈值就认为是同一条数据。阈值怎么定,是大厨说了算——业务规则,比如客户表里姓名、身份证号、手机号三个字段同时匹配才合并,防止把两个真的不同的人误判为一条。

保留策略同样重要:去重不是见重就删,而是要保留信息最完整、创建时间最早或业务状态最新的一条记录,其余条目合并其独有信息后归档。这样既消除了冗余,又不丢失有价值的内容。

去重就像大厨整理食材架:外观完全一样的合并,细微差别的按「哪个新鲜留哪个」的原则处理,最后每一颗土豆都有自己的位置,不再有碍事的双胞胎占着地方。

重复率=NdupNtotal×100%\text{重复率} = \displaystyle \frac{N_{dup}}{N_{total}} \times 100\%
重复率表示数据集中有多少比例的记录是重复的。重复率越高,说明数据写入环节越缺乏唯一性约束。
  1. N_{dup}(重复记录条数)经去重规则识别出的重复行数,既包括完全重复,也包括超过相似度阈值的相似重复。
    Ndup=被判定为重复的记录总数N_{\text{dup}} = \text{被判定为重复的记录总数}
  2. N_{total}(总记录数)去重前的原始总行数,用它做分母才能反映真实的冗余程度。
    Ntotal=原始数据总行数N_{\text{total}} = \text{原始数据总行数}
  3. ×100%(转百分比)把重复率转成百分数,当它超过 10% 时通常需要排查上游数据写入逻辑,而非仅仅在清洗层兜底。
    重复率=NdupNtotal×100%\text{重复率} = \frac{N_{\text{dup}}}{N_{\text{total}}} \times 100\%

异常值检测

一箱苹果里混进一个烂透的,不仅不能上桌,还会传染旁边的苹果。数据里的「烂苹果」就是异常值——数值明显偏离正常分布,比如一个人的年龄是 200 岁,一笔订单金额是 999999 元。识别异常值主要有三类方法:箱线图法、Z-score 法和业务规则法。

箱线图法不假设数据服从什么分布,用四分位数画出「箱子」:箱子的上下边缘分别是 Q3 和 Q1,箱外 1.5 倍四分位距之外的点就是离群点。Z-score 法先把数据标准化,看每个值偏离均值多少个标准差,一般|Z|大于 3 就认为是异常。业务规则法最直接——大厨知道海鲜不能常温过夜,数据工程师也知道年龄不可能超过 120 岁,这类规则能快速锁定错误。

检测到异常值后不是只有删除一条路。先要判断它是「脏异常」还是「真异常」:录入错误、测量故障造成的脏异常直接修正或剔除;欺诈交易、突发热点这类真异常反而要保留,因为它们往往是最有价值的信号。修正策略包括按边界值截断、用缺失值逻辑重新填充,或者标记后交给业务方人工确认。

大厨挑出烂苹果后会分辨是放坏的还是本身就坏:放坏的扔掉,但如果是「黑心果」被商家以次充好,那不仅不能扔,还要留下当证据。异常值处理同样如此——识别只是第一步,结合业务判断才能决定修、删还是留。

Zi=xi−μσZ_i = \displaystyle \frac{x_i - \mu}{\sigma}
Z-score 把每个数据点转换成「它离平均值有几个标准差」。绝对值越大离群越远,超过 3 通常判定为异常。
  1. x_i(第 i 个观测值)当前要判断是否异常的单条数据取值,比如某用户的年龄字段。
    xi=第 i 条记录的字段值x_i = \text{第 i 条记录的字段值}
  2. μ(均值)该列所有数值的平均数,代表数据的「中心位置」,相当于一大箱苹果的平均重量。
    μ=1n∑j=1nxj\mu = \frac{1}{n} \sum_{j=1}^{n} x_j
  3. σ(标准差)衡量数据围绕均值波动的幅度,标准差大说明数据天生就很「散」,不能轻易判异常。
    σ=1n∑j=1n(xj−μ)2\sigma = \sqrt{\frac{1}{n} \sum_{j=1}^{n} (x_j - \mu)^2}
  4. Z_i 的绝对值大小Z 的绝对值越大,说明该点离中心越远,超过 3 时通常列入异常候选。
    ∣Zi∣>3⇒xi 判定为异常值|Z_i| > 3 \Rightarrow x_i \text{ 判定为异常值}

格式统一清洗

一筐胡萝卜有的切块、有的切丝、有的切末,下锅前根本没法统一火候。数据格式混乱也是如此:日期有的写「2024-01-01」有的写「2024/1/1」,手机号有的带空格有的不带,商品名称有的全角字符有的半角字符,编码有的是 UTF-8 有的还是 GBK。这些问题不影响「能不能吃」,但严重影响了「能不能一起做菜」。

格式统一分三层:第一层是数据类型转换,把字符串型的数字转成数值型、把字符串型的日期解析成 datetime 类型,这样模型才能正确计算;第二层是文本规范化,去除首尾空格、统一大小写、全半角互换、同义词替换,让「北京」「北京市」「beijing」归到同一个标准值;第三层是日期与编码统一,日期统一成 ISO 8601 标准的 YYYY-MM-DD,文本统一成 UTF-8 编码,避免跨系统乱码。

格式统一的产出可以用「格式合规率」衡量:检查每个字段是否符合既定标准格式,符合的占比越高,说明数据越「齐整」。就像大厨检查切配结果——每根萝卜条都粗 0.8 厘米,这桌菜才有统一的美感。

就像大厨把切好的食材按规格装盘,格式统一的目的是让数据「刀工一致」,所有字段整齐划一,后续无论做聚合、关联还是建模,都不会被奇形怪状的写法绊倒。

合规率=NvalidNtotal×100%\text{合规率} = \displaystyle \frac{N_{valid}}{N_{total}} \times 100\%
合规率反映字段值符合预定义格式的比例。它衡量的是「样子对不对」,而不是「内容对不对」。
  1. N_{valid}(格式合规的字段数)逐字段校验类型、格式、编码均符合规范的数量,比如所有日期都能被解析成标准日期格式。
    Nvalid=通过格式校验的字段值个数N_{\text{valid}} = \text{通过格式校验的字段值个数}
  2. N_{total}(总字段数)参与校验的字段值总数,通常按列统计,也可按整行统计。
    Ntotal=受检字段值总数N_{\text{total}} = \text{受检字段值总数}
  3. ×100%(转百分比)合规率越接近 100%,说明数据格式越规整,下游处理时越不需要「临时救火」。
    合规率=NvalidNtotal×100%\text{合规率} = \frac{N_{\text{valid}}}{N_{\text{total}}} \times 100\%

清洗流程设计

大厨备菜从来不会随手乱来:先摘菜、再洗菜、再切菜、再摆盘,顺序颠倒就会手忙脚乱。数据清洗也有一套标准的流程设计,核心四步是:数据剖析 → 清洗规则定义 → 执行清洗 → 迭代优化。

数据剖析是第一步,相当于把食材全部倒出来看清家底——每一列的缺失率、唯一值数量、取值分布、数据类型,生成一份「数据体检报告」。第二步是清洗规则定义,把「缺失怎么填、重复怎么去、异常怎么修、格式怎么改」写成语义明确的规则,比如「age 列小于 0 或大于 120 的记录标记为异常并置空」。规则越明确,清洗行为就越可解释。

第三步执行清洗,可以是 SQL 脚本、Python 脚本或 ETL 工具中运行;第四步迭代优化最重要——清洗不是一次性的,跑完一轮看效果,发现误伤或漏网就调整规则再跑,直到数据质量稳定达标。整个流程可以用数据质量综合评分来驱动。

流程设计就像宴席的备菜动线:每道菜有它的处理顺序,每一步都有章法。剖析是摸清家底,规则是定菜谱,执行是动手做,迭代是不断试味调整,最终端出一桌质量稳定的好菜。

Q=Co+Ca+Cc+Ct4Q = \displaystyle \frac{C_o + C_a + C_c + C_t}{4}
这是数据质量的「四维总分」:完整性(Completeness)、准确性(Accuracy)、一致性(Consistency)、时效性(Timeliness)各占一席,取平均后得到一个 0-100 分的综合评分。
  1. C_o(完整性得分)字段非空的比例越高,完整性越好,它是缺失率的反向指标。
    Co=非空值数量÷总记录数C_o = \text{非空值数量} \div \text{总记录数}
  2. C_a(准确性得分)值与真值或业务期望一致的程度,比如通过规则校验和抽样人工核对得到。
    Ca=1−错误值数量总记录数C_a = 1 - \frac{\text{错误值数量}}{\text{总记录数}}
  3. C_c(一致性得分)同一实体在不同表、不同字段中的表达是否统一,比如「北京市」不会在一张表里出现三种写法。
    Cc=符合一致性约束的记录数总记录数C_c = \frac{\text{符合一致性约束的记录数}}{\text{总记录数}}
  4. C_t(时效性得分)数据是否及时更新,过期的数据即使完整准确也不可用。
    Ct=未过期记录数总记录数C_t = \frac{\text{未过期记录数}}{\text{总记录数}}

效果验证评估

菜做完不能直接端上桌,大厨总会先拿筷子尝一口——咸淡是否合适、摆盘是否整齐、火候是否到位。数据清洗同样需要「试菜」环节,也就是效果验证。常用的验证方法有三类:质量指标对比、下游任务验证和持续监控。

质量指标对比是最直观的方式:清洗前后分别计算完整性、准确性、一致性、时效性四个维度的得分,看综合评分提升了多少。假设清洗前评分 72 分,清洗后 96 分,提升率就是 33%。下游任务验证更贴近实际效果——用清洗前后的数据分别训练同一个模型或生成同一张报表,看准确率有没有上升、报错有没有减少,让数据质量改进直接体现为业务成果。

持续监控则是把验证变成习惯:数据是流动的,今天的干净数据明天可能又被脏数据污染。建立定时任务每天检查质量评分,一旦低于阈值就告警,让问题在影响业务前被发现。验证的量化可以用质量提升率来表达。

尝过菜才敢上桌。效果验证的价值在于让清洗从「感觉应该有用」变成「数据证明有用」——提升率摆在那里,下游模型的效果摆在那里,这场清洗才真正被承认。

ΔQ=Qafter−QbeforeQbefore×100%\Delta Q = \displaystyle \frac{Q_{after} - Q_{before}}{Q_{before}} \times 100\%
质量提升率对比清洗前后综合质量评分。数字为正且越大,说明这次清洗「做菜」的功夫越值得。
  1. Q_{after}(清洗后评分)按照四个质量维度打分并求平均后的结果,比如 96 分。
    Qafter=清洗后四维平均质量分Q_{\text{after}} = \text{清洗后四维平均质量分}
  2. Q_{before}(清洗前评分)同一批数据在清洗前的四维平均分,比如 72 分。
    Qbefore=清洗前四维平均质量分Q_{\text{before}} = \text{清洗前四维平均质量分}
  3. 相减、相除并乘 100(变化率)先求差值再除以基线,得到相对提升幅度。若 Q_before 为 0,说明基线太差,不宜直接用该公式。
    ΔQ=Qafter−QbeforeQafter×100%\Delta Q = \frac{Q_{\text{after}} - Q_{\text{before}}}{Q_{\text{after}}} \times 100\%

日志与可追溯

老练的大厨身后总挂着一块记录板:今天处理了什么食材、扔掉了什么、加了什么调料、几点做的。万一客人反映菜品有问题,他能按记录倒排查到是哪一步出了差错。数据清洗也必须有这样一块「记录板」——清洗日志。

清洗日志记录每一次操作的五要素:谁(哪个任务)、什么时候、对哪个字段、执行了什么规则、影响了多少行。变更记录则更进一步,把清洗前后的数据快照都保存下来,比如「清洗前 age 列均值 32,清洗后 29」,这样任何一步都有据可查。版本回溯让工程师可以一键回到清洗前的状态重新处理,就像大厨觉得这次调味不对,能倒掉重来。

审计合规是日志的终极价值:金融、医疗等行业要求你能够证明数据处理过程合法合规。完整日志就是你的「厨房监控录像」,监管问询时拿得出、说得清。可追溯覆盖率是衡量日志完整度的指标。

记录板上的每一笔,都是大厨职业素养的证明。清洗日志和版本回溯让数据管线不再是黑盒子——出了问题能找到源头,审计时候能拿出证据,这才是成熟数据团队应有的底气。

Ctrace=NloggedNtotal×100%C_{trace} = \displaystyle \frac{N_{logged}}{N_{total}} \times 100\%
可追溯覆盖率表示有多少条记录能通过日志追踪到完整的清洗历史。覆盖率越高,数据的可信度和可审计性越强。
  1. N_{logged}(有日志记录的数据条数)在清洗表中能找到完整变更历史的记录数量。
    Nlogged=可以追溯清洗历史的记录数N_{\text{logged}} = \text{可以追溯清洗历史的记录数}
  2. N_{total}(总记录数)经过清洗的全部记录数,作为可追溯性的分母。
    Ntotal=清洗涉及的总记录数N_{\text{total}} = \text{清洗涉及的总记录数}
  3. ×100%(转百分比)覆盖率未达 100% 说明有「黑箱操作」,需要补记日志。
    Ctrace=NloggedNtotal×100%C_{\text{trace}} = \frac{N_{\text{logged}}}{N_{\text{total}}} \times 100\%

AI 应用场景

数据仓库 ETL 清洗

阶段:数据集成与数据仓库建设

解决的问题:数仓建设中,来自业务库、埋点日志、第三方 API 的数据格式各异、重复率高、存在大量缺失。如果不提前清洗,事实表和维度表关联时会出现数据对不上、指标统计翻倍等问题,报表结果自然没人敢信。

具体案例:某电商团队搭建用户行为数仓,将订单表和用户表接进来后,先用数据剖析脚本生成质量报告,发现 user_id 缺失率 6%、重复订单号 1200 条、日期格式有两种。随后按清洗规则执行去重和格式统一,清洗完成后跑了一遍「订单金额汇总」对比,金额偏差从 8.2% 降到 0.3%,下游报表团队终于敢直接用数仓出数。

机器学习训练数据准备

阶段:模型训练前的数据准备

解决的问题:模型训练对数据质量极其敏感。训练集里的重复样本会让模型对高频重复模式过拟合,异常值会把损失函数拉偏,缺失值未经处理会导致模型训练报错或隐含偏差。清洗过的数据能让模型更稳定地收敛。

具体案例:某风控团队训练信用评分模型,原始样本 200 万条,其中 20% 重复、10% 缺失、3% 年龄异常。经过去重、缺失填充、异常值截断后,模型 AUC 从 0.82 提升到 0.89,坏样本召回率提升近 7 个百分点。清洗这一步的收益比换一个复杂模型更大。

BI 报表数据治理

阶段:数据分析与报表展示

解决的问题:BI 报表口径不统一、数据打架是数据团队的常见痛点。比如「北京市」和「北京」两个值在 group by 时被分成两行,销售额被重复计算。格式统一和数据去重直接消除这类「看起来不对劲」的报表问题。

具体案例:某零售企业月度经营分析会上,华北区销售额连续两月对不上,排查后发现门店维度表里有 37 家门店存在相似重复记录,导致指标翻倍。数据团队用相似度匹配把这批门店合并,同时补充了清洗日志和版本快照,从此每月报表都能追溯口径变化。

Python 演示

python74 行
import pandas as pdimport numpy as np  class DataCleaner:    def __init__(self, df):        self.df = df.copy()        self.log_list = []        self.missing_count = 0        self.duplicate_count = 0        self.outlier_count = 0     def report_missing(self, columns=None):        cols = columns if columns is not None else self.df.columns        return self.df[cols].isnull().sum()     def handle_missing(self, strategy='drop', fill_value=None):        if strategy == 'drop':            before = len(self.df)            self.df = self.df.dropna()            dropped = before - len(self.df)            self.missing_count += dropped            self.log_list.append(f'删除缺失行 {dropped} 条')        elif strategy == 'fill' and fill_value is not None:            self.df = self.df.fillna(fill_value)            self.log_list.append(f'填充缺失值: {fill_value}')        elif strategy == 'median':            num_cols = self.df.select_dtypes(include=[np.number]).columns            self.df[num_cols] = self.df[num_cols].fillna(self.df[num_cols].median())            self.log_list.append(f'中位数填充数值列 {list(num_cols)}')     def remove_duplicates(self, subset=None):        before = len(self.df)        self.df = self.df.drop_duplicates(subset=subset)        self.duplicate_count = before - len(self.df)        self.log_list.append(f'删除重复行 {self.duplicate_count} 条')     def remove_outliers(self, columns, factor=1.5):        before = len(self.df)        for col in columns:            q1 = self.df[col].quantile(0.25)            q3 = self.df[col].quantile(0.75)            iqr = q3 - q1            low = q1 - factor * iqr            high = q3 + factor * iqr            self.df = self.df[(self.df[col] >= low) & (self.df[col] <= high)]        self.outlier_count = before - len(self.df)        self.log_list.append(f'剔除异常值 {self.outlier_count} 条')     def clean(self, dropna=True, dedup=True, outlier_cols=None):        if dropna:            self.handle_missing('drop')        if dedup:            self.remove_duplicates()        if outlier_cols:            self.remove_outliers(outlier_cols)        return self.df     def show_log(self):        for item in self.log_list:            print('[清洗日志]', item)        print('共执行', len(self.log_list), '步清洗操作')  if __name__ == '__main__':    raw = pd.DataFrame({        'name': ['Alice', 'Bob', 'Alice', 'Carol', None, 'Dave'],        'age': [25, 30, 25, 200, 28, None],        'city': ['北京', '上海', '北京', '广州', '深圳', '深圳']    })    cleaner = DataCleaner(raw)    clean_df = cleaner.clean(outlier_cols=['age'])    print(clean_df)    cleaner.show_log()
目的:实现一个轻量数据清洗工具类,跑通「缺失值删除 → 重复行去重 → IQR 异常值剔除」三步清洗,并输出可追溯的清洗日志。
  1. self.log_list = []用列表保存每一步清洗操作的说明,最后统一打印,对应正文「清洗日志」的思想。
  2. self.df[cols].isnull().sum()isnull() 把空值标记为 True,sum() 统计每列缺失数,是「数据剖析」的核心动作。
  3. strategy == 'drop'当策略为 drop 时执行删除,对应正文里「低缺失率且随机缺失可删除」的规则。
  4. self.df.dropna()调用 pandas 内置方法删除含缺失值的整行,返回清洗后的新表。
  5. self.df.select_dtypes(include=[np.number]).columns选出数值型列,只对数值列做中位数填充,避免把文本列填坏。
  6. drop_duplicates(subset=subset)按指定列(或全部列)去重,处理完全重复记录。
  7. self.df[col].quantile(0.25)` 和 `quantile(0.75)分别取该列的四分位数 Q1 和 Q3,用来计算 IQR 和异常值边界。
  8. (self.df[col] >= low) & (self.df[col] <= high)布尔索引同时过滤上下界,注意与运算必须给每个条件加括号。

常见误区

误区数据清洗不就是把脏数据删掉吗?删干净数据就干净了,删不干净就多删点,反正数据库里的行那么多,删几条无所谓。

正解清洗的操作工具箱里,「删除」只是最不得已的一招。缺失值可以填充、可以插值;异常值可以修正、可以标记;重复数据可以合并、可以保留信息最完整的副本;格式错误可以转换和标准化。盲目删除会丢失有价值的信息,尤其当脏数据本身携带着真实业务信号时,比如欺诈样本。正确的做法是像大厨一样先判断哪些能修、哪些才该扔。

为何常见:因为「删除」是最简单直接、肉眼可见效果的操作。初学者看到脏数据的第一反应就是「眼不见为净」,却忽视了删除造成的隐性信息损失。

误区缺失值很简单,不管什么列,用这一列的平均值填进去就行了,又快又省事,模型也不会因为空值报错。

正解均值填充只对数值型、且分布接近对称的列才比较合适。对于分类列,应该用众数填充;对于时间序列,线性插值或前向填充能更好地保留趋势;对于缺失机制是非随机缺失的列,直接用均值填充会引入系统性偏差。填充前先算缺失率、看分布、判断缺失机制,才能选对方法。

为何常见:因为「均值填充」在教材里出现频率最高,代码也简单。但它是一个默认 option,不是万能答案,忽略了不同字段缺失背后的原因。

误区只要数据点超出正常范围,就是记录错误,直接删掉或改成边界值就好,留着只会干扰分析。

正解异常值分「脏异常」和「真异常」。录入错误、传感器故障产生的脏异常当然要处理;但欺诈交易、突发热点事件、极端天气这类真异常往往是最值得分析的信息。Z-score 或 IQR 只负责「找出来」,是否处理以及怎么处理必须结合业务判断。风控模型如果一开始就把欺诈样本当异常删掉,模型就永远学不会识别欺诈。

为何常见:因为统计方法给了人一个「客观标准」的错觉,人们倾向于把统计上离群和实际上错误混为一谈,忽略了业务上下文。

误区我把这批数据清洗干净了,以后就不需要再管了,报表和模型直接用这份干净数据就行。

正解数据是持续流动的,今天清洗干净的数据,明天会有新的缺失、新的重复、新的格式问题进来。正确做法是把清洗流程固化成定时任务,每天或每周自动运行,搭配质量监控看板,一旦质量评分跌破阈值就触发告警。清洗是一套持续运营的流程,不是一锤子买卖。

为何常见:因为新手接触的往往是静态数据集,完成一次清洗后看不到数据还在增长变化,自然而然地以为「一次搞定」。

误区重复数据有什么好分析的,直接 drop_duplicates 全删,保留第一行就完事了。

正解去重前必须先定义「什么算重复」:完全重复可以直接删,但相似重复需要设定相似度阈值和比对字段。业务上「同一个人换手机号又注册了一次」到底是两条会员还是一人一档,必须由业务规则决定。而且保留哪一条也要讲究——保留信息最完整的,保留时间最新的,同时把不同记录里的独有字段合并起来,而不是简单删掉。

为何常见:因为工具提供了「一键去重」的便利,让人误以为去重是机械操作。实际上「重复」的定义高度依赖业务语义,根本不是纯技术问题。

面试题

Q1. 以下哪项属于典型的脏数据?

单选题 · easy

  • A.类型统一的日期格式列
  • B.带大量缺失值的用户表← 正确答案
  • C.全部字段填充完整的记录
  • D.格式规范的文本列

答案:B。典型的脏数据表现为缺失、重复、异常、格式混乱四类。缺失值是四类中非常典型的一种,带大量缺失值的用户表显然属于脏数据。日期格式统一、字段完整、文本规范都是干净数据的特征,不是脏数据。

Q2. 数据集中某列缺失率高达 40%,最优先的处理动作是什么?

单选题 · easy

  • A.直接删除该列全部数据
  • B.分析缺失原因再定策略← 正确答案
  • C.用随机数盲目填充该列
  • D.忽略缺失直接开始建模

答案:B。缺失率高时首先要做的是剖析缺失机制——是完全随机缺失还是与其他字段相关,不同机制对应删除、填充、插值等不同策略。直接删除或盲目填充都会引入严重偏差,忽略不做则会让模型无法训练或产生错误结果。

Q3. 用 IQR 法识别异常值时,若 Q1=10、Q3=20,则该列数值的上边界是多少?

单选题 · mid

  • A.25.0 左右
  • B.35.0 左右← 正确答案
  • C.40.0 左右
  • D.45.0 左右

答案:B。IQR 法的上边界公式为 Q3+1.5×IQR。先算 IQR=Q3-Q1=20-10=10,再算上边界=20+1.5×10=35。大于 35 的值会被判定为异常候选,这是箱线图法最核心的计算步骤。

Q4. Z-score 法中,一般认定 |Z| 大于多少时该点属于异常值?

单选题 · mid

  • A.1.5 以上即算异常
  • B.2.0 以上即算异常
  • C.3.0 以上即算异常← 正确答案
  • D.4.0 以上即算异常

答案:C。统计学经验认为数据大约有 99.7% 落在均值±3 个标准差内,因此 |Z|>3 是常用的异常判定阈值。1.5 和 2.0 过于灵敏,会把正常波动误判为异常,4.0 又太宽松,会漏掉异常。

Q5. 三种缺失机制中,哪种情况下直接删除缺失行最危险?

单选题 · hard

  • A.MCAR 完全随机缺失
  • B.MAR 缺失依赖其他列
  • C.MNAR 缺失与未观测值相关← 正确答案
  • D.三者危险程度完全相同

答案:C。MCAR 缺失完全随机,删除后剩余数据仍有代表性;MAR 缺失与其他已观测字段相关,删除会损失部分信息;MNAR 的缺失本身与缺失值的高低相关(如高收入人群故意不填收入),直接删除会导致严重的样本偏差。因此 MNAR 最危险。

Q6. 完全重复与相似重复记录的主要区别是什么?

单选题 · mid

  • A.字段值是否完全相同← 正确答案
  • B.记录数量是否相同
  • C.删除策略是否相同
  • D.检测算法是否相同

答案:A。完全重复指所有字段值完全相同,相似重复则大部分字段相同但存在细微差异(如地址写法不同)。二者的检测方法自然不同,完全重复用精确匹配,相似重复要用编辑距离、SimHash 等相似度算法。

Q7. 清洗日志的最主要作用是什么?

单选题 · easy

  • A.保存清洗前的数据快照
  • B.记录清洗全程便于追溯← 正确答案
  • C.删除原始数据备份
  • D.自动修复全部脏数据

答案:B。清洗日志记录「何时、谁、对哪个字段、执行了什么规则、影响了多少行」,核心目的就是可追溯。出了问题能回溯,审计问询时能拿出证据。快照是日志的一部分而非全部,日志本身不会自动修复数据,也不会删除原始备份。

Q8. 以下哪些属于数据清洗的常见方法?

多选题 · mid

  • A.缺失值的删除与填充← 正确答案
  • B.重复记录的检测与去重← 正确答案
  • C.异常值的统计检测与修正← 正确答案
  • D.模型超参数的网格搜索调优

答案:ABC。数据清洗解决的是数据本身的质量问题,包括缺失值处理、重复去重、异常值检测与修正,对应本文的三大核心方法。超参数调优属于模型训练环节,与数据清洗不在同一个阶段。

Q9. 数据质量评估常用的维度包括哪些?

多选题 · hard

  • A.完整性(缺失情况)← 正确答案
  • B.准确性(错误比例)← 正确答案
  • C.一致性(格式统一)← 正确答案
  • D.硬件性能(可扩展性)

答案:ABC。经典的 DQ 四维评估包括完整性、准确性、一致性、时效性。完整性看缺失,准确性看取值是否正确,一致性看字段表达是否统一。硬件性能属于系统架构指标,不属于数据质量维度。

Q10. 设计清洗流程时,正确的做法包括哪些?

多选题 · mid

  • A.先做数据剖析再定规则← 正确答案
  • B.清洗后验证效果对比← 正确答案
  • C.全程记录清洗日志← 正确答案
  • D.一步到位直接删光

答案:ABC。规范清洗流程是「剖析→规则→执行→验证→迭代」。先剖析了解数据全貌,清洗后做质量评分前后对比,全程保留日志,都是正确做法。直接删光是典型的错误操作,会丢失有价值的信息。