前言在机器学习项目中数据预处理往往占据整个工作流70%以上的时间。一个高质量的数据集是模型成功的基础而数据预处理正是构建这个基础的关键环节。本文以一个矿物种类分类任务为例完整展示从原始数据到可用于建模的标准数据集的全流程涵盖数据清洗、缺失值处理、标签编码、数据标准化、类别不平衡处理等核心步骤。一、项目背景1.1 任务目标根据矿物的多种物理/化学特征如密度、硬度、化学成分等预测其所属类别A/B/C/D四类。这是一个典型的多分类问题适用于地质勘探、矿物鉴定等实际应用场景。1.2 原始数据说明数据文件矿物数据.xlsExcel格式特征列多个数值型特征具体列名略标签列矿物类型取值为 A、B、C、D、E存在问题存在异常类别E需删除存在大量缺失值需填充类别分布可能不平衡需过采样数据类型可能不一致需统一二、数据预处理importpandasaspdimportnumpyasnpfromsklearn.model_selectionimporttrain_test_splitfromsklearn.preprocessingimportStandardScalerfromsklearn.preprocessingimportLabelEncoderfromimblearn.over_samplingimportSMOTEimportwarnings warnings.filterwarnings(ignore)2.1 读取数据# 读取Excel文件datapd.read_excel(矿物数据.xls)print(f原始数据形状:{data.shape})print(f数据预览:\n{data.head()})print(f列名:{data.columns.tolist()})2.2 删除异常类别原始数据中存在类别 E属于异常数据或噪声数据直接删除# 删除类别为E的行datadata[data[矿物类型]!E]print(f删除异常类别后数据形状:{data.shape})print(f类别分布:\n{data[矿物类型].value_counts()})2.3 检查缺失情况# 统计缺失值null_numdata.isnull()null_totalnull_num.sum()null_percent(null_total/len(data))*100print(f原始数据集总缺失单元格数{null_total.sum()})print(\n各列缺失情况:)forcolindata.columns:ifnull_total[col]0:print(f{col}:{null_total[col]}个缺失 ({null_percent[col]:.2f}%))2.3 特征与标签分离# 剔除序号列无意义和标签列X_wholedata.drop([矿物类型,序号],axis1)y_wholedata[矿物类型]print(f特征矩阵形状:{X_whole.shape})print(f标签向量形状:{y_whole.shape})2.4 标签编码将文字标签 A/B/C/D 转换为数值 0/1/2/3便于模型处理# 方法1使用字典映射label_dict{A:0,B:1,C:2,D:3}encoded_labels[label_dict[label]forlabeliny_whole]y_wholepd.Series(encoded_labels,name矿物类型)# 方法2使用LabelEncoder更通用# from sklearn.preprocessing import LabelEncoder# le LabelEncoder()# y_whole pd.Series(le.fit_transform(y_whole), name矿物类型)print(f编码后标签分布:\n{y_whole.value_counts()})2.5 数据类型统一确保所有特征列为数值类型非数值内容转为 NaN# 统一转换为数值类型forcolumn_nameinX_whole.columns:X_whole[column_name]pd.to_numeric(X_whole[column_name],errorscoerce)2.6 划分训练集与测试集# 划分数据集先划分再填充避免数据泄露x_train,x_test,y_train,y_testtrain_test_split(X_whole,y_whole,test_size0.2,random_state42,stratifyy_whole)print(f训练集形状:{x_train.shape})print(f测试集形状:{x_test.shape})print(f训练集标签分布:\n{y_train.value_counts()})print(f测试集标签分布:\n{y_test.value_counts()})三、缺失值填充核心环节3.1 填充策略说明本项目实现了三种填充策略并以中位数填充作为最终方案填充方法训练集策略测试集策略优点缺点均值填充按类别分组用组内均值填充用训练集同类均值填充简单快速对异常值敏感众数填充按类别分组用组内众数填充用训练集同类众数填充适合分类特征可能不适用于连续特征中位数填充按类别分组用组内中位数填充用训练集同类中位数填充对异常值稳健可能忽略数据分布3.2 中位数填充代码实现训练集填充函数分组中位数defmedian_method(data):按列计算中位数并填充缺失值fill_valuesdata.apply(lambdax:x.median()ifnotx.isna().all()elseNone)returndata.fillna(fill_values)defmedian_train_fill(train_data,train_label):训练集分组中位数填充# 合并特征和标签datapd.concat([train_data,train_label],axis1)# 按类别分组Adata[data[矿物类型]0]Bdata[data[矿物类型]1]Cdata[data[矿物类型]2]Ddata[data[矿物类型]3]# 分别填充Amedian_method(A)Bmedian_method(B)Cmedian_method(C)Dmedian_method(D)# 合并结果df_filledpd.concat([A,B,C,D]).reset_index(dropTrue)# 分离特征和标签returndf_filled.drop(矿物类型,axis1),df_filled[矿物类型]测试集填充函数使用训练集统计量填充defmedian_test_fill(train_data,test_data,train_label,test_label):测试集使用训练集统计量填充# 合并训练集特征和标签train_combinedpd.concat([train_data,train_label],axis1)# 计算每个类别的中位数median_by_class{}forclass_valin[0,1,2,3]:class_datatrain_combined[train_combined[矿物类型]class_val]median_by_class[class_val]class_data.median()# 合并测试集特征和标签test_combinedpd.concat([test_data,test_label],axis1)test_filledtest_combined.copy()# 按类别填充测试集forclass_valin[0,1,2,3]:class_masktest_filled[矿物类型]class_valifclass_mask.any():test_filled.loc[class_mask]test_filled.loc[class_mask].fillna(median_by_class[class_val])# 分离特征和标签returntest_filled.drop(矿物类型,axis1),test_filled[矿物类型]3.3 执行填充# 训练集填充x_train_fill,y_train_fillmedian_train_fill(x_train,y_train)# 测试集填充x_test_fill,y_test_fillmedian_test_fill(x_train,x_test,y_train,y_test)print(f填充后训练集缺失值:{x_train_fill.isnull().sum().sum()})print(f填充后测试集缺失值:{x_test_fill.isnull().sum().sum()})四、数据标准化Z-Score标准化使各特征处于同一量纲避免大数值特征主导模型训练# 创建标准化器scalerStandardScaler()# 训练集fit transformx_train_scaledscaler.fit_transform(x_train_fill)# 测试集只使用训练集的统计量进行transform避免数据泄露x_test_scaledscaler.transform(x_test_fill)# 转换回DataFrame保持列名x_train_scaledpd.DataFrame(x_train_scaled,columnsx_train_fill.columns)x_test_scaledpd.DataFrame(x_test_scaled,columnsx_test_fill.columns)print(标准化后训练集统计:)print(f均值:{x_train_scaled.mean().values[:5]}...)print(f标准差:{x_train_scaled.std().values[:5]}...)五、处理类别不平衡SMOTE过采样使用 SMOTE合成少数类过采样技术对训练集进行平衡处理# 检查过采样前的类别分布print(过采样前训练集类别分布:)print(y_train_fill.value_counts())# 应用SMOTEsmoteSMOTE(random_state42,k_neighbors5)x_train_bal,y_train_balsmote.fit_resample(x_train_scaled,y_train_fill)print(\n过采样后训练集类别分布:)print(y_train_bal.value_counts())print(f过采样后训练集形状:{x_train_bal.shape})# ⚠️ 重要只对训练集进行过采样测试集保持原样print(f测试集保持原样形状:{x_test_scaled.shape})六、导出最终数据集将处理后的数据保存为 Excel 文件便于后续建模使用# 保存特征列名feature_colsx_train_fill.columns.tolist()# 训练集df_trainpd.DataFrame(x_train_bal,columnsfeature_cols)df_train[矿物类型]y_train_bal.reset_index(dropTrue)# 测试集df_testpd.DataFrame(x_test_scaled,columnsfeature_cols)df_test[矿物类型]y_test_fill.reset_index(dropTrue)# 保存到Exceldf_train.to_excel(中位数填充训练集.xlsx,indexFalse)df_test.to_excel(中位数填充测试集.xlsx,indexFalse)print(数据集已保存:)print(f- 训练集: 中位数填充训练集.xlsx ({df_train.shape}))print(f- 测试集: 中位数填充测试集.xlsx ({df_test.shape}))七、fill_data.pyfill_data.py 模块包含的函数函数名功能适用场景cca_train_fill/cca_test_fill删除含缺失值的行完整案例分析缺失值很少时mean_train_fill/mean_test_fill分组均值填充数据分布近似正态mode_train_fill/mode_test_fill分组众数填充分类特征或离散值median_train_fill/median_test_fill分组中位数填充最终方案存在异常值或偏态分布八、总结与思考本项目的关键知识点数据清洗删除异常类别、处理非数值数据缺失值处理分组填充优于全局填充因为不同类别的特征分布可能不同数据泄露防范测试集使用训练集的统计量均值/中位数/众数进行填充标准化时 fit 训练集transform 测试集SMOTE 只应用于训练集类别不平衡SMOTE 能有效提升少数类的识别率