在地质勘探与矿物分析领域,基于矿物成分数据实现自动化分类是提升分析效率的关键。本文将完整复盘一个矿物类型分类项目,涵盖数据清洗、缺失值填充、样本均衡处理,并对比 6 种传统机器学习模型与 2 种深度学习模型的分类效果,为类似数据驱动的分类任务提供可复用的解决方案。
一、项目背景与数据概况
1. 项目目标
基于矿物的化学成分特征(如镁、铝、硅等元素含量),将矿物分为 A、B、C、D 四类(已剔除特殊类别 E),通过对比不同数据预处理方法与模型,找到最优分类方案。
2. 数据特点
- 原始数据存储于
矿物数据.xls,包含 “序号”“矿物类型” 及 7 个化学成分特征列; - 存在三类数据问题:字符串格式数值(如 "12.3")、特殊符号(如 "")、空格与缺失值(NaN);
- 样本存在类别不均衡问题,后续需通过 SMOTE 算法处理;
- 部分特征列缺失率较高,需针对性选择缺失值填充方法。
二、完整数据预处理流程
数据预处理直接影响模型效果,本项目采用 “清洗→填充→标准化→均衡” 四步流程,确保数据质量。
1. 数据清洗:统一格式与剔除异常
首先处理格式混乱问题,将非数值数据转为 NaN,便于后续填充:
import pandas as pd
# 读取数据并剔除类别E(仅1条,无统计意义)
data = pd.read_excel("矿物数据.xls")
data = data[data['矿物类型'] != 'E']
# 提取特征与标签(删除序号列,避免干扰模型训练)
X_whole = data.drop('矿物类型', axis=1).drop('序号', axis=1)
y_whole = data['矿物类型']
# 处理异常格式:字符串数值/特殊符号→NaN(仅保留可转为float的值)
for col in X_whole.columns:
X_whole[col] = pd.to_numeric(X_whole[col], errors='coerce')
# 标签编码(将文字标签转为数字,适配模型输入:A→0, B→1, C→2, D→3)
label_dict = {"A": 0, "B": 1, "C": 2, "D": 3}
y_whole = pd.Series([label_dict[label] for label in y_whole], name='矿物类型')
2. 缺失值填充:6 种方法完整代码实现
缺失值是本数据的核心问题之一,我们设计了传统统计方法与机器学习方法两类共 6 种填充策略,并封装为可直接调用的函数,适配训练集与测试集(测试集需用训练集参数填充,避免数据泄露)。
2.1 工具函数封装:fill_data.py 完整代码
import pandas as pd
from sklearn.linear_model import LinearRegression
from sklearn.ensemble import RandomForestRegressor
"""
1. CCA(Complete Case Analysis):直接删除含缺失值的行
适用场景:缺失率极低(<5%),数据量充足时使用
"""
def cca_train_fill(train_data, train_label):
# 合并特征与标签,便于统一删除含缺失值的行
data = pd.concat([train_data, train_label], axis=1).reset_index(drop=True)
# 删除所有含NaN的行
df_filled = data.dropna()
# 拆分回特征与标签
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).reset_index(drop=True)
df_filled = data.dropna()
return df_filled.drop('矿物类型', axis=1), df_filled['矿物类型']
"""
2. 平均值填充:按矿物类别计算均值,填充同类缺失值
适用场景:特征分布近似正态,无极端异常值
"""
def _mean_fill_method(data):
# 计算每列均值(按类别分组后内部计算)
fill_values = data.mean()
return data.fillna(fill_values)
def mean_train_fill(train_data, train_label):
# 合并数据并按类别拆分(A/B/C/D四类分别填充)
data = pd.concat([train_data, train_label], axis=1).reset_index(drop=True)
class_A = data[data['矿物类型'] == 0]
class_B = data[data['矿物类型'] == 1]
class_C = data[data['矿物类型'] == 2]
class_D = data[data['矿物类型'] == 3]
# 按类别填充后合并
filled_A = _mean_fill_method(class_A)
filled_B = _mean_fill_method(class_B)
filled_C = _mean_fill_method(class_C)
filled_D = _mean_fill_method(class_D)
df_filled = pd.concat([filled_A, filled_B, filled_C, filled_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_all = pd.concat([train_data, train_label], axis=1).reset_index(drop=True)
train_A = train_all[train_all['矿物类型'] == 0]
train_B = train_all[train_all['矿物类型'] == 1]
train_C = train_all[train_all['矿物类型'] == 2]
train_D = train_all[train_all['矿物类型'] == 3]
# 测试集:按类别拆分,用训练集同类均值填充
test_all = pd.concat([test_data, test_label], axis=1).reset_index(drop=True)
test_A = test_all[test_all['矿物类型'] == 0].fillna(train_A.mean())
test_B = test_all[test_all['矿物类型'] == 1].fillna(train_B.mean())
test_C = test_all[test_all['矿物类型'] == 2].fillna(train_C.mean())
test_D = test_all[test_all['矿物类型'] == 3].fillna(train_D.mean())
df_filled = pd.concat([test_A, test_B, test_C, test_D]).reset_index(drop


1126

被折叠的 条评论
为什么被折叠?



