矿物分类数据处理:缺失值填充方法详解

前言

在矿物分类任务中,原始数据往往存在缺失值、异常字符等问题。本文记录一次完整的数据预处理流程,重点讲解缺失值填充的两种基础方法:完整行删除(CCA)和均值填充(按类别)。后续文章会继续介绍中位数、众数填充以及更高级的模型填充方法(线性回归、随机森林)。

代码使用 Python 的 pandas 和 scikit-learn,保持原样,仅最后保存 CSV 的文件名可自行调整。

一、数据准备与基础处理

1.先导入必要的库

python 复制代码
import pandas as pd
import matplotlib.pyplot as plt
import fill_data

2.读取数据并做简单清洗

python 复制代码
data = pd.read_excel("矿物数据.xls")
data = data[data['矿物类型'] != 'E']   # 删除仅1条的类别E

3.检查缺失值

data.isnull() 返回一张布尔表格

  • 缺失值位置 → True
  • 有数据位置 → False

sum()默认按列求和True=1False=0

python 复制代码
#统计每列空缺值数量
null_num = data.isnull()
null_total = null_num.sum() 

4.提取特征和标签,并将标签编码为数字

python 复制代码
X_whole = data.drop('矿物类型', axis=1).drop('序号', axis=1)
y_whole = data.矿物类型

label_dict = {'A':0,'B':1,'C':2,'D':3}
encoded_labels = [label_dict[label] for label in y_whole]
y_whole = pd.Series(encoded_labels, name='矿物类型')

5.将特征列中的非数值内容(如 `|`、空格、字符串数字)转为数值,无法转换的置为 NaN

pd.to_numeric()函数将参数中的数据转换为数值类型。如果转换失败,它会引发一个异常,

设置errors="coerce",会将无法转换的值设置为NaN

python 复制代码
for column_name in X_whole.columns:
    X_whole[column_name] = pd.to_numeric(X_whole[column_name], errors='coerce')

6.标准化(Z-score)使特征均值为0、方差为1

Z标准化处理后为umpy数据,这里再转换成pondas数据

python 复制代码
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
X_whole_Z = scaler.fit_transform(X_whole)
X_whole = pd.DataFrame(X_whole_Z, columns=X_whole.columns)

7.划分训练集和测试集(70%训练,30%测试)

python 复制代码
from sklearn.model_selection import train_test_split
x_train, x_test, y_train, y_test = train_test_split(X_whole, y_whole, test_size=0.3, random_state=10)

二、缺失值填充的思路

数据中存在 NaN 时,直接送入模型会报错或导致结果偏差。填充缺失值的目标是:用合理的估计值代替缺失位置,尽量保留数据信息,同时避免引入过大噪声。

常见的填充策略有:

  • 删除法:直接删除含有缺失值的行或列(如 CCA)。

  • 统计量填充:用均值、中位数、众数等代替。

  • 模型预测填充:用其他特征建立模型预测缺失值(如回归、随机森林)。

对于分类问题,若不同类别的数据分布差异较大,按类别分别计算统计量往往比全局统计量更合理。例如,矿物类型 A 的某特征均值可能远高于类型 B,若用全局均值填充所有缺失值,会模糊类别间的差异。因此,本文实现的均值填充是按矿物类型分组进行的。

三、完整行分析(CCA)

思路

CCA 是最简单的处理方式:只保留没有任何缺失值的样本。这样做的好处是无需估计,完全保留原始信息;缺点是当缺失比例较高时,会丢弃大量数据,可能导致模型训练不充分。

代码实现

fill_data模块中定义了训练集和测试集的 CCA 填充函数:

python 复制代码
def cca_train_fill(train_data, train_label):
    '''CCA(Complete Case Analysis)只考虑包含完整数据的行'''
    data = pd.concat([train_data, train_label], axis=1)
    #用于重囂索引的。当你对数据进行工排序、筛选或其他操作后案引可能会变得不连续或混乱。
    data = data.reset_index(drop=True)
    #用于删除(或过滤掉)包含缺失值(NaN)的行或列。pandas里面有大量和数据清洗相关的函数
    df_filled = data.dropna()
    return df_filled.drop('矿物类型', axis=1), df_filled.矿物类型


def cca_test_fill(train_data, train_label, test_data, test_label):
    '''CCA测试集填充:直接删除含缺失值的行(与训练集无关)'''
    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.矿物类型

