矿物识别数据集预处理:缺失值填充与样本均衡化实践
在矿物识别的机器学习任务中,数据集的质量直接决定了模型最终的性能表现。实际工业场景中采集的矿物数据往往存在两大核心问题:一是因仪器故障、人工记录失误等导致的缺失值,二是因不同矿物采集难度差异造成的样本分布不均衡。这两个问题若不妥善处理,会导致模型训练偏差、泛化能力下降,甚至完全失效。
本文以矿物识别项目为背景,详细讲解如何基于 Python 实现矿物数据集的完整预处理流程,包括数据清洗、6 种缺失值填充策略的实现、Z 标准化处理以及 SMOTE 算法的样本均衡化,最终生成可直接用于模型训练的标准化数据集。
一、项目环境与数据准备
1. 环境依赖
本项目基于 Python 生态实现,核心依赖库及版本建议如下:
# 核心库安装命令
pip install imblearn==0.12.0
- imblearn:实现 SMOTE 过采样算法,解决样本不均衡问题
二、数据预处理核心流程
2.1 数据读取与初步清洗
首先完成数据的读取、无效数据过滤和数据类型标准化,为后续处理奠定基础:
import pandas as pd
import matplotlib.pylab as plt
from 数据清洗 import fill_data
# 读取Excel数据(xlrd引擎兼容.xls格式)
data = pd.read_excel('矿物数据.xls', engine='xlrd')
# 过滤无效标签(剔除矿物类型为E的样本)
data = data[data['矿物类型'] != 'E']
null_num = data.isnull()
null_total = null_num.sum() # 检测每列的缺失值
# 分离特征与标签(剔除无意义的序号列和矿物类型)
X_whole = data.drop('矿物类型', axis=1).drop('序号', axis=1)
# 将矿物类型单独列出
Y_whole = data.矿物类型
# 标签编码(将字符标签A/B/C/D转换为数值0-3,适配模型输入)
label_dict = {'A': 0, 'B': 1, 'C': 2, 'D': 3}
encoded_label = [label_dict[label] for label in Y_whole]
Y_whole = pd.Series(encoded_label, name='矿物类型')
# 特征列数据类型标准化(强制转换为数值型,无法转换的标记为NaN)
# 字符串数值直接转换为float,\和空格转换为Nan
for column_name in X_whole.columns:
X_whole[column_name] = pd.to_numeric(X_whole[column_name], errors='coerce')
2.2 特征标准化(Z 标准化)
矿物特征的量纲差异(如硬度单位为 HRC,密度单位为 g/cm³)会导致模型偏向数值范围大的特征,因此需进行 Z 标准化(均值为 0,方差为 1):
from sklearn.preprocessing import StandardScaler
# 初始化标准化器并拟合特征数据
scaler = StandardScaler()
X_whole_Z = scaler.fit_transform(X_whole)
# 转换为DataFrame,保留列名便于后续处理
X_whole = pd.DataFrame(X_whole_Z, columns=X_whole.columns)
2.3 数据集切分
将数据集按 7:3 比例切分为训练集和测试集,确保后续缺失值填充时测试集不参与训练集的统计计算(避免数据泄露):
from sklearn.model_selection import train_test_split
# 随机种子random_state=50000保证切分结果可复现
x_train_w, x_test_w, y_train_w, y_test_w = train_test_split(
X_whole, Y_whole, test_size=0.3, random_state=50000
)
三、缺失值填充:6 种策略的实现
缺失值是矿物数据的核心问题,本文在fill_data.py文件中实现了 6 种填充策略,涵盖简单统计填充、机器学习填充等不同类型,适配不同业务场景需求,并且主文件调用。
3.1 策略 1:删除缺失值(保留完整行)
核心逻辑:直接剔除包含缺失值的样本,仅保留完整数据行,是最简单的缺失值处理方式,适用于缺失率极低(<5%)的场景。
def cca_train_fill(train_data, train_label):
"""训练集缺失值处理:删除含缺失值的行"""
data = pd.concat([train_data, train_label], axis=1)
data = data.reset_index(drop=True)
df_filled = data.dropna() # 剔除所有含NaN的行
return df_filled.drop('矿物类型', axis=1), df_filled.矿物类型
def cca_test_fill(train_data, train_label, test_data, test_label):
"""测试集缺失值处理:与训练集逻辑一致"""
data = pd.concat([test_data, test_label], axis=1)
data = data.reset_index(drop=True)
df_filled = data.dropna()
return df_filled.drop('矿物类型', axis=1), df_filled.矿物类型
3.2 策略 2-4:统计值填充(均值 / 中位数 / 众数)
统计值填充是工业界最常用的缺失值处理方式,核心逻辑是按矿物类型分组计算统计值,用同类样本的统计特征填充缺失值,避免不同类型样本的统计值交叉污染。
以均值填充为例,实现代码如下:
def mean_train_method(data):
"""按列计算均值,填充缺失值"""
fill_values = data.mean()
return data.fillna(fill_values)
def mean_train_fill(train_data, train_label):
"""训练集均值填充:按矿物类型分组填充"""
data = pd.concat([train_data, train_label], axis=1)
data = data.reset_index(drop=True)
# 按矿物类型分组(0-3对应A-D)
A = data[data['矿物类型'] == 0]
B = data[data['矿物类型'] == 1]
C = data[data['矿物类型'] == 2]
D = data[data['矿物类型'] == 3]
# 分组填充
A = mean_train_method(A)
B = mean_train_method(B)
C = mean_train_method(C)
D = mean_train_method(D)
df_filled = pd.concat([A, B, C, D]).reset_index(drop=True)
return df_filled.drop('矿物类型', axis=1), df_filled.矿物类型
def mean_test_fill(train_data, train_label, test_data, test_label):
"""测试集均值填充:使用训练集的统计值(避免数据泄露)"""
train_data_all = pd.concat([train_data, train_label], axis=1).reset_index(drop=True)
test_data_all = pd.concat([test_data, test_label], axis=1).reset_index(drop=True)
# 训练集分组
A_train = train_data_all[train_data_all['矿物类型'] == 0]
B_train = train_data_all[train_data_all['矿物类型'] == 1]
C_train = train_data_all[train_data_all['矿物类型'] == 2]
D_train = train_data_all[train_data_all['矿物类型'] == 3]
# 测试集分组
A_test = test_data_all[test_data_all['矿物类型'] == 0]
B_test = test_data_all[test_data_all['矿物类型'] == 1]
C_test = test_data_all[test_data_all['矿物类型'] == 2]
D_test = test_data_all[test_data_all['矿物类型'] == 3]
# 用训练集均值填充测试集
A = A_test.fillna(A_train.mean())
B = B_test.fillna(B_train.mean())
C = C_test.fillna(C_train.mean())
D = D_test.fillna(D_train.mean())
df_filled = pd.concat([A, B, C, D]).reset_index(drop=True)
return df_filled.drop('矿物类型', axis=1), df_filled.矿物类型
中位数 / 众数填充:仅需将上述代码中的mean()替换为median()(中位数)或自定义众数计算逻辑即可
3.3 策略 5-6:机器学习填充(线性回归 / 随机森林)
对于缺失率较高(>20%)且特征间存在强相关性的矿物数据,统计值填充会丢失特征关联信息,此时可通过机器学习模型预测缺失值,核心逻辑是:将含缺失值的列作为目标变量,其他列作为特征,训练回归模型预测缺失值。
该函数的核心逻辑是:按缺失值数量升序遍历特征列,用其他已处理特征训练线性回归模型,预测并填充当前列的缺失值,测试集数据根据训练集的特征和模型预测,测试集数据不参与统计填充数据。(随机森林的逻辑同上)
from sklearn.linear_model import LinearRegression
def lr_train_fill(train_data, train_label):
# 拼接训练集特征和标签(合并为完整DataFrame,方便后续统一处理索引)
# train_data:训练集特征(DataFrame);train_label:训练集标签(Series,矿物类型)
train_data_all = pd.concat([train_data, train_label], axis=1)
# 重置索引(避免拼接后索引混乱,保证行索引从0开始连续)
train_data_all = train_data_all.reset_index(drop=True)
# 分离纯特征列(剔除标签列“矿物类型”,只保留需要填充的特征数据)
train_data_X = train_data_all.drop('矿物类型', axis=1)
# 统计每列的缺失值数量(isnull()返回布尔矩阵,sum()按列求和得到每列缺失值个数)
null_num = train_data_X.isnull().sum()
# 按缺失值数量升序排序列名(核心!先填充缺失少的列,保证后续填充有足够有效特征)
# 比如:列A缺失5个值,列B缺失20个值 → 先填列A,再填列B
null_num_sorted = null_num.sort_values(ascending=True)
# 初始化空列表:记录已处理(无缺失/已填充)的特征列,作为后续填充的自变量
filling_feature = []
# 遍历排序后的列名(按缺失值从少到多)
for i in null_num_sorted.index:
# 将当前列加入“已处理特征列表”(无论是否有缺失,都加入,保证后续列能用到)
filling_feature.append(i)
# 仅处理有缺失值的列(无缺失则跳过,节省计算)
if null_num_sorted[i] != 0:
# 构建回归模型的自变量X:已处理特征列中剔除当前列(用其他特征预测当前列)
# filling_feature是累积的已处理列,drop(i)排除当前待填充列
X = train_data_X[filling_feature].drop(i, axis=1)
# 构建回归模型的因变量Y:当前列的所有值(含缺失值,后续会分离)
Y = train_data_X[i]
# 定位当前列缺失值的行索引(找到需要填充的行)
# train_data_X[i].isnull():当前列缺失值位置(True),index.tolist()转为索引列表
row_numbers_mg_null = train_data_X[train_data_X[i].isnull()].index.tolist()
# 分离模型训练数据:剔除缺失行,仅用有值样本训练
X_train = X.drop(row_numbers_mg_null) # 自变量(无缺失)
Y_train = Y.drop(row_numbers_mg_null) # 因变量(无缺失)
# 分离待预测数据:仅保留缺失行的自变量(用于预测填充值)
X_test = X.iloc[row_numbers_mg_null]
# 初始化线性回归模型(默认参数,无正则化)
regr = LinearRegression()
# 训练模型:用有值样本拟合线性关系(X_train → Y_train)
regr.fit(X_train, Y_train)
# 预测缺失值:用训练好的模型预测缺失行的取值
Y_pred = regr.predict(X_test)
# 填充缺失值:将预测值赋值到当前列的缺失位置
train_data_X.loc[row_numbers_mg_null, i] = Y_pred
# 打印进度:提示当前列填充完成(方便调试)
print('完成训练数据集中的‘{}’列数据的填充'.format(i))
# 返回填充后的训练集特征 + 原标签(标签无缺失,直接返回)
return train_data_X, train_data_all.矿物类型
def lr_test_fill(train_data, train_label, test_data, test_label):
# 拼接训练集特征+标签(构建完整训练集,用于训练模型,无缺失值)
train_data_all = pd.concat([train_data, train_label], axis=1)
# 重置训练集索引(避免索引混乱)
train_data_all = train_data_all.reset_index(drop=True)
# 拼接测试集特征+标签(构建完整测试集,方便后续分离特征列)
test_data_all = pd.concat([test_data, test_label], axis=1)
# 重置测试集索引(保证行索引连续)
test_data_all = test_data_all.reset_index(drop=True)
# 分离训练集纯特征列(剔除标签,用于训练模型)
train_data_X = train_data_all.drop('矿物类型', axis=1)
# 分离测试集纯特征列(剔除标签,需要填充的目标数据)
test_data_X = test_data_all.drop('矿物类型', axis=1)
# 统计测试集每列缺失值数量(和训练集逻辑一致)
null_num = test_data_X.isnull().sum()
# 按缺失值数量升序排序测试集列名(和训练集填充顺序保持一致)
null_num_sorted = null_num.sort_values(ascending=True)
# 初始化已处理特征列表(和训练集逻辑一致)
filling_feature = []
# 遍历测试集排序后的列名(按缺失值从少到多)
for i in null_num_sorted.index:
# 将当前列加入已处理特征列表
filling_feature.append(i)
# 仅处理测试集有缺失值的列
if null_num_sorted[i] != 0:
# 构建模型训练数据(核心!仅用训练集数据)
# X_train:训练集已处理特征列 - 当前列(自变量)
X_train = train_data_X[filling_feature].drop(i, axis=1)
# Y_train:训练集当前列的完整值(因变量,无缺失)
Y_train = train_data_X[i]
# 构建测试集自变量:测试集已处理特征列 - 当前列(用于定位缺失行)
X_test = test_data_X[filling_feature].drop(i, axis=1)
# 定位测试集当前列缺失值的行索引(需要填充的行)
row_numbers_mg_null = test_data_X[test_data_X[i].isnull()].index.tolist()
# 分离测试集待预测数据:仅保留缺失行的自变量
X_test = X_test.iloc[row_numbers_mg_null]
# 初始化线性回归模型(和训练集一致)
regr = LinearRegression()
# 训练模型:仅用训练集数据拟合(绝对不用测试集数据!避免数据泄露)
regr.fit(X_train, Y_train)
# 预测测试集缺失值:用训练好的模型预测
y_pred = regr.predict(X_test)
# 填充测试集缺失值:将预测值赋值到缺失位置
test_data_X.loc[row_numbers_mg_null, i] = y_pred
# 打印进度:提示测试集当前列填充完成
print('完成测试数据集中的‘{}’列数据的填充'.format(i))
# 返回填充后的测试集特征 + 原标签
return test_data_X, test_data_all.矿物类型
随机森林填充,实现代码如下:
from sklearn.ensemble import RandomForestRegressor
def rf_train_fill(train_data, train_label):
# 拼接训练集特征和标签(合并为完整DataFrame,统一处理索引)
# train_data:训练集特征(DataFrame);train_label:训练集标签(矿物类型,Series)
train_data_all = pd.concat([train_data, train_label], axis=1)
# 重置索引(拼接后可能出现索引重复/不连续,reset_index保证索引从0开始,drop=True删除原索引列)
train_data_all = train_data_all.reset_index(drop=True)
# 分离纯特征列(剔除标签列“矿物类型”,只保留需要填充的特征数据)
train_data_X = train_data_all.drop('矿物类型', axis=1)
# 统计每列缺失值数量(isnull()返回布尔矩阵,sum()按列求和得到每列缺失值个数)
null_num = train_data_X.isnull().sum()
# 按缺失值数量升序排序列名(核心!先填充缺失少的列,后续填充时有更多有效特征)
# 例:列A缺失10个值,列B缺失50个值 → 先填列A,列A填充后可作为列B的特征
null_num_sorted = null_num.sort_values(ascending=True)
# 初始化空列表:记录“已处理(无缺失/已填充)的特征列”,作为后续填充的自变量
filling_feature = []
# 遍历排序后的列名(按缺失值从少到多)
for i in null_num_sorted.index:
# 将当前列加入“已处理特征列表”(无论是否有缺失,都加入,保证后续列能用到)
filling_feature.append(i)
# 仅处理有缺失值的列(无缺失则跳过,节省计算资源)
if null_num_sorted[i] != 0:
# 构建模型自变量X:已处理特征列中剔除当前列(用其他特征预测当前列)
# filling_feature是累积的已处理列,drop(i)排除当前待填充列
X = train_data_X[filling_feature].drop(i, axis=1)
# 构建模型因变量Y:当前列的所有值(含缺失值,后续会分离有值/缺失样本)
Y = train_data_X[i]
# 定位当前列缺失值的行索引(找到需要填充的行,返回索引列表)
# train_data_X[i].isnull() → 标记当前列缺失值位置(True),index.tolist()转为索引列表
row_numbers_mg_null = train_data_X[train_data_X[i].isnull()].index.tolist()
# 分离模型训练数据:剔除缺失行,仅用“有值样本”训练模型
X_train = X.drop(row_numbers_mg_null) # 自变量(无缺失)
Y_train = Y.drop(row_numbers_mg_null) # 因变量(无缺失)
# 分离待预测数据:仅保留缺失行的自变量(用于预测填充值)
X_test = X.iloc[row_numbers_mg_null]
# 初始化随机森林回归模型(核心参数说明)
# n_estimators=100:构建100棵决策树(数量越多越稳定,计算成本越高)
# random_state=42:固定随机种子,保证结果可复现
regr = RandomForestRegressor(n_estimators=100, random_state=42)
# 训练模型:用“有值样本”拟合特征和当前列的关系(捕捉非线性)
regr.fit(X_train, Y_train)
# 预测缺失值:用训练好的模型预测缺失行的取值
Y_pred = regr.predict(X_test)
# 填充缺失值:将预测值精准赋值到当前列的缺失位置
train_data_X.loc[row_numbers_mg_null, i] = Y_pred
# 打印进度:方便调试,确认每列是否填充完成
print('完成训练数据集中的‘{}’列数据的填充'.format(i))
# 返回填充后的训练集特征 + 原标签(标签无缺失,直接返回)
return train_data_X, train_data_all.矿物类型
def rf_test_fill(train_data, train_label, test_data, test_label):
# 拼接训练集特征+标签(构建完整训练集,训练集已填充无缺失,可直接用于建模)
train_data_all = pd.concat([train_data, train_label], axis=1)
# 重置训练集索引(避免索引混乱,保证行索引连续)
train_data_all = train_data_all.reset_index(drop=True)
# 拼接测试集特征+标签(构建完整测试集,方便后续分离特征列)
test_data_all = pd.concat([test_data, test_label], axis=1)
# 重置测试集索引(保证行索引从0开始,避免iloc/loc索引错误)
test_data_all = test_data_all.reset_index(drop=True)
# 分离训练集纯特征列(剔除标签,用于训练模型)
train_data_X = train_data_all.drop('矿物类型', axis=1)
# 分离测试集纯特征列(剔除标签,需要填充的目标数据)
test_data_X = test_data_all.drop('矿物类型', axis=1)
# 统计测试集每列缺失值数量(和训练集逻辑一致)
null_num = test_data_X.isnull().sum()
# 按缺失值数量升序排序测试集列名(和训练集填充顺序保持一致,保证特征累积逻辑相同)
null_num_sorted = null_num.sort_values(ascending=True)
# 初始化已处理特征列表(和训练集逻辑一致)
filling_feature = []
# 遍历测试集排序后的列名(按缺失值从少到多)
for i in null_num_sorted.index:
# 将当前列加入已处理特征列表
filling_feature.append(i)
# 仅处理测试集有缺失值的列
if null_num_sorted[i] != 0:
# 构建模型训练数据(核心!仅用训练集数据,绝对不用测试集)
# X_train:训练集已处理特征列 - 当前列(自变量)
X_train = train_data_X[filling_feature].drop(i, axis=1)
# Y_train:训练集当前列的完整值(因变量,无缺失)
Y_train = train_data_X[i]
# 构建测试集自变量:测试集已处理特征列 - 当前列(用于定位缺失行)
X_test = test_data_X[filling_feature].drop(i, axis=1)
# 定位测试集当前列缺失值的行索引(需要填充的行)
row_numbers_mg_null = test_data_X[test_data_X[i].isnull()].index.tolist()
# 分离测试集待预测数据:仅保留缺失行的自变量
X_test = X_test.iloc[row_numbers_mg_null]
# 初始化随机森林回归模型(参数和训练集一致,保证模型结构相同)
regr = RandomForestRegressor(n_estimators=100, random_state=42)
# 训练模型:仅用训练集数据拟合(核心禁忌:绝对不用测试集数据训练)
regr.fit(X_train, Y_train)
# 预测测试集缺失值:用训练好的模型预测
y_pred = regr.predict(X_test)
# 填充测试集缺失值:将预测值赋值到缺失位置
test_data_X.loc[row_numbers_mg_null, i] = y_pred
# 打印进度:提示测试集当前列填充完成
print('完成测试数据集中的‘{}’列数据的填充'.format(i))
# 返回填充后的测试集特征 + 原标签
return test_data_X, test_data_all.矿物类型
四、样本均衡化:SMOTE 过采样
矿物数据中不同类型样本的采集难度差异大,易出现样本不均衡(如 A 类占 80%,D 类仅占 5%),导致模型偏向多数类,少数类识别精度极低。本文采用 SMOTE(合成少数类过采样技术)解决该问题。
4.1 SMOTE 算法原理
SMOTE 通过在少数类样本的特征空间中插值生成合成样本,而非简单复制,避免模型过拟合。核心步骤:
- 对每个少数类样本,找到其 k 近邻(本文 k=1);
- 在样本与近邻之间随机生成新样本;
- 重复直至各类样本数量均衡。
4.2 代码实现与效果验证
from imblearn.over_sampling import SMOTE
# 1. 选择缺失值填充策略(以删除缺失值为例)
x_train_fill, y_train_fill = cca_train_fill(x_train_w, y_train_w)
x_test_fill, y_test_fill = cca_test_fill(x_train_fill, y_train_fill, x_test_w, y_test_w)
# 2. SMOTE过采样(仅对训练集处理,测试集保持原始分布)
oversampler = SMOTE(k_neighbors=1, random_state=42)
os_x_train, os_y_train = oversampler.fit_resample(x_train_fill, y_train_fill)
# 3. 可视化样本分布(验证均衡效果)
Y_whole = pd.concat([os_y_train, y_test_fill])
label_count = pd.value_counts(Y_whole)
# 绘制柱状图,标注样本数量
fig, ax = plt.subplots(figsize=(8, 6))
bars = ax.bar(label_count.index, label_count.values)
for bar in bars:
yval = bar.get_height()
ax.text(bar.get_x() + bar.get_width() / 2, yval,
round(yval, 2), va='bottom', ha='center', fontsize=10)
plt.xlabel('labels')
plt.ylabel('number')
plt.show()
# 4. 生成最终训练/测试集并保存
data_train = pd.concat([os_y_train, os_x_train], axis=1).sample(frac=1, random_state=4)
data_test = pd.concat([y_test_fill, x_test_fill], axis=1)
# 保存为Excel文件,便于后续模型训练
data_train.to_excel('训练集数据[删除空数据行].xlsx', index=False)
data_test.to_excel('测试集数据[删除空数据行].xlsx', index=False)
五、总结
本文以矿物识别项目为案例,完整讲解了矿物数据集预处理的核心流程,包括数据清洗、Z 标准化、6 种缺失值填充策略实现、SMOTE 样本均衡化,最终生成可直接用于模型训练的标准化数据集。核心要点总结:
- 缺失值处理:需根据缺失率、特征类型选择合适的填充策略,缺失率 > 20% 时优先选择机器学习填充(随机森林),保证特征关联信息不丢失;
- 样本均衡化:仅对训练集执行 SMOTE 过采样,测试集保持原始分布,避免模型评估偏差;
- 数据隔离:测试集的统计特征(如均值、中位数)必须基于训练集计算,杜绝数据泄露,保证模型泛化能力。
矿物数据预处理是模型训练的基础,合理的预处理流程可使后续模型的识别精度提升 10%-30%。实际应用中需结合业务场景、数据分布特点选择最优策略,而非盲目套用固定流程。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐


所有评论(0)