资讯动态

十年数据清洗实战:从pandas到DataX的预处理进化之路

发布时间:2026/9/14 17:48:27 来源:尧图企业网站定制
2014 年我接手第一个正经的数据分析项目时组里分给我的第一个任务不是建模而是洗数据。那份数据来自好几个业务系统导出的 Excel光字段名就有七八种写法日期列有的带时间、有的不带金额列里混着文本备注缺失值用“-”、“null”、空字符串三种方式混着表示。我用 Python 手工写了快一个星期的脚本才把它收拾干净然后当时的组长对我说“这活儿没什么技术含量后面建模才是重点。”这句话我记了很长时间。因为这十年里我陆续做过电商、金融、工业制造几个领域的数据项目几乎每个项目真正的拦路虎都不是算法不够新而是数据不够干净。数据清洗这件事从 Excel 手工时代走到 pandas、DataX、工业传感器数据清洗大规模铺开的今天工具换了好几轮但它的地位反而越来越重要。这篇文章不打算讲高深理论就是把我自己在这十年里踩过的坑、沉淀下来的流程和对“干净数据”的理解摊开来讲希望能给正在和数据质量较劲的人一些参考。1. 十年之前没有 DataFrame 的日子我们怎么洗数据1.1 Excel、Shell 和“人肉调参”式清洗2014 年前后很多公司里的数据清洗和预处理工作还远没有形成体系。业务部门拿到一份报表先复制到 Excel用筛选、排序、CtrlH 替换把明显的错误改掉再用 VLOOKUP 把不同表的数据拼起来。遇到日期格式不统一就分列、自定义格式、手动拖拽一套操作下来眼睛都快看花了。我当时在代码里干的事情现在看起来也相当原始。用 Python 的 csv 模块一行一行读文件if 判断去跳过空行用正则表达式去匹配不合法的手机号或金额再手动拼一个新的列表写回去。没有 DataFrame每一列都是一个独立的 list索引对齐全靠自己小心维护。一个几十万行的 CSV 处理完内存开销大不说代码逻辑也绕得人头晕。真正的转折点是 pandas 开始流行起来。那个年代的教材里数据清洗和预处理还只是数据分析书里的一个小章节讲的内容无非是读 CSV、处理缺失值、画个直方图。但哪怕只是这些基础操作也已经让大批做数据的人从行级手工操作里解放出来了。我第一次用df.dropna()一次性清掉所有空行的时候最大的感受不是“好先进”而是“我以前那些手工循环写得真蠢”。1.2 为什么那时候数据清洗被当成“打杂”现在回头看早期数据清洗不受重视有一定的客观原因。当时很多公司的数据体量还没大到需要专门的数据团队数据问题更多出现在业务系统之间的接口上大家默认“数据是从系统里导出来的应该没问题”。于是洗数据被归为一种临时的、事务性的工作谁有空谁干干完也不留文档。但它带来的后果是隐蔽的。数据质量长期积压最后会以极其难看的方式爆发。比如业务部门拉出来的报表同一个指标在两个地方两种口径比如模型训练集里混了大量重复样本准确率虚高再比如做月度复盘时发现某个月的销售额少了十几万追查半天发现是导出的时间戳时区没对齐。这些事的根子都在最初的清洗环节没做到位。我后来养成了一个习惯任何项目开始之前先花足够的时间做数据探查也就是用 describe、value_counts、isnull 这些手段把数据“摸”一遍。表面上看这一步拖慢了进度实际上恰恰相反。数据探查做扎实了后面建模、报表环节返工的概率会小很多。这个习惯一直保留到今天也是我写这篇文章想强调的第一个经验。2. pandas 出现后数据清洗第一次有了像样的“工作台”2.1 几个常用操作马上用起来pandas 之所以能成为 Python 数据清洗和处理的事实标准核心在于它把“表格”这个抽象模型做到了极致。几乎任何对数据集的增删改查都能用一两行代码表达清楚。我到现在给新人培训依然只先讲四个操作读数据、看数据、补数据、查数据。import pandas as pd df pd.read_csv(raw_data.csv, encodingutf-8-sig) print(df.info()) print(df.describe()) # 缺失值处理 df[age] df[age].fillna(df[age].median()) df df.dropna(subset[user_id]) # 类型纠正 df[price] df[price].astype(str).str.replace(,, ).astype(float) df[signup_date] pd.to_datetime(df[signup_date]) # 去重 df df.drop_duplicates(subset[order_id], keeplast) # 筛选异常范围 df df[(df[amount] 0) (df[amount] 100000)]这段代码非常朴素但已经覆盖了清洗阶段 80% 的常见操作。需要说明的是fillna 的取值不是随手填的median和mean的选择要看字段分布。如果字段右偏用均值填充容易被极端值带跑用中位数更稳妥。如果缺失值本身带有业务含义比如“未填写”是一个有效状态那就不应该用统计值填充而是要保留一个单独的类别。2.2 从一次性脚本到可复用清洗规则早期我用 pandas经常是写一个很长的脚本从上到下跑一遍输出一个干净的表就收工。后来项目多了发现这种做法有三个问题第一清洗逻辑和业务逻辑全混在一个脚本里后面根本没法维护第二同样的规则换个项目就得重新写一遍第三上线之后一旦源数据变了脚本很容易报错但错误往往要隔很久才发现。所以后来我把清洗拆成函数每个函数只做一件事比如clean_phone、normalize_date、remove_outliers_by_iqr。再进一步把这些函数串成一个 pipeline输入原始 DataFrame输出清洗后的结果。这样的好处是每一步规则都可以单独测试、单独调整也方便在项目文档里记录“这个字段为什么要这么洗”。对于中小型项目我用 pandas 自带的pipe方法就能拼出可读性很高的链路。更复杂的生产环境则会用 sklearn 的Pipeline和ColumnTransformer把清洗和建模放进同一个工作流这样在线推断时也能复用同一套预处理逻辑避免训练和推理时的特征不一致。2.3 我踩过的 pandas 清洗坑默认参数不可全信pandas 好用但它的很多默认参数是“方便型默认”不是“安全型默认”。第一个坑是dropna它的默认行为是只要某行有任何一列是空值就把整行删掉。很多新手在没看数据分布的情况下直接df.dropna()结果几千行数据变成几百行后面模型训练直接崩。正确的做法是先df.isnull().sum()看每一列的缺失情况再决定是删行、删列还是填充。第二个坑是astype转类型时遇到脏数据。比如把包含1,234这种千分位字符串的列转成 float直接转会报错需要先用str.replace(,, )清理。还有日期字段pd.to_datetime遇到无法解析的值默认会抛异常但在大数据量下我更倾向于传errorscoerce把非法值变成 NaT再统一处理。第三个坑是drop_duplicates时忽略了keep参数。默认保留第一条但如果你的数据里后出现的记录才是最新状态那应该用keeplast。这种细节看似不起眼实际在订单、库存这类有状态变化的表里选错一条就会导致统计结果对不上账。3. 当数据大到单机装不下DataX 与平台化清洗3.1 单机 pandas 的边界在哪里断掉pandas 很强大但它也有一个明显软肋单机内存。几千万行的 DataFrame在普通服务器上处理起来已经明显吃力。到了 TB 级数据别说read_csv光是df.describe()都可能触发 OOM。这时候整个数据清洗的思路就得换一换从“把数据读进内存再洗”变成“在数据流转过程里洗”。我在实际项目中遇到过两个典型场景。第一个是每天凌晨从业务库同步一批增量数据到数据仓库数据量在千万级如果全部拉回本地清洗时间和内存都扛不住。第二个是离线数仓里有多张源表需要做 join 和过滤单机 pandas 没办法直接把整个数据集读进来只能靠 SQL 在集群上完成大部分清洗工作。这个阶段我开始接触各种 ETL 工具和同步组件。它们的思路是一致的清洗规则不再写在 Python 脚本里而是定义在数据流转管道中用配置的方式管理。这样既能处理大数据量也让清洗流程更容易被团队协作和复核。3.2 DataX 的角色异构数据源之间的“搬用工”兼“质检员”DataX 是一个离线数据同步工具我最早接触它是为了解决 MySQL 到 Hive 的数据同步问题。它最核心的能力是把不同数据源之间的数据搬运过程抽象成“读端-通道-写端”三部分读端插件连接源库写端插件连接目标库中间经过数据转换和写入控制。在数据清洗的语境里DataX 起到的作用更像一个预处理关卡。它支持在同步过程中配置转换规则比如字段裁剪、类型转换、默认值填充甚至可以自定义 script在同步过程中做更复杂的清洗逻辑。用它做完整的数据清洗并不现实但用来做“同步即可用”的初步清洗非常高效。我印象比较深的一次实践是把某个旧系统导出的 JSON 日志同步到数仓。源数据里时间字段是字符串时区也不统一金额字段偶尔会出现科学计数法。以前的做法是先导到临时表再写 Spark 任务清洗周期长、调试麻烦。后来我在 DataX 的转换配置里统一做了格式化同步完成后目标表的数据已经可以直接用于下游计算整个流程缩短了差不多一个多小时。{ job: { content: [ { reader: { name: mysqlreader, parameter: { column: [id, trade_time, amount, status], connection: [ { jdbcUrl: [jdbc:mysql://xxx:3306/order_db], table: [order_info] } ] } }, writer: { name: hdfswriter, parameter: { defaultFS: hdfs://xxx:8020, fileType: text, path: /warehouse/ods/order_info, writeMode: append, column: [ {name: id, type: bigint}, {name: trade_time, type: datetime}, {name: amount, type: double}, {name: status, type: string} ] } }, transformer: [ { name: dx_substr, parameter: { columnIndex: 1, parBegin: 0, parEnd: 19 } } ] } ], setting: { speed: {channel: 4} } } }这段配置不算复杂但已经把“同步 清洗”两个动作合到了一起。重点在transformer部分它会在数据从源库读出、还没写入 HDFS 之前对字段做处理。比如上面把 trade_time 截成 19 位去掉毫秒和时区后缀就是一个典型的字段规范化动作。通道数channel决定并发度要根据源库和目标 HDFS 的吞吐能力来调不是越大越好。3.3 数据仓库分层后清洗开始变成标准动作数据量一大团队必然开始做数仓分层。常见的分层是 ODS、DWD、DWS每一层对数据质量的关注点不一样。ODS 层基本是原样接入保留最原始的状态不做过多的判断。DWD 层才是清洗和预处理的主战场要完成字段标准化、去重、维表补全、非法值过滤。我以前遇到过一个很典型的错误做法在 ODS 层就做了大量清洗结果下游业务口径变了想回看原始数据发现已经被改得面目全非。所以后来团队约定清洗过程尽量放在 DWD 层并且每一条清洗规则都要有对应记录最好能在数据字典里写清楚“什么条件下做了什么样的处理”。这也就是大家常说的数据治理其实落地下来没那么玄乎核心就是把规则显性化、把处理过程可追溯。从这以后我再也不会把“数据清洗”理解成某个人写个脚本跑一遍的临时工作。它是一套从源端到应用端的持续治理流程DataX 这类同步工具负责在入口处把关pandas/Spark 负责在计算过程中做精细处理而元数据和数据字典负责让整个过程有据可查。4. 工业场景的数据清洗和预处理传感器数据是块硬骨头4.1 传感器数据为什么这么脏和业务系统里的结构化数据相比工业传感器数据的数据清洗和处理完全是另一种画风。传感器按固定频率采集温度、压力、振动、流量等物理量但采集环境远没有数据库那么规整。设备断电、网络抖动、传感器老化都会让数据出现异常。传感器数据常见的问题可以归为这么几类。一是缺失采集终端断传、数据库写入失败都会造成时间点上的空洞。二是噪声也就是真实物理量之外的高频波动可能来自电磁干扰、机械振动也可能来自采集电路的本身误差。三是漂移传感器长时间运行后零点发生偏移导致整体数值缓慢偏离真实值。四是尖峰和毛刺比如瞬时高值或低值和真实过程不符。如果把这些数据直接拿去训练模型或做报警判断结果会相当不靠谱。我见过一个设备预测性维护项目由于没有做异常值剔除训练出的模型把传感器尖峰当成了故障信号报警准确率只有不到一半。后来花了一个多星期洗数据模型效果才恢复到可用的水平。4.2 缺值、漂移、尖峰三类常见问题的处理思路处理缺失值工业传感器数据和业务数据不太一样。业务数据可以用均值、中位数填充但传感器数据往往有很强的时间连续性用整体均值填充会破坏局部趋势。更常用的方法是前向填充、后向填充或者用相邻时间点做线性插值。如果缺失跨度太长比如连续几小时都没有数据填充的意义就很有限这时候直接标记该区间为“无效”反而更稳妥。处理尖峰和异常值我比较常用两类方法。一类是基于统计的比如滑动窗口内的 3σ 法则在时间窗口内算均值和标准差偏离超过 3 倍标准差就被标记为异常。另一类是基于四分位距的 IQR 方法用分位数而不是均值来判断异常对非正态分布更稳健。import pandas as pd import numpy as np df[ts] pd.to_datetime(df[ts]) df df.sort_values(ts) df df.set_index(ts) # 删除完全重复的采集点 df df[~df.index.duplicated(keepfirst)] # 线性插值填补短时缺失 df[value] df[value].interpolate(methodtime, limit10, limit_directionboth) # 滚动窗口 3σ 异常剔除 window 30 mean df[value].rolling(window).mean() std df[value].rolling(window).std() df[is_anomaly] (df[value] - mean).abs() 3 * std df.loc[df[is_anomaly], value] np.nan df[value] df[value].interpolate(methodtime)这套代码的思路是先按时间排序并去重再对短时缺失做插值然后用滚动窗口识别尖峰把异常点先置空再重新插值。limit10的意思是只填充最多连续 10 个缺失点超过这个数量就保持缺失避免长段空白被无依据填满。这个参数怎么定取决于你设备的采集频率比如 1 分钟一条数据10 分钟内的断点可以用插值再长时间就需要结合设备工况判断了。处理漂移又是一个层面的事。最简单的漂移矫正手段是高通滤波或去趋势但这需要了解传感器的物理特性只用通用统计方法硬来会出问题。我的建议是漂移问题最好在信号处理层面解决比如用差分、带通滤波把趋势项和周期项分离而不是在数据清洗阶段一律用均值或插值。4.3 一套可落地的工业传感器清洗流程含参数说明我整理过一套比较通用的工业传感器数据清洗和预处理流程分成七个环节时间对齐把不同终端、不同采样频率的数据统一到同一时间轴上。采集频率不一致的用重采样方法统一到最粗的频率比如 1Hz 的 10Hz 的数据合并到 5s 一个点。去重因为网络重传、写入重试造成的完全重复记录以时间戳为准去重。缺失标记先区分“正常无值”和“异常断传”。设备停机期间无数据不是缺失不能填充要打上工况标签。插值填充对短时缺失做线性或样条插值长时缺失保持空值交由下游处理。异常值检测使用滚动 IQR 或 3σ 初筛再用业务的上下限校验。比如温度传感器的合理范围由设备工艺决定超出范围的值直接标记。噪声平滑用 Savitzky-Golay 滤波或指数加权移动平均消掉高频噪声保留趋势。结果校验把清洗后的数据和原始数据画在一起肉眼确认关键区间的处理是否合理再统计清洗前后的均值、方差变化。这套流程看起来不复杂但执行起来容易忽略的是第 3 步。很多人在处理传感器数据时一看到 NaN 就想着填却忘了先去理解“为什么缺”。如果设备本来就是停机的那这段时间的数据就不应该出现在训练集里如果是采集断传插值才有意义。这个判断题错了后面所有步骤都会跟着错。5. 洗到什么程度才算“干净”一个经常被低估的问题5.1 清洗边界由业务目标决定不是由工具决定干了十年数据清洗我最想纠正一个观念不存在一个绝对的“干净”标准。同样一份数据给运营做周报和给算法做训练清洗策略可能完全不同。比如缺失率 40% 的字段对运营来说也许仍可以按“未知”分类展示但对机器学习模型来说作为一个特征塞进去可能引入大量偏差删除反而更安全。判断清洗边界的依据永远是业务目标。你是要做趋势分析还是要做实时报警要预测设备的剩余寿命还是要输出一份财务对账明细不同的目标决定了你要保留多少细节、容忍多少误差。所以在清洗开始前我会先和业务方对齐几个问题这份数据最终谁来用哪个字段最不能出错可以接受多少比例的缺失这比急着写代码重要得多。5.2 清洗和建模的“安全距离”清洗和建模之间也有一个必须保持的距离那就是不能为了让模型表现好看而过度清洗。我见过有人把测试集里预测错误的样本“当异常值”过滤掉然后模型精度变高了上线却一塌糊涂。这就是典型的清洗泄露你提前把模型要预测的困难情况删掉了。正确的做法是清洗规则只能基于通用知识或训练集统计信息不能用测试集数据来定填充值或异常值阈值。更严谨的做法是把清洗参数放到交叉验证的 pipeline 里让每一次训练只看到训练集内部计算出的统计量。在处理时间序列数据时这个问题尤其敏感因为时间序列天然有先后关系不能用未来的数据去填充过去的缺失否则会造成前瞻偏差。我自己的经验是写清洗代码时要有意识地记录“用了哪些列的统计量”凡是用到均值、分位数、最大最小值的地方都要标注出来避免在实际建模时不小心用全量数据算。如果项目到了细粒度较高的阶段直接上 sklearn Pipeline把 imputer、scaler 都放进交叉验证流程是最省心的方案。5.3 清洗代码和规则本身就值得版本管理我早期做过一个很傻的事清洗完数据把生成的新 CSV 保存下来就没有然后了。过了两个月业务想调整口径问我“这个字段当时是怎么填的”我打开电脑发现脚本文件名是“clean_v3_final_真的最终版.py”里面也没有注释根本说不清楚填的是中位数还是均值。这种经历应该不少人都遇到过。清洗代码是数据资产的一部分它记录了原始数据是怎么一步步变成你手里这份分析结果的。如果中间任何一步出错后果会传递到所有下游报表和模型里。所以我现在做任何清洗任务都会用 Git 记录版本并且单独维护一份 README写清楚数据来源、清洗规则、参数选择和产出物。规则变更时不是直接覆盖旧脚本而是提交一个新版本。这件事听起来麻烦但实际用起来会发现它非常值得。查问题的时候你能快速定位是哪一版规则导致结果变化审计的时候你能给出让人信服的处理依据团队协作时其他人也能通过文档理解你的处理逻辑。数据治理从一个口号变成一个可落地的习惯靠的不是某个平台而是每个人的版本意识和文档习惯。6. 沉淀下来的工作流我的数据清洗标准动作与避坑清单6.1 六步通用流程适配大多数项目如果把十年里的经验浓缩成一套可复用的流程我的标准动作大概是六步。第一步业务理解。不急着看代码和数据先搞清楚这套数据从哪个系统来、谁在用、最关心什么。第二步数据探查。用df.info()、df.describe()、df.isnull().sum()和简单的分布图把数据全貌摸清楚看清楚每列的类型是否合理、缺失是否严重、分布有没有明显异常。第三步明确清洗规则。把“日期怎么统一、金额怎么去逗号、缺失怎么填、异常怎么剔”逐条写下来最好让业务方确认一遍。第四步写清洗脚本。按规则实现并保留原始数据备份。第五步清洗后校验。用统计指标对比清洗前后再抽查几条记录人工复核。第六步记录归档。更新数据字典和版本说明。这套流程不一定是最新潮的但非常稳。我帮几个团队做过数据质量复盘发现很多问题并不是没有清洗而是跳过了第一步和第三步。业务口径都没对齐技术再强也只是在错误的方向上加速。6.2 新手最容易踩的五个坑第一个坑拿到数据就 dropna。我前面已经说过dropna 会悄无声息删掉大量行正确的顺序是先看缺失情况再决定是填充、删除还是保留。第二个坑清洗前不做原始备份。原始数据是唯一的真相来源清洗错了还可能回头重来如果直接被覆盖修改想后悔都找不到地方。我自己的习惯是永远保留一份 raw 目录只读文件任何清洗结果都写到另一个目录。第三个坑不看分布直接填均值。均值对离群点非常敏感一个超大数据就能把均值拉高。填充前最好画个箱线图或直方图偏态分布用中位数有明确业务含义的用规则填充。第四个坑处理时间序列时忘了排序。很多清洗函数依赖顺序比如 fillna 的methodffill和滚动窗口如果数据没有按时间排序处理结果全是错的。哪怕原始文件看起来是有序的也建议显式sort_values(ts)。第五个坑清洗完不做验证。跑完脚本就直接建模等到模型效果不好才回头怀疑数据。实际上只要多花几分钟对比一下清洗前后的 describe或者抽几行数据看看就能发现大部分问题。除了这五个坑还有一个容易被忽略的点清洗后一定要检查行数的变化是否符合预期。比如按 user_id 去重后删掉了多少行按异常值过滤后剩多少行这些数字本身就能帮你发现清洗规则是否写错。6.3 一点个人体会说了这么多其实回到最开始那个“洗数据没技术含量”的评价我现在的想法已经完全不同了。数据清洗不是打杂而是整个数据链路里最需要业务理解、耐心和工程素养的环节之一。它不会像调参或模型创新那样带来惊艳效果但它决定了所有后续工作的上限。数据是脏的模型再高级也救不回来。这十年工具从 Excel 换成了 pandas又加上了 DataX、Spark、各类数据质量平台工业传感器数据清洗甚至开始引入专门的时间序列算法。但核心思路没有变先理解业务再探查数据定好规则留下记录反复验证。把这几件事做到位不管工具怎么变你都不会被数据质量拖后腿。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价