注意:训练集和测试集的删除是独立的,即分别删除各自含缺失值的行。这样做可能导致训练集和测试集样本数不同,但各自内部都是完整数据,可以直接用于模型。

适用场景

  • 缺失值很少(例如 <5%),删除后不影响样本量。

  • 数据量本身很大,即使删除一部分也足够训练。

四、均值填充(按类别)

思路

对于每个特征,计算同一矿物类型下该特征的非缺失值的平均值,然后用该平均值填充该类别样本的缺失位置。这样既利用了类别信息,又避免了全局均值可能带来的偏差。

例如:对于矿物类型 A,若特征"硬度"有缺失,则用所有 A 类样本"硬度"的平均值填充;类型 B 的缺失则用 B 类样本的平均值填充,以此类推。

代码实现

首先定义一个辅助函数 'mean_method',对单个 DataFrame 用每列均值填充:

python 复制代码
def mean_method(train_data):
    '''数据集中的空值使用每列的均值替代'''
    fill_values = train_data.mean()
    return train_data.fillna(fill_values)

然后分别处理训练集和测试集。训练集:按类别拆分,每个子集独立填充,再合并:

python 复制代码
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_method(A)
    B = mean_method(B)
    C = mean_method(C)
    D = mean_method(D)

    df_filled = pd.concat([A, B, C, D])
    df_filled = df_filled.reset_index(drop=True)
    return df_filled.drop('矿物类型', axis=1), df_filled.矿物类型

测试集:为了避免数据泄露,必须使用训练集计算出的均值来填充测试集,而不是用测试集自身的均值。因此'mean_test_fill'函数的设计如下:

python 复制代码
def mean_test_method(train_data, test_data):
    '''用训练集的均值填充测试集'''
    fill_values = train_data.mean()
    return test_data.fillna(fill_values)


def mean_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)

    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 = mean_test_method(A_train, A_test)
    B = mean_test_method(B_train, B_test)
    C = mean_test_method(C_train, C_test)
    D = mean_test_method(D_train, D_test)

    df_filled = pd.concat([A, B, C, D])
    df_filled = df_filled.reset_index(drop=True)
    return df_filled.drop('矿物类型', axis=1), df_filled.矿物类型

优点与局限

优点:

  • 简单快速,容易实现。

  • 保留了样本数量,不会丢失数据。

  • 按类别填充更符合数据分布。

局限:

  • 均值对异常值敏感,若某类别中存在极端值,均值可能偏离真实中心。

  • 忽略了特征之间的相关性,填充值可能不够精确。

五、中位数、众数填充

中位数填充与均值填充思路完全一致,只需将 'mean()' 替换为 'median()';众数填充则使用 'mode()'并取第一个众数值。

python 复制代码
def mode_test_method(train_data, test_data):
    '''数据集中的空值使用每列的众数替代'''
    fill_values =train_data.apply(lambda x: x.mode().iloc[0] if len(x.mode()) > 0 else None)
    return test_data.fillna(fill_values)  # 使用众数填充缺失值

六、线性回归填充

思路

线性回归假设特征与目标之间存在线性关系。在填充某个特征时,我们把该特征作为目标变量 y,其他已处理过的完整特征作为输入 X。用非缺失行训练模型,然后预测缺失行的目标值。由于我们按缺失数量从小到大依次处理,所以当处理到某个特征时,前面处理过的特征都已经填充完整,可以作为输入特征使用,这就是迭代填充的核心。

训练集代码

python 复制代码
def lr_train_fill(train_data, train_label):
    # 合并特征和标签
    train_data_all = pd.concat([train_data, train_label], axis=1)
    train_data_all = train_data_all.reset_index(drop=True)
    train_data_X = train_data_all.drop('矿物类型', axis=1)

    # 统计每个特征的缺失值数量,并排序
    null_num = train_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:        # 如果该特征有缺失,才进行填充
            # 用 filling_feature 中除当前特征外的所有特征作为输入
            X = train_data_X[filling_feature].drop(i, axis=1)
            y = train_data_X[i]            # 当前特征作为目标

            # 找到缺失行索引
            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()
            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.矿物类型

