
1. 项目概述从“有数据”到“好数据”的鸿沟刚接触数学建模的朋友常常会陷入一个误区拿到题目看到一堆数据就觉得万事俱备可以直接上手跑模型了。我当年也是这么想的直到在第一次正式比赛中因为数据预处理不当导致模型结果完全偏离实际才真正明白“数据的准备”这五个字背后究竟藏着多少门道和“坑”。这绝不是把Excel表格打开、另存为CSV那么简单。它更像是一个外科手术前的消毒与备皮或者厨师烹饪前的食材清洗与刀工处理直接决定了你后续所有“烹饪”建模的成败与风味。“数模历程二数据的准备”这个标题精准地指向了数学建模流程中承上启下的关键一环。它承接了第一部分的“问题理解与抽象”又为第三部分的“模型构建与求解”奠定基石。这里的数据准备核心目标是将原始、粗糙、可能充满“杂质”的数据转化为干净、规整、适合特定模型“消化”的“营养餐”。这个过程我们称之为数据预处理。它解决的痛点非常明确原始数据往往存在缺失、异常、量纲不一、分布怪异等问题直接喂给模型轻则导致结果不准确重则让模型直接“崩溃”或得出荒谬结论。无论是参加国赛、美赛还是在工作中解决一个实际的业务问题数据准备所花费的时间常常会占到整个项目周期的60%甚至更多。这篇文章我就结合自己踩过的无数个坑系统梳理一下数据准备的完整流程、核心技术与那些教科书里不会写的实操心得。2. 数据准备的核心流程与战略思考在动手处理任何一个数据单元格之前我们必须先有一个清晰的“作战地图”。数据准备不是一堆零散技巧的堆砌而是一个有逻辑、分步骤的系统工程。它的核心流程可以概括为“获取-审视-清洗-转换-重构”五个阶段但每个阶段都需要根据具体问题灵活调整策略。2.1 数据获取与初步审视避免“垃圾进垃圾出”一切始于数据源。常见的数据来源包括比赛组委会提供的附件、公开数据集如Kaggle、UCI、从网络爬取、或是从公司内部数据库导出。无论来源如何第一步永远是初步审视。我常用的“第一眼”检查清单文件格式与编码用文本编辑器如VS Code、Notepad打开看看确认文件是UTF-8、GBK还是其他编码避免用Pandas或Excel打开时出现乱码。特别是中文数据编码问题首当其冲。数据规模用df.shapePandas或简单浏览了解数据有多少行样本数、多少列特征数。这决定了后续处理的计算复杂度和内存占用。字段概览查看列名df.columns和每列的前几行数据df.head()对每个字段的含义、类型数值、文本、日期建立初步认知。基础统计信息快速运行df.describe()和df.info()。describe()会给出数值型字段的均值、标准差、分位数能立刻发现一些极端值info()则显示每列的非空值数量和数据类-型是发现缺失值的利器。注意千万不要一上来就做任何修改操作先完整地看一遍对数据的“原貌”有整体把握甚至可以用df.to_csv(‘raw_data_backup.csv’)做个备份。很多错误是在盲目清洗后才发现的没有原始数据对照你将无从查起。2.2 数据清洗处理“脏数据”的四大顽疾清洗是数据准备中最耗时、也最考验耐心的部分。主要对付四大问题缺失值、异常值、重复值和格式不一致。2.2.1 缺失值处理不是简单删除或填充缺失值就像食材上的霉斑不能视而不见也不能一刀切。处理前必须先分析其缺失机制完全随机缺失MCAR缺失和任何变量都无关。例如调查问卷因印刷问题漏了一行。随机缺失MAR缺失只与其他已观测变量有关。例如年轻人更可能不填写收入项。非随机缺失MNAR缺失与自身值有关。例如高收入人群更可能拒绝透露收入。常用处理方法及选择逻辑删除仅当缺失比例极低如5%且样本量足够大时可考虑删除整行。如果某特征缺失率极高如50%考虑删除整列。命令df.dropna(axis0, how‘any’, threshNone, subsetNone)。填充这是更常用的方法。统计值填充用均值、中位数、众数填充。适用于数值型且分布相对均匀时。注意填充均值会减少方差可能引入偏差。前后值填充ffill/bfill适用于时间序列数据用前一个或后一个有效值填充。df.fillna(method‘ffill’)。插值法df.interpolate()适用于有序数据如时间、空间序列效果通常优于简单填充。模型预测填充用其他特征建立模型如回归、KNN来预测缺失值。这是最复杂但可能最准确的方法适用于缺失机制为MAR时。例如用sklearn.impute.KNNImputer。实操心得对于比赛如果时间紧迫对数值变量我常先用中位数填充对异常值不敏感对分类变量用众数填充。但在论文中必须说明你的选择及理由。绝对禁忌盲目用0填充除非0有明确的业务含义如未购买。2.2.2 异常值检测与处理找出“害群之马”异常值不一定是错误可能是重要的业务信号如欺诈交易也可能是录入错误。检测是关键。检测方法原理与适用场景操作示例Python3σ原则 / Z-score假设数据服从正态分布计算Z-score通常将|Z|3的值视为异常。from scipy import stats; z_scores np.abs(stats.zscore(df[‘col’]))IQR箱线图法更稳健不依赖正态分布。IQR Q3 - Q1通常将小于Q1-1.5IQR或大于Q31.5IQR的值视为异常。Q1 df[‘col’].quantile(0.25); Q3 df[‘col’].quantile(0.75); IQR Q3 - Q1基于模型的方法如孤立森林Isolation Forest、局部离群因子LOF适用于高维数据或复杂分布。from sklearn.ensemble import IsolationForest处理策略核实如果可能追溯原始数据源确认是否为错误。修正如果是明显错误如身高2.5米可根据业务逻辑修正或视为缺失值处理。删除确认是错误且无法修正或对模型影响极大时。保留如果是业务上的重要异常如黑天鹅事件则不能删除可能需要单独建模或使用对异常值不敏感的模型如树模型。缩尾/截尾将超出特定分位数的值用该分位数值替代减少极端值影响。df[‘col’] np.clip(df[‘col’], a_minlower_percentile, a_maxupper_percentile)2.2.3 重复值与格式标准化重复值df.duplicated()查找df.drop_duplicates()删除。但需谨慎有些业务场景下重复记录是合理的如同一用户多次购买。格式标准化文本清洗统一大小写、去除首尾空格、纠正拼写错误可用fuzzywuzzy库。日期时间用pd.to_datetime()统一格式并提取年、月、日、星期等特征。分类变量编码将文本型类别如“男”、“女”转换为模型可读的数字。注意有序分类如“小”、“中”、“大”和无序分类如“北京”、“上海”的区别。3. 数据转换与特征工程从“原材料”到“半成品”清洗后的干净数据就像洗好的蔬菜还需要切配、腌制才能下锅。这就是特征工程被广泛认为是影响模型性能的最关键因素。3.1 特征缩放让模型“公平”地看待每一个特征当特征量纲差异巨大时如年龄18-100收入5000-500000基于距离的模型如KNN、SVM、神经网络和依赖梯度下降的模型如线性回归、逻辑回归会严重偏向数值大的特征。必须进行缩放。缩放方法公式对单个特征x适用场景注意事项标准化 (Z-Score)(x - μ) / σ数据近似正态分布或需要用到特征协方差矩阵时如PCA、LDA。对异常值敏感因为μ和σ受异常值影响大。归一化 (Min-Max)(x - min) / (max - min)数据边界清晰需要将值压缩到固定区间如[0,1]。常用于图像像素值。对异常值极度敏感min/max会被异常值拉偏。鲁棒缩放 (Robust)(x - median) / IQR数据中存在显著异常值时。使用中位数和四分位距受异常值影响小。缩放后数据不一定在固定区间内。实操命令使用sklearnfrom sklearn.preprocessing import StandardScaler, MinMaxScaler, RobustScaler scaler StandardScaler() X_scaled scaler.fit_transform(X) # 切记用训练集fit的参数去transform测试集避免数据泄露3.2 特征构造挖掘数据的深层信息这是最能体现建模者业务理解和创造力的环节。通过对现有字段进行组合、分解、聚合创造出对目标变量预测能力更强的新特征。从单一特征衍生日期时间从“交易时间”可以提取“是否周末”、“是否节假日”、“一天中的时段早晨/下午/晚上”、“季度”等。文本从“地址”中提取“城市”、“区县”从“商品描述”中提取关键词、计算文本长度、情感分值。数值对“价格”做对数变换log1p以稳定方差计算“增长率”、“累计值”。特征交叉四则运算“单价” “总价” / “数量”“BMI” “体重” / (“身高”^2)。多项式特征生成特征的高次项和交互项用于捕捉非线性关系。from sklearn.preprocessing import PolynomialFeatures分组聚合在用户行为数据中计算每个用户的“历史平均购买金额”、“最近一次购买距今天数”等。这通常需要用到groupby操作。一个经典案例在电商销量预测中原始数据只有“日期”和“销量”。通过特征工程可以构造出时间特征星期几、月份、是否节假日、是否促销日。历史统计特征前3天平均销量、前7天销量标准差、去年同期销量。趋势特征最近7天的销量移动平均线斜率。 这些构造出的特征往往比原始日期对销量的预测能力更强。3.3 特征选择降维与提纯不是所有特征都是有益的。无关或冗余的特征会增加模型复杂度、降低泛化能力、增加计算成本。特征选择的目标是找到最优特征子集。方法类别代表方法原理与特点过滤法方差选择、相关系数、卡方检验、互信息独立于任何机器学习模型基于统计指标对特征评分和排序。计算快但未考虑特征与模型的关系。包裹法递归特征消除RFE、前向/后向选择将特征选择看作一个搜索问题使用特定模型的性能作为评价准则。效果通常更好但计算成本高。嵌入法Lasso回归、决策树特征重要性、基于树模型的特征选择在模型训练过程中自动进行特征选择。是过滤法和包裹法的折中效率与效果兼顾。我的常用策略先用过滤法如去除方差接近0的特征或与目标变量相关性极低的特征做快速粗筛。然后使用嵌入法例如训练一个随机森林查看feature_importances_保留重要性较高的特征。最后如果特征数量仍然很多再用包裹法如RFE进行精调。这个过程可以在交叉验证循环中进行以避免过拟合。4. 数据集划分与评估准备为模型训练铺好最后一里路数据准备好之后在投入模型之前还有至关重要的一步划分数据集。这是评估模型泛化能力、防止过拟合的基础。4.1 训练集、验证集与测试集的划分核心原则测试集只能用于最终评估在模型开发和调参过程中绝对不可见。训练集用于模型训练学习参数。验证集用于在训练过程中调整超参数、选择模型、进行早停等。它是我们“模拟考试”的试卷。测试集在模型完全确定包括超参数后用于最终、一次性的性能评估。它是“最终高考”的试卷。常用划分比例如70%-15%-15%或80%-10%-10%。数据量极大时验证/测试集比例可以更小。实操命令from sklearn.model_selection import train_test_split # 第一次分割分出训练验证集 和 测试集 X_train_val, X_test, y_train_val, y_test train_test_split(X, y, test_size0.15, random_state42) # 第二次分割从训练验证集中再分出训练集和验证集 X_train, X_val, y_train, y_val train_test_split(X_train_val, y_train_val, test_size0.176, random_state42) # 0.176 ≈ 0.15/0.85关键点random_state设置一个固定值保证每次运行划分结果一致实验可复现。4.2 时间序列数据的特殊划分对于时间序列数据如销量预测、股票价格绝对不能随机划分必须按时间顺序划分。例如用前80%时间的数据做训练紧接着的10%做验证最后10%做测试。这模拟了在现实世界中我们只能用过去的数据预测未来。4.3 类别不平衡问题的处理当分类问题中不同类别的样本数量差异巨大时如欺诈交易仅占1%模型会倾向于预测多数类导致对少数类的识别率极低。必须在数据准备阶段或模型训练阶段处理。重采样数据层面过采样增加少数类样本。最简单的是随机复制但容易过拟合。更高级的有SMOTE算法通过插值合成新的少数类样本。from imblearn.over_sampling import SMOTE欠采样减少多数类样本。可能丢失重要信息。调整类别权重算法层面大多数分类算法如逻辑回归、SVM、决策树都支持在训练时为不同类别的样本设置不同的权重让模型更“关注”少数类。例如在sklearn中设置class_weight‘balanced’。我的建议先尝试使用算法内置的类别权重调整。如果效果不佳再考虑使用SMOTE进行过采样。同时评估指标不能再用准确率Accuracy而应使用精确率Precision、召回率Recall、F1-score或AUC-ROC曲线。5. 数据准备的实战工具箱与避坑指南理论说再多不如动手实践。这里我分享一套自己常用的、以Python为核心的数据准备“工具箱”和那些踩过坑才得来的经验。5.1 核心工具链与高效工作流探索性数据分析EDAPandas数据操作、NumPy数值计算是基石。可视化强烈推荐Seaborn和Plotly。Seaborn的pairplot,heatmap,boxplot能快速揭示数据关系和分布。Plotly的交互性在分析多维数据时尤其有用。数据清洗与转换Pandas提供了一站式解决方案fillna,dropna,apply,map等。对于复杂转换可以结合NumPy的向量化运算。特征工程scikit-learn的preprocessing模块是标准库StandardScaler,PolynomialFeatures等。对于日期处理Pandas的dt属性非常强大。对于文本特征可以开始使用scikit-learn的CountVectorizer或TfidfVectorizer。工作流自动化使用scikit-learn的Pipeline和ColumnTransformer。它们可以将清洗、缩放、编码等步骤封装成一个可复用的流水线确保训练集和测试集得到完全相同的处理极大减少错误。from sklearn.compose import ColumnTransformer from sklearn.pipeline import Pipeline from sklearn.impute import SimpleImputer from sklearn.preprocessing import OneHotEncoder, StandardScaler # 定义数值型和分类型特征的处理管道 numeric_transformer Pipeline(steps[ (‘imputer’, SimpleImputer(strategy‘median’)), (‘scaler’, StandardScaler())]) categorical_transformer Pipeline(steps[ (‘imputer’, SimpleImputer(strategy‘constant’, fill_value‘missing’)), (‘onehot’, OneHotEncoder(handle_unknown‘ignore’))]) # 组合起来 preprocessor ColumnTransformer( transformers[ (‘num’, numeric_transformer, numeric_features), (‘cat’, categorical_transformer, categorical_features)]) # 然后将preprocessor与estimator放入最终Pipeline5.2 常见“大坑”与排查技巧实录即使流程清晰工具熟练在实际操作中还是会遇到各种意想不到的问题。下面这个表格是我总结的“避坑清单”问题现象可能原因排查与解决思路模型训练时损失不下降或准确率不变1. 特征未进行缩放特别是基于梯度下降的模型。2. 学习率设置不当。3. 特征与目标完全不相关。4. 数据标签本身有大量错误。1. 检查特征尺度使用df.describe()查看。2. 绘制特征与目标的散点图或计算相关系数。3. 从最简单的模型如线性回归开始看能否学到任何规律。模型在训练集上表现完美在测试集上很差过拟合1. 特征工程中数据泄露使用了未来或全局信息。2. 模型过于复杂特征过多。3. 训练数据量太少。1.最隐蔽的坑仔细检查在填充缺失值、做特征缩放时是否先划分了数据集是否用到了测试集的信息如全局均值必须保证所有预处理步骤的fit只作用于训练集。2. 进行特征选择增加正则化项。分类模型总是预测同一个类别1. 严重的类别不平衡。2. 评估指标使用错误用了准确率。3. 模型决策阈值默认是0.5可能不适合你的数据。1. 检查类别分布df[‘target’].value_counts()。2. 改用F1-score或AUC作为评估指标。3. 调整分类阈值或使用class_weight。处理后的数据出现大量NaN或inf1. 除以了零值。2. 对负数或零取了对数。3. 缩放时分母如标准差为零。1. 在除法或对数运算前检查分母和真数范围可以使用np.clip进行限制。2. 对于标准差为零的特征常数特征在缩放前直接将其剔除。内存溢出无法处理大数据1. 使用了pandas默认的float64或int64类型内存占用大。2. 一次性读入全部数据。1. 使用df.astype()将数值列转换为更小的类型如float32,int32。对于分类文本使用category类型。2. 使用分块读取pd.read_csv(chunksize50000)或考虑使用Dask库。5.3 数据准备的文档与可复现性在比赛或项目中数据准备步骤繁多参数复杂。为了确保可复现性让你自己一周后还能看懂也让队友能接手必须做好文档记录。编写数据预处理脚本将所有清洗、转换步骤写成独立的Python脚本如preprocess.py而不是在Jupyter Notebook里零散执行。脚本应包含清晰的函数和注释。保存中间数据和预处理对象将处理后的干净数据保存为新文件如cleaned_data.csv。更重要的是使用joblib或pickle保存拟合好的Scaler、Imputer、Encoder等对象。import joblib joblib.dump(scaler, ‘scaler.pkl’) joblib.dump(imputer, ‘imputer.pkl’) # 在预测时加载 scaler joblib.load(‘scaler.pkl’) new_data_scaled scaler.transform(new_data)记录处理决策在实验报告或代码注释中记录你为什么选择某种缺失值填充方法、为什么删除某些特征、异常值的判断标准是什么。这既是严谨性的体现也便于后续优化。数据准备是一场艰苦的“前哨战”它没有模型训练那样立竿见影的反馈却从根本上决定了你整个项目的天花板。花再多时间在数据准备上都不为过。当你觉得模型效果遇到瓶颈时不妨回过头来再看看你的数据——也许答案就藏在某个尚未被挖掘的特征里或者某个未被妥善处理的异常值中。把数据当成你最重要的合作伙伴理解它清洁它塑造它它才会在模型中给你最丰厚的回报。