数据预处理这件事,我在数据科学项目里翻来覆去折腾了很多年。刚入行时总觉得建模才是核心,后来被现实教育过几次才发现,真正决定项目成败的往往不是模型,而是你在建模之前那十几个小时面对脏数据所下的功夫。数据预处理听着基础,但恰恰是它,决定了数据科学工作流走到后面是顺风顺水还是寸步难行。这篇文章不绕弯子,直接把数据预处理的全链路、实操细节和我踩过的坑一次性讲清楚。
1. 数据预处理:数据科学项目的真正胜负手
1.1 为什么预处理比建模更决定项目成败
业内有一句话流传很广:“Garbage in, garbage out”。翻译过来就是,喂给模型的是垃圾,模型给出的也只能是垃圾。我见过太多人花大把时间调参、换算法、堆特征,模型效果始终上不去,最后回头一查,问题出在源数据上——日期格式错乱、类别字段里有三种写法表示同一个城市、缺失值被粗暴地填成-9999。模型再强大,也扛不住这种输入。
数据预处理的核心价值,不是帮你“处理完这些事再去建模”这么简单,而是直接决定了你后面所有工作的可行性。数据科学项目往往不是算法竞赛那种数据集已经清洗干净的场景,现实里的数据来自业务系统、爬虫、传感器、人工录入,各种格式、各种脏乱差的环境下,预处理的质量直接决定了模型上限。说得直白点,预处理做得好,模型自然有东西可学;预处理做得粗糙,后面的一切都是空中楼阁。
从工程角度来看,数据预处理还有一个被忽视的作用:它是整个数据科学工作流里唯一能“把原始信息转化为可用信息”的环节。特征工程、模型训练、评估都在消费这一步的产出物。一旦这一步埋了雷,后面排查成本极高,而且越晚发现问题,纠错成本越高,这可能就是预处理被称作“关键步骤”的根本原因。
1.2 预处理在数据科学工作流中的位置
一个标准的数据科学项目工作流大概是这样的:业务问题定义、数据获取、数据预处理、特征工程、模型训练、模型评估、部署上线。数据预处理在这里的位置非常特殊,它紧接在数据获取之后,是后面所有环节的前提。很多初学者以为特征工程和数据处理是一回事,其实二者有重叠,但侧重不同:预处理更偏向“让数据可用”,特征工程更偏向“让数据变得有预测力”。
我的习惯是把总项目时间的60%到80%花在数据探索和预处理上,剩下20%到40%留给建模调参。这个比例听起来夸张,实际做过项目的人都懂。一个真实案例是:我接手过一份来自多个渠道的销售数据,光是把不同系统里对“订单状态”这个字段的多种取值统一成标准化枚举,就花了半天。后面建模十分钟就训完了,而模型效果主要依赖的就是这半天清洗后的干净数据。
预处理在数据科学中的角色,可以拿做饭来类比——数据获取是买菜,预处理是洗菜切菜备菜,建模是大火快炒。很多人觉得炒菜水平决定了菜好不好吃,但餐厅后厨的老手都知道,食材没处理干净、切得不规整,再好的厨师也做不出一桌好菜。数据预处理就是这样一种“看不见的功夫”,它不直接产出炫目的结果,但没有它,结果根本不会来。
2. 数据预处理的完整方法框架
2.1 四个核心环节拆解:清洗、集成、转换、归约
数据预处理从方法论上可以拆成四大块,这四块基本覆盖了我在实际项目中遇到过的大部分情况。
数据清洗是最基础也最耗时的环节,主要处理缺失值、重复值、异常值和格式不一致。缺失值不能无脑删,也不能无脑填,得先搞清楚缺失机制:是随机缺失,还是因为某些业务原因导致的系统性缺失?比如用户画像数据里年龄字段大量缺失,很可能用户群体偏年轻,不愿意填年龄,这种情况下直接删行会引入偏差。重复值则相对简单,但也得小心“看起来重复但实际有业务含义”的情况,比如一个人可以在平台上有多条合法的历史变更记录。
数据集成解决的是多数据源合并的问题。不同系统的表结构、命名规范、主键粒度都可能不一致。最典型的是用户ID,A系统里是自增整数,B系统里是UUID,C系统里是手机号。集成时先要统一实体标识,否则后续join出来的结果就是一个灾难。数据集成里常见的坑还包括字段语义冲突——两个系统都叫“金额”,但一个是含税价,一个是不含税价,直接合并会闹出大问题。
数据转换是让原始数据符合算法要求的环节,包括无量纲化、离散化、编码、函数变换等。很多模型对特征的尺度敏感,比如KNN和SVM,距离计算直接受特征量纲影响;而树模型则完全不在乎这个,所以你做的转换必须和后续模型匹配,不能不管三七二十一上来就标准化。
数据归约的意思是在不显著损失信息的前提下缩小数据规模。典型做法有降维(PCA、SVD等)、特征选择、采样、分箱。归约不是单纯的“删数据”,是通过保留主要结构来降低后续计算开销。这个环节在大规模数据处理中尤其重要,比如当你有几千万行数据时,模型训练时间会爆炸,合理采样或降维能显著提速,同时保持预测精度。
| 环节 | 主要任务 | 常见误区 | 我的建议 |
|---|---|---|---|
| 清洗 | 缺失/重复/异常/格式 | 无脑删除或填充 | 先探查缺失机制和异常成因 |
| 集成 | 多源合并、实体对齐 | 忽略字段语义差异 | 统一标识和度量标准后再join |
| 转换 | 归一化/编码/离散化 | 盲目标准化 | 根据模型类型选择相应变换 |
| 归约 | 降维/选择/采样 | 过早删除信息 | 先建模基检出重要性再删 |
2.2 不同数据类型对应的处理策略
数据科学里接触的数据类型虽然五花八门,但抽象起来就那么几类:数值型、类别型、时间型、文本型,以及我在后文会单独展开的栅格/时空数据。每类数据都有自己的一套处理思路,搞混了就会出问题。
数值型数据是最常见的。处理的时候我会先看分布,看有没有极端值,是否近似正态。对于右偏严重的特征,做一次log变换或Box-Cox变换能让后续模型更稳定。对于量纲差异巨大的特征,如果你用的是线性模型、距离类模型,必须做标准化或归一化;如果用的是树模型,则不需要,因为它只关心分裂点的顺序。
类别型数据的核心是编码。传统做法是哑变量编码(one-hot encoding),但类别极多时会产生稀疏矩阵,给内存和训练都带来压力。这时候可以考虑目标编码、标签编码、嵌入编码等。目标编码有个陷阱,就是极易造成过拟合,所以必须配合交叉验证或平滑处理来使用。我自己用过一次目标编码之后发现单特征AUC暴涨,但验证集AUC反而降了,后来用了五折交叉内的目标编码才解决。
时间型数据要先统一格式和时区,再从时间戳里提取出星期、月份、节假日、距上次事件间隔等特征。时间序列数据里还有一个独特问题——不能用普通交叉验证随机打乱,必须按时间顺序划分。这个坑在金融场景里尤其致命,未来数据混进训练集,指标非常好看,实盘一塌糊涂。
文本型数据则是另一套流程:分词、去停用词、词干化或词形还原,再到TF-IDF或词嵌入。文本处理的地域性很强,中文和英文的预处理逻辑差别很大,中文分词用什么词典、如何处理网络新词,都需要结合实际场景调整。
2.3 先理解业务,再动手清洗是铁律
这可能是整个数据预处理里我吃过最多亏后总结出的最重要一条原则:动手清洗之前,必须先搞懂业务含义。否则你眼里看到的是异常值,在业务上却可能是极其重要的信号。
举个例子。我在处理电商退款率数据时,发现某个单一用户订单数量异常高,整整比中位数高出三个数量级,按IQR法这妥妥是异常值,直接剔除的话,模型会损失掉一个重要规律——这个用户实际上是一个批发客户。后来和业务方确认后才知道,这批“异常数据”恰恰是整个销售漏斗里最高价值的标签,剔除后模型对高价值客户的识别能力大打折扣。
所以我在每个项目里都会做一件事:拿数据探查出的“异常”结果去和业务方做一轮确认,把怀疑清单列出来:“这些样本你们看是真实情况吗?是什么业务原因导致的?”这个问题经常会带来意外收获,有时能直接发现数据采集环节的bug,有时能帮你发现一条新的业务规则。数据预处理的最终目标不是让数据“看起来干净”,而是让数据如实反映你所关心的那个真实世界。不理解业务,你的清洗就只是在瞎猜。
3. 实操:用 Python 完成一份标准的数据预处理流程
3.1 环境准备与工具选型
Python做数据预处理的主力工具,绕不开pandas、numpy、scipy和matplotlib这几个老牌库。pandas负责表格操作,numpy提供数值计算基础,scipy在统计检验和插值上非常方便,matplotlib用来做任务周期里的快速可视化探查。近两年polars也开始流行,它底层基于Rust,处理大数据集时速度比pandas快不少,但生态和认知度还没有pandas广。我的建议是常规项目直接用pandas,遇到单表几个G以上的场景再考虑polars。
安装环境其实没什么可说的,直接pip install pandas numpy scipy matplotlib就行。但我建议用虚拟环境管理Python依赖,避免不同项目的包版本冲突。我见过最惨的一次是同事在全局环境里装了一个新版本numpy,把另一个老项目直接搞到无法运行,最后花了两小时才定位到是numpy版本兼容问题。
用Python做数据预处理,最大的优势不是某个库“魔法般”地好用,而是你在清洗过程中的每一步都可以随时打印中间结果、画图、验证,这种交互式的探索体验是其他工具很难替代的。数据预处理本质上是一个“发现问题-验证猜想-解决”的循环过程,Jupyter Notebook的交互模式天然适合这种工作方式。
3.2 第一步:数据导入与初步探查(EDA)
拿到一份数据,千万别着急处理。我每次工作的第一步都是先把数据看透。这里的“看透”不是全量浏览,而是有方法地快速了解整体情况。
import pandas as pd import numpy as np import matplotlib.pyplot as plt df = pd.read_csv("sales_data.csv", encoding="utf-8") print(df.shape) print(df.info()) print(df.describe()) print(df.head(10)) print(df.nunique()) print(df.isnull().sum())这一串代码看起来简单,但信息量很大。df.shape告诉你行数和列数,df.info告诉你每列的类型和非空数量,df.describe给出数值列的分布统计量,df.nunique可以快速发现哪些列是常量、哪些列是高基数类别,df.isnull().sum()让你立刻定位缺失严重的地方。这些都是EDA(探索性数据分析)的起点。
EDA阶段还有一个重要动作是可视化探查。我会用hist画数值列的分布直方图,用boxplot看离群值,用correlation heatmap看特征间相关性。可视化最大的价值是“看见本应该被发现的模式” —— 比如一个本来应该服从正态分布的字段出现了双峰,那多半是数据混入了两个不同群体,这种信息光看统计量是发现不了的。
df.hist(figsize=(12, 12), bins=50) plt.tight_layout() plt.show() corr = df.select_dtypes(include=[np.number]).corr() plt.imshow(corr, cmap="coolwarm", aspect="auto") plt.colorbar() plt.show()这一步做完,你对这份数据已经有整体认识了。接下来才开始真正的清洗。
3.3 第二步:缺失值处理的关键判断
缺失值处理最忌讳一刀切。我的做法是先按列统计缺失比例,然后根据重要性和缺失比例做一个策略判断。
缺失比例低于5%的列,默认可以直接删除缺失行,前提是缺失行是随机出现的;如果某一列缺失比例超过30%,而且这一列又不是建模核心特征,我一般是直接丢弃该列;对于核心特征列的缺失,会优先考虑填充。填充方式按数据场景来选择:连续变量通常用中位数或均值填充,但有明显偏态的变量我几乎只用中位数——均值会被极端值拉偏;类别变量用众数填充最安全。
for col in df.columns: missing_rate = df[col].isnull().mean() if missing_rate == 0: continue if missing_rate < 0.05: df = df.dropna(subset=[col]) elif missing_rate > 0.3 and col not in key_features: df = df.drop(columns=[col]) else: if df[col].dtype in ["float64", "int64"]: df[col] = df[col].fillna(df[col].median()) else: df[col] = df[col].fillna(df[col].mode()[0])上面这段代码体现的就是我前面说的策略框架。真实项目里,你还会遇到时间序列或者带明显趋势的数据,这时候用前向填充(ffill)或后向填充(bfill)更合适。比均值填充更高级的还有KNN填充和模型预测填充,但它们的计算成本高,适合对最终精度要求极高的场景。顺序上我会先简单填充跑一版基线模型,如果发现某个特征对结果影响很大,再回头对它精细处理。
缺失值处理上我踩过最大的坑是数据泄漏。有一回我用全量数据的均值去填充训练集和测试集的缺失值,自己觉得没什么大不了,结果模型在线上表现远不如离线指标。原因就是我用到了整个数据集(包括未来数据)计算出的统计量去填充,把未来信息带进了训练。正确的做法是只用训练集统计出填充参数,再套用到测试集上。这跟特征缩放要在训练集上fit、在测试集上transform是同一个道理。
3.4 第三步:异常值与重复值处理
异常值的判断不能只看一个指标,最好结合业务逻辑和多种统计方法交叉验证。我常用的是IQR法和Z-score法,两者各有适用场景。
IQR法的逻辑是用四分位距来界定离群区域:小于Q1-1.5IQR或大于Q3+1.5IQR的值视为异常。这个方法的优点是抗极端值干扰,不需要数据满足正态分布,非常适合偏态严重的现实数据。Z-score法用标准差来衡量偏离程度,一般把Z-score绝对值大于3的点视为异常,但它对数据分布假设比较强,如果数据不是近似正态,Z-score很容易把人畜无害的偏态数据误判为异常。
def detect_outliers_iqr(s): Q1 = s.quantile(0.25) Q3 = s.quantile(0.75) IQR = Q3 - Q1 lower_bound = Q1 - 1.5 * IQR upper_bound = Q3 + 1.5 * IQR return (s < lower_bound) | (s > upper_bound) outlier_mask = detect_outliers_iqr(df["price"]) print(f"Detected {outlier_mask.sum()} potential outliers") df.loc[outlier_mask, "price"].head(20)检测到异常值之后,处理方式有几种。如果确认是录入错误,直接改正或者删除。如果是真实存在的极端值,但业务上有意义,比如某些高端商品的售价天然就高,那不应该删,而是要单独做分箱处理或者用稳健缩放(RobustScaler)来压缩极端值的影响。还有一种做法是给异常值加一个标记列,把“是否为异常”本身当作一个特征交给模型,模型自己可以去学习这个规律。
重复值处理相对机械:直接按所有字段去重,或者按业务主键去重。但这里有个细节,就算主键相同,如果其他字段不一样,这可能是同一个人在不同时间产生的行为记录,不能简单去重。我会明确区和“完全相同的事务性记录”与“同一实体多次行为记录”的区别。
3.5 第四步:特征工程与数据转换
清洗干净之后,还没到建模那一步,中间还隔着特征工程。这一步的核心是让原始数据“长出”有价值的信息。
类别特征我常用的编码策略是:基数少(比如性别、设备类型)用one-hot,基数中等(比如城市、职业)先用频次编码或者目标编码做实验,基数很高(比如用户ID、商品ID)就不直接进模型,而是转换为“该用户历史行为总量”“该商品在窗口期内的销量”之类的聚合特征。
数值特征的转换要看分布。我前面提到,使用线性模型时需要对偏态数据做log变换,对量纲问题要标准化或归一化。标准化适合符合近似正态分布的数据,归一化(MinMaxScaler)适合有明确上下界的数据。还有一个容易被忽略的点是:缩放操作必须在训练集上计算参数,否则会引入数据泄漏。
from sklearn.preprocessing import StandardScaler scaler = StandardScaler() X_train_scaled = scaler.fit_transform(X_train) X_test_scaled = scaler.transform(X_test)时间特征的处理也很有意思。把datetime拆成年、月、日、星期几,通常还不够,还需要构造“距上次购买的间隔天数”“该用户在一天内的活跃时段”“是否周末”等业务含义明确的特征。这类特征的预测力,往往比简单的“月份数字”强得多。
特征分箱也是一个实战中很好用的技巧。把连续变量比如年龄分成“少年/青年/中年/老年”区间,可以降低特征对异常值的敏感度,同时让非线性关系更容易被线性模型捕捉。分箱的边界怎么定,最稳妥的是基于业务常识,或者用分位数来切分以保证每箱样本量均衡,不推荐等宽切分——它很容易造成某些箱里一个样本都没有。
3.6 第五步:数据划分与输出管理
数据预处理到后面,一定要把训练集、验证集、测试集分离好。这一步千万别拖到建模之前才做,因为从预处理开始就得严格按“训练集统计参数 → 应用全部数据”的口径来操作。
我的习惯是在滚动开发周期里先切片出测试集,单独保存,整个预处理和特征工程的过程只能在这个测试集上应用一次,最佳实践是最后评估时才碰它。训练集内部再切出验证集用来调参。这里要强调:凡是需要从数据中计算的统计量(均值、方差、缺失值填充值、编码映射等),都必须只用训练集计算。
from sklearn.model_selection import train_test_split train_val, test = train_test_split(df, test_size=0.2, random_state=42, stratify=df["target"]) train, val = train_test_split(train_val, test_size=0.25, random_state=42, stratify=train_val["target"]) print(train.shape, val.shape, test.shape)如果标签是分类问题,注意切分时用stratify参数做分层抽样,保持各组别标签分布一致,否则很可能某一类在你训练集中只出现几十个样本,模型根本学不到。随机种子固定下来是为了实验结果可复现。
输出管理通常被忽略但并不该被忽略。我要求自己和团队把每一次数据处理的脚本版本化,输出的干净数据带版本号,建模结果里记录“数据版本-脚本版本-参数组合”。否则三个月后回来看模型,你根本说不清这份结果是用哪一版数据做出来的。打包数据用parquet格式存储先天比csv格式快和紧凑,还保住了数据类型——csv会把你辛辛苦苦清理好的日期类型全部还原成字符串。
4. 进阶:栅格与时空数据预处理的特殊问题
4.1 遥感数据预处理的基本链路
核心热词里提到的“npp夜间灯光数据预处理”和“gf2qgis数据预处理”,属于遥感与GIS方向。这类数据跟普通表格数据的预处理思路不太一样,处理对象是栅格影像,核心链路要比表格数据长得多。
以国产高分二号(GF-2)为例,拿到原始影像后,第一步是辐射定标,把传感器记录的数字量化值(DN值)转换为辐亮度或反射率;然后是大气校正,消除大气散射、吸收对地表反射信号的影响,这一步如果不做,后续计算各种指数的数值都是错的;接着是正射校正和几何校正,把影像纠正到真实的地理坐标上,否则影像跟矢量数据叠加时会出现偏移;再往下是云检测与去云处理;最后才根据研究范围裁剪和重采样。
在QGIS里操作遥感预处理的坑,我提几个实践体会。第一是坐标参考系统(CRS)必须统一。很多时候导进来的栅格是WGS84经纬度,矢量数据却用的是投影坐标系(比如UTM或CGCS2000分带投影),直接在QGIS里叠加后看着似乎能用,实际上量算面积和距离会出错。第二是无效值(NoData)问题,影像边缘的黑色区域其实是有值的,只是有指定的NoData值,不设置NoData掩膜的话,后面任何统计运算都会带上这些无效像元。第三是重采样方法的选择,降采样用双线性或三次卷积会更平滑,升采样用最近邻可以保留原始像元分类信息。
4.2 夜间灯光数据的处理要点
夜间灯光数据NPP-VIIRS是研究人类活动、城市化、碳排放中非常热门的空间数据源。但这类数据有个特殊之处:它不像Landsat那种影像经过精处理可以直接用,原始数据里混杂了火光、渔船灯光、极光以及一些残留噪声。
处理NPP夜间灯光数据时,我一般会做这几件事:投影转换统一到研究区常用的投影;月度数据合成年度数据时要注意去除异常亮像元,比如火灾点和瞬时光污染;对极少见的负值(通常来自云掩膜或底部噪声)做截断或掩膜处理;最后按研究区边界裁剪并重采样到统一分辨率。
有一个细节值得多说一句:不同年份的NPP数据之间其实存在连续性偏差,因为卫星传感器本身会有衰变,如果你拿来做多年趋势分析,最好做“同类校正”或者只做同一年份的横截面对比,否则趋势很容易被传感器退化掩盖。这个坑我在论文级项目里看到过好几回,方法论审稿人一定会问。
4.3 时空数据还需要多留一个心眼
除了专业的遥感影像,普通数据科学项目里也常遇到带经纬度或者行政区划的时空数据。处理这类数据时,除了常规的表格数据清洗,还要多考虑几个问题。
首先是坐标系的统一。一个大数据集里混进部分数据的经纬度用的是GCJ-02,另一部分用WGS-84,坐标本身只差几百米,但在地图可视化或者空间计算时就会告诉你“晴空万里下有东西过着不可描述的生活”。这个“坐标系不统一导致结果全错”的坑,我至少见过三次。
其次是空间粒度不匹配。有的数据是到省,有的是到市,还有的是具体经纬度。聚合后做模型时,必须明确以最小的空间粒度为准,还是以业务分析目标为准,别把不同粒度的数据直接拼一起当同一个维度。最后是边界效应,很多分析里做空间分箱时,把位于城市边界上的样本硬分到某一区会引入偏差,这时候先用sjoin做空间连接,正确地按行政区划进行分组统计,会比你自己写经纬度范围硬切靠谱得多。
5. 常见问题与排查技巧实录
5.1 缺失值一删结果模型全崩了
这是新手最容易遇到的情况:训练集缺失值删完之后,模型训练效果反而大幅退化。排查思路不是急着调参,而是回头看看你删掉的行占比多大。如果缺失行占整体比例的30%以上,删除会直接改变训练集分布,导致模型学到的规律偏离真实场景。处理方式是改用填充策略,或者干脆在模型里加入“是否缺失”的指示特征,让模型自己决定缺失信息对预测有没有贡献。缺失本身有时候就是信息,比如用户没填手机号,可能说明他是临时游客,这个信号有价值。
5.2 标准化做完模型反而不如不标准化
这个问题的根源是忽略了“模型是否是尺度敏感模型”。对决策树、随机森林这类树模型,特征是否标准化影响的是分裂点计算,但不影响树的拓扑结构,因为树分裂只关注相对顺序。你如果把所有特征都标准化了,树模型反而可能因为特征的原始分布信息被改变而变得更难解释。解决办法很简单:用之前先想清楚你要用的模型是什么。线性回归、逻辑回归、KNN、SVM、神经网络原则上建议标准化;GBDT系列、随机森林可以跳过这一步。
5.3 中文编码问题总给数据“捣乱”
用pandas读含中文的CSV或Excel时,如果你不确定编码,最好在open阶段就处理,而不是等到读出乱码再抢救。我的经验是:Python里的read_csv时经常用encoding='utf-8'读,如果报错,就试'gbk'或'gb2312',再不行用latin1兜底。但更本质的建议是:项目开始时统一规定所有文件编码为UTF-8,并在数据采集脚本里强制处理转换,省得后面每个环节都提心吊胆怕编码出问题。
5.4 预处理后模型泄漏,离线好上线崩
泄漏这个问题隐蔽性很强,它的本质是:在训练时模型见到了本不应该见到信息。常见来源有三处:一是填充缺失值的统计量用了全量数据(含测试集);二是特征缩放fit时用了全量数据;三是做目标编码时用了包含目标变量信息的统计量。一个好的自查方法是建模后问自己一句:“对于测试集里某一行样本,我在算它的特征时,有没有可能用到除了这一行以外的、和测试集同一时间点的其他信息?”如果有,那就基本泄漏了。
5.5 数据版本管理被忽略
很多项目前期混乱就乱在数据版本上。我今天清洗了一份,明天收到新数据又清洗了一份,两份口径还不一样,三个实验跑下来谁也不知道哪份对应哪个结果。我的解决方案很简单:每份输出数据都带数据版本标签和生成日期,模型训练脚本里把这个标签写进日志。数据处理脚本提交到Git,关键节点打tag。头疼的时候少很多。
6. 数据预处理的经验体会与避坑清单
6.1 我踩过三次的细节坑
一个细节是在做去重时,只按主键去重而忽略了更新记录的版本,结果把用户最新的行为覆盖丢失了。正确的做法是先按用户+事件时间排序,再按用户保留最新一条记录。另一个是我以前习惯在数据清洗时做一个全流程代码跑完一气呵成,后来发现非常难复现和排除问题。现在我会把预处理拆成多个小步骤,每一步都输出中间文件,比如clean_01_raw.py、clean_02_missing.py。这样哪个环节出错能立刻定位,也能随时对比不同清洗策略对下游的影响。
还有一个是关于QGIS与Python混用的经验。很多人喜欢在QGIS里手工点点点处理数据,而忽视了整个过程的可复现性。QGIS其实自带Python控制台和Processing框架,处理流程应该尽量写成模型文件或脚本,以后更新数据时一键重跑。
6.2 给新入行朋友的数据预处理自检清单
每次项目交付前,我会按下面这个清单快速自查一遍:所有字段格式是否符合预设类型?数值列分布是否合理,是否有应识别未识别的异常值?缺失值处理是否说明了原因,是否可能导致偏差?类别编码映射是否记录在案?时间列是否已统一时区?特征统计量是否只在训练集上计算?数据集是否按同一随机种子划分并可复现?
这份清单不一定适合所有领域,但核心思想是一致的:数据预处理不是随便跑几个函数就结束的,它的产出必须有据可查、有逻辑可循。你处理的每一处数据异常,都要能够向业务方解释:“为什么这样处理”。那些不能解释的处理,往往就是后面出问题的隐患。
数据预处理这件苦活,表面上不如建模那么光鲜亮丽,但恰恰是它让数据科学真正落地。我见过太多项目从数据摸底那一刻就注定命运,也见过用非常简单的模型,因为数据预处理扎实,反而赢得了最终结果。踏踏实实把数据处理好,比什么炫技都管用。