关键点

  • filling_feature 列表会依次加入所有特征名。当处理第一个有缺失的特征时,列表中已经包含了所有缺失数量比它少(包括无缺失)的特征,这些特征都是完整的,可以作为输入。处理第二个有缺失的特征时,第一个缺失特征已经被填充,也变成了完整特征,所以也能作为输入。这样实现了迭代填充

  • 输入特征中不包含当前待填充的特征,避免自我预测。

  • 训练集和测试集的划分基于当前特征是否缺失,而不是随机划分。

测试集代码

python 复制代码
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 = train_data_X[filling_feature].drop(i, axis=1)
            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.矿物类型

注意 :这里训练模型时使用的 X_train 来自已经填充好的训练集,而不是测试集本身,保证了填充的客观性。

七、随机森林填充

思路

随机森林是一种集成学习算法,由多棵决策树组成,能够捕捉特征间的非线性关系,对异常值和噪声更稳健。其填充思路与线性回归完全一致:同样采用迭代填充,按缺失数量从小到大依次处理每个特征,用其他完整特征训练随机森林回归模型,预测当前特征的缺失值。区别仅在于回归器的选择------用 RandomForestRegressor 替代 LinearRegression

python 复制代码
def rf_train_fill(train_data, train_label):
    # 与 lr_train_fill 几乎相同,仅模型不同
    ...
    regr = RandomForestRegressor(n_estimators=100, random_state=42)
    regr.fit(X_train, y_train)
    y_pred = regr.predict(X_test)
    ...

测试集填充同样如此。这里不再重复贴出全部代码,但可以在实际项目中参考上面的线性回归版本,将 LinearRegression() 替换为 RandomForestRegressor(n_estimators=100, random_state=42) 即可。

八、调用函数

python 复制代码
#1.完整行数据
cca_x_train,cca_y_train=fill_data.cca_train_fill(x_train,y_train)
cca_x_test,cca_y_test=fill_data.cca_test_fill(x_train,y_train,x_test,y_test)

注意:后面的改一下即可,但是最后一个随机森林不行,他要借助训练集填充过的数据集进行测试集的训练

python 复制代码
#6.随机森林
rf_x_train,rf_y_train=fill_data.rf_train_fill(x_train,y_train)
rf_x_test,rf_y_test=fill_data.rf_test_fill(rf_x_train,rf_y_train,x_test,y_test)

九、保存csv文件

均值填充后的数据保存以下代码展示了均值填充完成后,将训练集和测试集合并并导出为 CSV 文件

python 复制代码
# 训练集:特征 + 标签横向合并
df_train = pd.concat([cca_x_train, cca_y_train], axis=1)
# 测试集:特征 + 标签横向合并
df_test  = pd.concat([cca_x_test, cca_y_test],  axis=1)

# 保存csv,utf-8-sig防止Excel中文乱码,index=False不输出行号
df_train.to_csv("data/训练数据集_均值填充.csv", index=False, encoding="utf-8-sig")
df_test.to_csv("data/测试数据集_均值填充.csv", index=False, encoding="utf-8-sig")

文件名可以根据需要修改,例如 "data/train_rf_filled.csv"。

相关推荐
晚风醉蝶15 分钟前
1-18-基数排序-RadixSort
python·算法·排序算法·基数排序
今天AI了吗25 分钟前
从“金鱼脑”到“大象记忆”:AI Agent 短期记忆与长期记忆的存储与检索全解
数据库·人工智能·python·sql·rust
whcyhhh42 分钟前
头歌实践教学平台:数据科学与大数据技术导论(十八4)
大数据·开发语言·python
编码者卢布42 分钟前
【Azure Developer】通过 API 获取 Azure VM 信息实践指南 Part 2(Azure China)
python·flask·azure
65岁退休Coder1 小时前
LangGraph v1.2.9 核心概念 & 流程控制
后端·python·langchain
IPdodo_1 小时前
2026年AI 数据采集代理 IP 选型:成功率、并发、轮换与成本评估
前端·网络·人工智能·chrome·python·http·网络调试
啥都想学点的研究生1 小时前
一篇文章讲清楚:超参数的选择方法——交叉验证和网格搜索
人工智能·深度学习·机器学习
z落落1 小时前
C# Modbus-RTU 串口读写设备+CRC16+邮件告警
开发语言·c#
DS随心转小程序1 小时前
Gemini只能导出了一部分Word文档,很少的一部分,不是我这个窗口所有的对话内容?
开发语言·人工智能·c#·word·豆包·deepseek·ai导出鸭