矿物成分数据智能分类实战(一):从脏数据到可用数据集的全流程清洗

目录

一、项目背景与环境准备

[1. 业务背景](#1. 业务背景)

[2. 环境依赖](#2. 环境依赖)

[3. 原始数据概览](#3. 原始数据概览)

二、异常值与标签标准化处理

[1. 核心问题](#1. 核心问题)

[2. 处理方案与代码实现](#2. 处理方案与代码实现)

(1)非数值异常值清洗

(2)分类标签数值编码

[三、缺失值深度分析与 6 种工业级填充方案](#三、缺失值深度分析与 6 种工业级填充方案)

[1. 缺失值统计分析](#1. 缺失值统计分析)

[2. 核心设计思路](#2. 核心设计思路)

[3. 六种缺失值填充方案全实现](#3. 六种缺失值填充方案全实现)

填充方案对比总表

(1)核心填充方法代码拆解

[① 类别内均值填充](#① 类别内均值填充)

[② 随机森林预测填充(本项目最优方案)](#② 随机森林预测填充(本项目最优方案))

四、数据标准化与数据集划分

[1. Z-Score 标准化](#1. Z-Score 标准化)

[2. 数据集划分](#2. 数据集划分)

[3. 缺失值填充执行](#3. 缺失值填充执行)

[五、类别不平衡优化(SMOTE 过采样)](#五、类别不平衡优化(SMOTE 过采样))

六、清洗结果落地与输出

七、清洗结果总结


本文基于 1000 + 条矿物检测样本的实战项目,完整拆解工业级表格数据清洗全流程,覆盖异常值处理、多方案缺失值填充、标准化、类别不平衡优化等核心环节,所有代码开箱即用,可直接复用于各类结构化分类任务。

一、项目背景与环境准备

1. 业务背景

我们拿到了一份1044 条矿物检测数据,包含氯、钠、镁、硫等 13 项化学成分特征,目标是实现 A/B/C/D 四类矿物的智能分类。原始数据存在大量非数值异常值、不规则缺失值、类别分布不平衡等问题,直接建模会出现精度失真、模型泛化能力差等问题,因此数据清洗是本项目的核心基石。

正确流程(数据预处理黄金法则)

  1. 读取数据→删除 E 类→处理异常值(转数值)

  2. 切分训练 / 测试集(先切分!避免测试集信息泄露)

  3. 基于训练集填充测试集的缺失值(测试集不能用自身数据填充)

  4. 基于训练集做标准化(测试集仅用训练集的均值 / 方差)

  5. 训练集做 SMOTE 过采样(仅训练集!)

  6. 保存数据

2. 环境依赖

python 复制代码
import pandas as pd
import matplotlib.pyplot as plt
from pathlib import Path
from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import train_test_split
from imblearn.over_sampling import SMOTE
# 缺失值填充自定义模块(下文完整拆解)
import filldata

3. 原始数据概览

原始数据为 Excel 格式,核心结构如下表所示,共 13 个数值型特征、1 个分类标签列,我们首先过滤掉业务中不需要的 E 类矿物样本。

特征列 含义 数据类型 业务说明
氯 / 钠 / 镁 / 硫 / 钙 / 钾 / 碳 / 溴 / 锶 /pH/ 硼 / 氟 / 硒 矿物化学成分含量 数值型 矿物分类核心特征,存在大量非数值异常值
矿物类型 分类标签 字符型 目标值,分为 A/B/C/D 四类,需做数值编码

数据加载与初步过滤代码

python 复制代码
BASE_DIR = Path(__file__).resolve().parent
data_path = BASE_DIR / "矿物数据.xls"
output_dir = BASE_DIR / "temp_data"
output_dir.mkdir(parents=True, exist_ok=True)

# 导入数据并过滤E类矿物
data = pd.read_excel(data_path)
data = data[data['矿物类型'] != 'E']

# 拆分特征与标签
X_whole = data.drop('序号', axis=1).drop('矿物类型', axis=1)
Y_whole = data.矿物类型

二、异常值与标签标准化处理

1. 核心问题

原始数据中存在大量字符串、特殊符号、空格、非法数值等异常数据,直接传入模型会触发类型报错,同时字符型标签无法被机器学习模型识别,需做标准化处理。

2. 处理方案与代码实现

(1)非数值异常值清洗

使用pd.to_numeric将所有特征列强制转换为数值型,无法转换的异常值统一设为NaN,为后续缺失值处理做准备。

python 复制代码
# 数据中存在大量字符串数值、|、空格等异常数据。字符串数值直接转换为float,\和空格转换为nan
for column_name in X_whole.columns:
    X_whole[column_name] = pd.to_numeric(X_whole[column_name], errors='coerce')
# pd.to_numeric()函数尝试将参数中的数据转换为数值类型。如果转换失败,它会引发一个异常,
    # 设置errors='coerce',将会无法转换的值设置为nun
(2)分类标签数值编码

将字符型标签 A/B/C/D 映射为数值 0/1/2/3,符合分类模型的输入要求。

python 复制代码
# 标签数值编码
label_dict = {"A":0, "B":1, "C":2, "D":3}
enecoded_label = [label_dict[label] for label in Y_whole]
Y_whole = pd.Series(enecoded_label,name="矿物类型")

三、缺失值深度分析与 6 种工业级填充方案

1. 缺失值统计分析

首先统计每列的缺失值数量,明确缺失分布:

python 复制代码
# 缺失值统计
null_num = data.isnull()
null_total = null_num.sum()
print("各特征缺失值数量:\n", null_total)

缺失值分布可视化代码

python 复制代码
# 绘制缺失值分布柱状图
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.figure(figsize=(12, 6))
null_total.sort_values(ascending=False).plot(kind='bar', color='#1f77b4')
plt.title('各特征缺失值数量分布', fontsize=14)
plt.xlabel('特征名称', fontsize=12)
plt.ylabel('缺失值数量', fontsize=12)
plt.grid(axis='y', linestyle='--', alpha=0.3)
plt.tight_layout()
plt.show()

2. 核心设计思路

不使用全局填充,而是按矿物类型分组填充:不同矿物的化学成分分布存在本质差异,全局均值 / 中位数填充会引入严重的业务偏差,按标签分组填充更符合矿物检测的业务逻辑。

3. 六种缺失值填充方案全实现

我们实现了从简单统计填充到机器学习预测填充的 6 种方案,覆盖不同业务场景,所有代码均为原项目可运行版本。

填充方案对比总表
填充方案 核心原理 优点 缺点 适用场景
完整案例删除法 删除含缺失值的整行样本 实现简单,不引入虚假数据 丢失大量样本,加剧类别不平衡 缺失率极低 (<5%)、样本量极大的场景
类别内均值填充 按矿物类型分组,用组内均值填充缺失值 实现简单,保留类别分布特征 对异常值敏感,缩小特征方差 数据分布正态、无极端异常值的场景
类别内中位数填充 按矿物类型分组,用组内中位数填充缺失值 抗异常值能力强,鲁棒性高 对离散特征适配性差 存在极端异常值、数据分布偏态的场景
类别内众数填充 按矿物类型分组,用组内众数填充缺失值 适配离散型特征,不改变数据分布 连续特征填充效果差,易出现偏差 离散特征、分类特征的缺失值填充
线性回归预测填充 用无缺失特征构建线性回归模型,预测缺失值 利用特征间相关性,填充精度高 对非线性关系拟合差,易过拟合 特征间线性相关性强的场景
随机森林预测填充 用无缺失特征构建随机森林回归模型,预测缺失值 拟合非线性关系,抗过拟合,填充精度最高 计算成本略高 结构化表格数据的高精度填充需求(本项目首选)
(1)核心填充方法代码拆解

以下为 6 种方案的核心实现,完整代码可参考项目filldata.py,核心逻辑分块如下:

① 类别内均值填充
python 复制代码
"""均值填充"""
def mean_train_method(data):
    fill_value = data.mean()
    return data.fillna(fill_value)

def mean_train_fill(train_data,train_label):
    data = pd.concat([train_data,train_label],axis=1)
    data = data.reset_index(drop=True)
    # 按矿物类型分组填充
    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],axis=0)
    df_filled = df_filled.reset_index(drop=True)
    return df_filled.drop('矿物类型',axis=1),df_filled.矿物类型

# 测试集填充(仅用训练集统计值,避免数据泄露)
def mean_test_method(train_data,test_data):
    fill_value = train_data.mean()
    return test_data.fillna(fill_value)

def mean_test_fill(train_data,train_label,test_data,test_label):
    data_test = pd.concat([test_data,test_label],axis=1)
    data_test = data_test.reset_index(drop=True)
    data_train = pd.concat([train_data,train_label],axis=1)
    data_train = data_train.reset_index(drop=True)
    # 按矿物类型分组,用训练集统计值填充测试集
    A = data_test[data_test['矿物类型'] == 0 ]
    B = data_test[data_test['矿物类型'] == 1 ]
    C = data_test[data_test['矿物类型'] == 2 ]
    D = data_test[data_test['矿物类型'] == 3 ]
    A_t = data_train[data_train['矿物类型'] == 0]
    B_t = data_train[data_train['矿物类型'] == 1]
    C_t = data_train[data_train['矿物类型'] == 2]
    D_t = data_train[data_train['矿物类型'] == 3]
    A = mean_test_method(A_t,A)
    B = mean_test_method(B_t,B)
    C = mean_test_method(C_t,C)
    D = mean_test_method(D_t,D)
    df_filled = pd.concat([A,B,C,D],axis=0)
    df_filled = df_filled.reset_index(drop=True)
    return df_filled.drop('矿物类型',axis=1),df_filled.矿物类型

关键说明:测试集填充仅使用训练集的统计值,严格避免数据泄露,保证模型泛化能力。中位数、众数填充的代码结构与均值填充完全一致,仅替换统计方法。

② 随机森林预测填充(本项目最优方案)
python 复制代码
"""随机森林填充"""
from sklearn.ensemble import RandomForestRegressor

def rf_train_fill(train_data,train_label):
    data = pd.concat([train_data, train_label], axis=1)
    data = data.reset_index(drop=True)
    train_data = data.drop('矿物类型', axis=1)
    # 按缺失值数量从小到大排序,优先填充缺失少的特征
    null_num = train_data.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_data[filling_feature].drop(i, axis=1)
            y = train_data[i]
            row_numbers = train_data[train_data[i].isnull()].index.tolist()
            x_train = X.drop(row_numbers)
            y_train = y.drop(row_numbers)
            x_test = X.iloc[row_numbers]
            
            # 训练随机森林回归模型预测缺失值
            rf = RandomForestRegressor(n_estimators=100, max_depth=20, random_state=0)
            rf.fit(x_train, y_train)
            y_pred = rf.predict(x_test)
            train_data.loc[row_numbers, i] = y_pred
            print("完成训练数据集中'{}'列的数据填充".format(i))
    return train_data, data.矿物类型

# 测试集预测填充(无数据泄露)
def rf_test_fill(train_data,train_label,test_data,test_label):
    data_train = pd.concat([train_data,train_label],axis=1)
    data_train = data_train.reset_index(drop=True)
    train_data = data_train.drop('矿物类型',axis=1)
    data_test = pd.concat([test_data,test_label],axis=1)
    data_test = data_test.reset_index(drop=True)
    test_data = data_test.drop('矿物类型',axis=1)
    
    null_num =  test_data.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_data[filling_feature].drop(i,axis=1)
            y = train_data[i]
            x_test = test_data[filling_feature].drop(i, axis=1)
            row_numbers = test_data[test_data[i].isnull()].index.to_list()
            x_test = x_test.iloc[row_numbers]
            
            # 无特征可训练时,用均值兜底
            if X.shape[1] == 0:
                fill_val = y.mean()
                test_data.loc[row_numbers, i] = fill_val
                print(f"无特征可训练,测试集[{i}]列用均值{fill_val:.2f}填充")
                continue
            
            non_null_idx = y.notna()
            X_train_clean = X[non_null_idx]
            y_train_clean = y[non_null_idx]
            
            # 仅用训练集训练模型,预测测试集缺失值
            rf = RandomForestRegressor(n_estimators=100, max_depth=20, random_state=0)
            rf.fit(X_train_clean,y_train_clean)
            y_pred = rf.predict(x_test)
            test_data.loc[row_numbers,i] = y_pred
            print("完成测试集中'{}'的填充".format(i))
    return test_data,data_test.矿物类型

四、数据标准化与数据集划分

1. Z-Score 标准化

不同化学成分的量纲差异极大(如氯含量最高达 37 万,pH 值仅为个位数),基于距离的模型(SVM、逻辑回归)对量纲高度敏感,因此使用 Z-Score 标准化将数据缩放到均值为 0、方差为 1 的分布。

python 复制代码
'''z标准化'''
from sklearn.preprocessing import StandardScaler

scaler = StandardScaler()
# 训练集:fit+transform(拟合+转换)
x_train_scaled = scaler.fit_transform(x_train_fill)
# 测试集:仅transform(复用训练集的均值/标准差,避免数据泄露)
x_test_scaled = scaler.transform(x_test_fill)

# 转回DataFrame(方便后续保存)
x_train_scaled = pd.DataFrame(x_train_scaled, columns=x_train_fill.columns)
x_test_scaled = pd.DataFrame(x_test_scaled, columns=x_test_fill.columns)

2. 数据集划分

采用 7:3 比例划分训练集与测试集,固定随机种子保证结果可复现,重置索引避免后续填充出现索引错位。

python 复制代码
'''数据集切分'''
from sklearn.model_selection import train_test_split
x_train,x_test,y_train,y_test = \
    train_test_split(X_whole,Y_whole,random_state=7)
x_train = x_train.reset_index(drop=True)
y_train = y_train.reset_index(drop=True)
x_test = x_test.reset_index(drop=True)
y_test = y_test.reset_index(drop=True)

3. 缺失值填充执行

以随机森林填充为例,执行填充流程,其他方案仅需替换函数名即可。

python 复制代码
"""数据填充6种方法"""
#6随机森林填充
x_train_fill,y_train_fill = filldata.rf_train_fill(x_train,y_train)
x_test_fill,y_test_fill = filldata.rf_test_fill(x_train_fill,y_train_fill,x_test,y_test)

五、类别不平衡优化(SMOTE 过采样)

矿物样本存在天然的类别分布不平衡,小类别样本量少会导致模型偏向大类别,因此使用 SMOTE 算法对训练集做过采样,生成合成样本平衡类别分布,测试集不做任何采样处理,保证评估结果真实。

python 复制代码
'''smote拟合(仅训练集!测试集绝不过采样)'''
from imblearn.over_sampling import SMOTE
oversample = SMOTE(k_neighbors=1,random_state=0)
os_x_train,os_y_train = oversample.fit_resample(x_train_scaled,y_train_fill)

六、清洗结果落地与输出

将最终处理好的训练集和测试集保存为 Excel 文件,方便后续模型训练调用,同时打乱训练集顺序避免模型学习到顺序特征。

python 复制代码
'''数据保存为excel'''
# 训练集:合并标签+特征,打乱顺序(提升训练鲁棒性
data_train = pd.concat([os_y_train,os_x_train],axis=1).sample(frac=1,random_state=0)
# 测试集:合并标签+特征,不打乱(方便后续评估)
data_test = pd.concat([y_test_fill, x_test_scaled], axis=1) #测试集不用传入模型训练,无须打乱顺序

data_train.to_excel(output_dir / '训练数据集[随机森林填充].xlsx',index=False)
data_test.to_excel(output_dir / '测试数据集[随机森林填充].xlsx',index=False)

七、清洗结果总结

经过上述全流程处理,我们得到了无缺失值、无异常值、无量纲差异、类别平衡的高质量数据集:

  1. 所有特征列无缺失值,填充过程严格避免数据泄露;
  2. 异常值全部完成标准化处理,符合模型输入要求;
  3. 特征完成标准化,消除量纲影响;
  4. 训练集类别完全平衡,解决了样本分布不均的问题。
相关推荐
爱吃烤鸡翅的酸菜鱼1 小时前
从抽象设计到落地实践:openJiuwen可插拔会话存储机制深度解析
人工智能·redis·ai·agent
falldeep1 小时前
LLM中的强化学习方法分类
开发语言·人工智能·机器学习
志栋智能1 小时前
安全超自动化的四大支柱:检测、分析、响应、恢复
运维·网络·人工智能·安全·web安全·自动化
Gavin_Huangw1 小时前
计算机会议分类
人工智能
量子-Alex1 小时前
【大模型RAG】Retrieval-Augmented Generation for Knowledge-Intensive NLP Tasks
人工智能·自然语言处理
yumgpkpm2 小时前
华为昇腾910B 开源软件GPUStack的介绍(Cloudera CDH、CDP)
人工智能·hadoop·elasticsearch·flink·kafka·企业微信·big data
Elastic 中国社区官方博客2 小时前
AI agent 记忆:使用 Elasticsearch 托管记忆创建智能代理
大数据·人工智能·elasticsearch·搜索引擎·ai·云原生·全文检索
腾视科技TENSORTEC2 小时前
私有云时代来临:AI NAS如何重塑你的数字生活?
大数据·人工智能·科技·ai·生活·ainas
黎阳之光2 小时前
黎阳之光:AI硬核技术锚定十五五,赋能海空天全域智能感知
大数据·人工智能·算法·安全·数字孪生