机器学习项目分享之实现智能的矿物识别系统(一)
目录
简介
一、数据清洗
二、项目分析
三、数据集处理
1.空值删除
2.平均值填充
3.中位数填充
4.众数填充
5.线性回归算法填充
6.随机森林算法填充
四、保存新数据集
1. 导入必要的库
2. 数据读取与初步筛选
3. 空值检查
4. 特征与标签分离
5. 标签编码
6.数据类型转换
7. 数据标准化
8. 数据集拆分
9. 空值填充(核心部分)
10. 处理类别不平衡
11. 数据整理与保存
总结
简介
我们已经学习了机器学习的几个经典算法了,今天我们就用一个项目分享来总体把我们学习的知识点运用到项目上。我会详细讲解对原始数据的初步探索与预处理过程:,如何处理异常值和缺失值以保证数据质量,又如何对特征进行标准化或归一化,为后续的模型训练扫清障碍。这一步看似基础,却是决定模型性能的关键前提。然后后续还会使用多种模型进行训练最终比较那个模型更好。
一、数据清洗
在讲项目前我们要了解一下什么是数据清洗。为什么要数据清洗?
数据清洗指发现并纠正数据文件中可识别的错误的最后一道程序,包括检查数据一致性,处理无效值和缺失值等。换句话说,数据清洗的目的是删除重复信息、纠正存在的错误,并提供数据一致性。在进行数据清洗时,需要按照一定的规则把 “脏数据”“洗掉”,以确保数据的准确性和可靠性。
数据清洗通常涉及以下步骤:
- 完整性:检查单条数据是否存在空值,统计的字段是否完善。
- 全面性:观察某一列的全部数值,可以通过比较最大值、最小值、平均值、数据定义等来判断数据是否全面。
- 合法性:检查数值的类型、内容、大小是否符合预设的规则。例如,人类的年龄超过 1000 岁这个数据就是不合法的。
- 唯一性:检查数据是否重复记录,例如一个人的数据被重复记录多次。
- 类别是否可靠。
数据是我们模型训练的基础,数据的质量直接影响我们模型的效果,使用对于数据的处理是我们重中之重。
二、项目分析
我们要对这个项目进行分析,分析一下我们每一步要干什么去实现哪些功能。下面简单做一个说明
任务:实现智能的矿物识别系统
一、搞定数据集
1、填充数据:2 种方法:
a、根据对应类型填充:平均值、众数、中位数。
b、算法进行填充。如何实现???
利用已有的数据来进行训练???分类还是回归??
二、训练模型
1、使用多种模型训练,lr、RF、svm、xgboost… 每种模型都需要调参。
2、对比每种模型的效果
三、数据集处理
我们发现数据集有很对空白的缺失值,我们需要填充、还有一些标点多打点现象,数据框内有许多空格的问题,对于点多打点现象比较少我们可以人为的给处理一下,其他空缺值我将介绍6中方法进行填充。我们专门创建一个代码定义各种填充数据的函数,这样我们想使用那个就可以直接调用。注意:我们在处理测试集缺失数据填充的时候需要用到训练集的数据
1.空值删除
我们可以不要哪些有空值的行,选取哪些完整的数据当做数据集进行训练集与测试集的划分。
完整代码
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()return df_filled.drop("矿物类型", axis=1), df_filled.矿物类型def cca_test_fill(train_data, train_label):data = pd.concat([train_data, train_label], axis=1)data = data.reset_index(drop=True)df_filled = data.dropna()
代码详解
第一个函数:cca_train_fill (train_data, train_label)
这个函数用于处理训练数据集的缺失值
数据合并:
data = pd.concat([train_data,train_label],axis=1)
使用pd.concat
将训练特征数据 (train_data
) 和训练标签 (train_label
) 按列 (axis=1) 合并成一个完整的 DataFrame
重置索引:
data = data.reset_index(drop=True)
删除缺失值:
df_filled = data.dropna()
使用dropna()
删除所有包含缺失值 (NaN) 的行,得到一个没有缺失值的数据集
返回处理结果:
return df_filled.drop("矿物类型", axis=1), df_filled.矿物类型
将处理后的数据集重新拆分为特征数据和标签:
- 特征数据:删除 "矿物类型" 列后的所有数据
- 标签数据:仅保留 "矿物类型" 列的数据
第二个函数:cca_test_fill (train_data, train_label)
用于处理测试数据集的缺失值,逻辑与上面一样
2.平均值填充
import pandas as pd # 假设已导入pandas库def mean_method(data):"""基础均值填充函数:用每列的均值填充该列的缺失值参数:data: pandas.DataFrame,需要填充缺失值的数据集返回:pandas.DataFrame,填充完成后的数据集"""# 计算数据集中每一列的均值,返回一个包含各列均值的Seriesfill_values = data.mean()# 使用计算得到的均值填充数据中的缺失值(NaN)return data.fillna(fill_values)def mean_train_fill(train_data, train_label):"""训练集缺失值填充函数:按"矿物类型"分组,用每组的均值填充组内缺失值参数:train_data: pandas.DataFrame,训练集特征数据train_label: pandas.DataFrame/Series,训练集标签数据(包含"矿物类型"列)返回:tuple: 填充后的训练特征数据和训练标签数据- 第一个元素:删除"矿物类型"列后的特征数据- 第二个元素:仅包含"矿物类型"的标签数据"""# 1. 合并特征数据和标签数据为完整数据集(按列合并)data = pd.concat([train_data, train_label], axis=1)# 重置索引,避免合并后索引混乱(drop=True表示不保留原索引)data = data.reset_index(drop=True)# 2. 按"矿物类型"进行分组(假设矿物类型分为0、1、2、3四类)A = data[data['矿物类型'] == 0] # 筛选出矿物类型为0的所有样本B = data[data['矿物类型'] == 1] # 筛选出矿物类型为1的所有样本C = data[data['矿物类型'] == 2] # 筛选出矿物类型为2的所有样本D = data[data['矿物类型'] == 3] # 筛选出矿物类型为3的所有样本# 3. 对每个分组单独进行均值填充(使用组内均值填充组内缺失值)A = mean_method(A)B = mean_method(B)C = mean_method(C)D = mean_method(D)# 4. 合并所有填充后的分组数据df_filled = pd.concat([A, B, C, D])# 再次重置索引,确保索引连续有序df_filled = df_filled.reset_index(drop=True)# 5. 拆分特征和标签并返回# 特征数据:删除"矿物类型"列# 标签数据:保留"矿物类型"列return df_filled.drop("矿物类型", axis=1), df_filled["矿物类型"]def mean_test_method(train_data, test_data):"""测试集填充辅助函数:用训练集的均值填充测试集的缺失值(避免数据泄露)参数:train_data: pandas.DataFrame,训练集的某个分组数据(用于计算填充均值)test_data: pandas.DataFrame,测试集的对应分组数据(需要被填充)返回:pandas.DataFrame,填充完成后的测试集分组数据"""# 计算训练集分组的均值(关键:不使用测试集自身数据计算)fill_value = train_data.mean()# 用训练集的均值填充测试集的缺失值return test_data.fillna(fill_value)def mean_test_fill(train_data, train_label, test_data, test_label):"""测试集缺失值填充函数:按"矿物类型"分组,用训练集对应组的均值填充测试集参数:train_data: pandas.DataFrame,训练集特征数据train_label: pandas.DataFrame/Series,训练集标签数据test_data: pandas.DataFrame,测试集特征数据test_label: pandas.DataFrame/Series,测试集标签数据返回:tuple: 填充后的测试特征数据和测试标签数据- 第一个元素:删除"矿物类型"列后的特征数据- 第二个元素:仅包含"矿物类型"的标签数据"""# 1. 合并训练集和测试集的特征与标签(分别形成完整数据集)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) # 重置测试集索引# 2. 按"矿物类型"对训练集和测试集分别分组# 训练集分组(用于计算各类型的填充均值)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]# 3. 用训练集对应分组的均值填充测试集分组(核心:保持数据分布一致性)A = mean_test_method(A_train, A_test) # 测试集类型0 → 用训练集类型0的均值填充B = mean_test_method(B_train, B_test) # 测试集类型1 → 用训练集类型1的均值填充C = mean_test_method(C_train, C_test)D = mean_test_method(D_train, D_test)# 4. 合并所有填充后的测试集分组df_filled = pd.concat([A, B, C, D])# 重置索引,确保索引连续有序df_filled = df_filled.reset_index(drop=True)# 5. 拆分特征和标签并返回return df_filled.drop("矿物类型", axis=1), df_filled["矿物类型"]
3.中位数填充
def median_method(data):"""基础中位数填充函数:计算每列的中位数并填充该列缺失值参数:data: pandas.DataFrame,包含缺失值的数据集返回:pandas.DataFrame,用中位数填充后的数据集"""# 计算数据集中每一列的中位数(中位数对异常值更稳健)fill_values = data.median()# 使用计算得到的中位数填充数据中的缺失值(NaN)return data.fillna(fill_values)def median_train_fill(train_data, train_label):"""训练集中位数填充函数:按"矿物类型"分组,用每组的中位数填充组内缺失值参数:train_data: pandas.DataFrame,训练集的特征数据train_label: pandas.DataFrame/Series,训练集的标签数据(含"矿物类型"列)返回:tuple: 填充后的特征数据和标签数据- 第一个元素:删除"矿物类型"列的特征数据- 第二个元素:仅包含"矿物类型"的标签数据"""# 1. 合并特征数据和标签数据为完整数据集(按列拼接)data = pd.concat([train_data, train_label], axis=1)# 重置索引,避免合并后索引重复或混乱(drop=True不保留原索引)data = data.reset_index(drop=True)# 2. 按"矿物类型"分组(假设类型为0、1、2、3四类)A = data[data['矿物类型'] == 0] # 筛选矿物类型为0的样本组B = data[data['矿物类型'] == 1] # 筛选矿物类型为1的样本组C = data[data['矿物类型'] == 2] # 筛选矿物类型为2的样本组D = data[data['矿物类型'] == 3] # 筛选矿物类型为3的样本组# 3. 对每个分组使用组内中位数填充缺失值A = median_method(A)B = median_method(B)C = median_method(C)D = median_method(D)# 4. 合并所有填充后的分组数据df_filled = pd.concat([A, B, C, D])# 重置索引,确保索引连续有序df_filled = df_filled.reset_index(drop=True)# 5. 拆分并返回填充后的特征和标签return df_filled.drop("矿物类型", axis=1), df_filled["矿物类型"]def median_test_method(train_data, test_data):"""测试集填充辅助函数:用训练集分组的中位数填充测试集对应分组的缺失值参数:train_data: pandas.DataFrame,训练集的某个分组数据(用于计算中位数)test_data: pandas.DataFrame,测试集的对应分组数据(需要填充缺失值)返回:pandas.DataFrame,填充后的测试集分组数据"""# 计算训练集分组的中位数(关键:不使用测试集自身数据,避免数据泄露)fill_values = train_data.median() # 注意:原代码此处漏写了括号(),已修正# 用训练集的中位数填充测试集的缺失值return test_data.fillna(fill_values)def median_test_fill(train_data, train_label, test_data, test_label):"""测试集中位数填充函数:按"矿物类型"分组,用训练集对应组的中位数填充参数:train_data: pandas.DataFrame,训练集特征数据train_label: pandas.DataFrame/Series,训练集标签数据test_data: pandas.DataFrame,测试集特征数据test_label: pandas.DataFrame/Series,测试集标签数据返回:tuple: 填充后的测试特征数据和标签数据- 第一个元素:删除"矿物类型"列的特征数据- 第二个元素:仅包含"矿物类型"的标签数据"""# 1. 分别合并训练集和测试集的特征与标签,形成完整数据集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) # 重置测试集索引# 2. 按"矿物类型"对训练集和测试集进行分组匹配# 训练集分组(用于计算各类型的中位数)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]# 3. 用训练集对应分组的中位数填充测试集分组A = median_test_method(A_train, A_test) # 测试类型0 → 训练类型0的中位数B = median_test_method(B_train, B_test) # 测试类型1 → 训练类型1的中位数C = median_test_method(C_train, C_test)D = median_test_method(D_train, D_test)# 4. 合并所有填充后的测试集分组df_filled = pd.concat([A, B, C, D])# 重置索引,确保数据连续性df_filled = df_filled.reset_index(drop=True)# 5. 拆分并返回填充后的特征和标签return df_filled.drop("矿物类型", axis=1), df_filled["矿物类型"]
4.众数填充
def mode_method(data):"""基础众数填充函数:计算每列的众数并填充该列缺失值参数:data: pandas.DataFrame,包含缺失值的数据集返回:pandas.DataFrame,用众数填充后的数据集"""# 计算每列的众数:# - 对每列应用lambda函数,先计算众数(x.mode())# - 若存在众数(len(x.mode())>0),取第一个众数(x.mode().iloc[0])# - 若不存在众数(如所有值都不同),则用None填充fill_values = data.apply(lambda x: x.mode().iloc[0] if len(x.mode()) > 0 else None)# 调试用:查看计算的众数(实际使用时可删除)a = data.mode()# 用计算得到的众数填充数据中的缺失值(NaN)return data.fillna(fill_values)def mode_train_fill(train_data, train_label):"""训练集众数填充函数:按"矿物类型"分组,用每组的众数填充组内缺失值参数:train_data: pandas.DataFrame,训练集的特征数据train_label: pandas.DataFrame/Series,训练集的标签数据(含"矿物类型"列)返回:tuple: 填充后的特征数据和标签数据- 第一个元素:删除"矿物类型"列的特征数据- 第二个元素:仅包含"矿物类型"的标签数据"""# 1. 合并特征数据和标签数据为完整数据集(按列拼接)data = pd.concat([train_data, train_label], axis=1)# 重置索引,避免合并后索引重复或混乱(drop=True不保留原索引)data = data.reset_index(drop=True)# 2. 按"矿物类型"分组(假设类型为0、1、2、3四类)A = data[data['矿物类型'] == 0] # 筛选矿物类型为0的样本组B = data[data['矿物类型'] == 1] # 筛选矿物类型为1的样本组C = data[data['矿物类型'] == 2] # 筛选矿物类型为2的样本组D = data[data['矿物类型'] == 3] # 筛选矿物类型为3的样本组# 3. 对每个分组使用组内众数填充缺失值A = mode_method(A)B = mode_method(B)C = mode_method(C)D = mode_method(D)# 4. 合并所有填充后的分组数据df_filled = pd.concat([A, B, C, D])# 重置索引,确保索引连续有序df_filled = df_filled.reset_index(drop=True)# 5. 拆分并返回填充后的特征和标签return df_filled.drop("矿物类型", axis=1), df_filled["矿物类型"]def mode_test_method(train_data, test_data):"""测试集填充辅助函数:用训练集分组的众数填充测试集对应分组的缺失值参数:train_data: pandas.DataFrame,训练集的某个分组数据(用于计算众数)test_data: pandas.DataFrame,测试集的对应分组数据(需要填充缺失值)返回:pandas.DataFrame,填充后的测试集分组数据"""# 计算训练集分组的众数(与mode_method逻辑一致)# 关键:使用训练集数据计算,避免测试集数据泄露fill_values = train_data.apply(lambda x: x.mode().iloc[0] if len(x.mode()) > 0 else None)# 用训练集的众数填充测试集的缺失值return test_data.fillna(fill_values)def mode_test_fill(train_data, train_label, test_data, test_label):"""测试集众数填充函数:按"矿物类型"分组,用训练集对应组的众数填充参数:train_data: pandas.DataFrame,训练集特征数据train_label: pandas.DataFrame/Series,训练集标签数据test_data: pandas.DataFrame,测试集特征数据test_label: pandas.DataFrame/Series,测试集标签数据返回:tuple: 填充后的测试特征数据和标签数据- 第一个元素:删除"矿物类型"列的特征数据- 第二个元素:仅包含"矿物类型"的标签数据"""# 1. 分别合并训练集和测试集的特征与标签,形成完整数据集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) # 重置测试集索引# 2. 按"矿物类型"对训练集和测试集进行分组匹配# 训练集分组(用于计算各类型的众数)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]# 3. 用训练集对应分组的众数填充测试集分组A = mode_test_method(A_train, A_test) # 测试类型0 → 训练类型0的众数B = mode_test_method(B_train, B_test) # 测试类型1 → 训练类型1的众数C = mode_test_method(C_train, C_test)D = mode_test_method(D_train, D_test)# 4. 合并所有填充后的测试集分组df_filled = pd.concat([A, B, C, D])# 重置索引,确保数据连续性df_filled = df_filled.reset_index(drop=True)# 5. 拆分并返回填充后的特征和标签return df_filled.drop("矿物类型", axis=1), df_filled["矿物类型"]
5.线性回归算法填充
def linear_train_fill(train_data, train_label):"""训练集线性回归填充函数:使用线性回归模型,根据其他特征预测并填充缺失值参数:train_data: pandas.DataFrame,训练集特征数据(可能包含缺失值)train_label: pandas.DataFrame/Series,训练集标签数据(含"矿物类型"列)返回:tuple: 填充后的训练特征数据和标签数据- 第一个元素:无缺失值的训练特征数据- 第二个元素:训练标签数据("矿物类型"列)"""# 1. 合并特征和标签为完整数据集,重置索引避免混乱train_data_all = pd.concat([train_data, train_label], axis=1)train_data_all = train_data_all.reset_index(drop=True)# 2. 分离特征数据(删除标签列"矿物类型")train_data_x = train_data_all.drop("矿物类型", axis=1)# 3. 统计每列的缺失值数量,并按缺失值从少到多排序# 原因:缺失值少的列先填充,后续可作为其他列的预测特征null_num = train_data_x.isnull().sum() # 计算每列缺失值数量null_num_sorted = null_num.sort_values(ascending=True) # 升序排序(少→多)# 存储已处理的特征列(用于构建预测模型的输入特征)filling_feature = []# 4. 按缺失值从少到多的顺序,逐列用线性回归填充缺失值for i in null_num_sorted.index:# 将当前列加入已处理特征列表filling_feature.append(i)# 若当前列无缺失值,直接跳过(无需填充)if null_num_sorted[i] != 0:# 构建回归模型的输入(X)和目标(y)# X:已处理特征中除当前列外的其他列(无缺失或已填充)x = train_data_x[filling_feature].drop(i, axis=1)# y:当前需要填充的列(作为回归目标)y = train_data_x[i]# 找到当前列中缺失值所在的行索引row_number_mg_null = train_data_x[train_data_x[i].isnull()].index.tolist()# 划分训练数据(无缺失值的样本)和测试数据(有缺失值的样本)x_train = x.drop(row_number_mg_null) # 输入特征的训练集(无缺失)y_train = y.drop(row_number_mg_null) # 目标列的训练集(无缺失)x_test = x.iloc[row_number_mg_null] # 输入特征的测试集(用于预测缺失值)# 训练线性回归模型regr = LinearRegression()regr.fit(x_train, y_train)# 预测缺失值并填充y_pred = regr.predict(x_test)train_data_x.loc[row_number_mg_null, i] = y_pred# 打印填充进度print("完成训练集中的{}列的填充".format(i))# 返回填充后的特征数据和原始标签return train_data_x, train_data_all.矿物类型def linear_test_fill(train_data, train_label, test_data, test_label):"""测试集线性回归填充函数:使用训练集训练的线性回归模型,填充测试集缺失值参数:train_data: pandas.DataFrame,训练集特征数据train_label: pandas.DataFrame/Series,训练集标签数据test_data: pandas.DataFrame,测试集特征数据(可能包含缺失值)test_label: pandas.DataFrame/Series,测试集标签数据返回:tuple: 填充后的测试特征数据和标签数据- 第一个元素:无缺失值的测试特征数据- 第二个元素:测试标签数据("矿物类型"列)"""# 1. 合并训练集和测试集的特征与标签,重置索引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)# 2. 分离特征数据(删除标签列)test_data_x = test_data_all.drop("矿物类型", axis=1) # 测试集特征train_data_x = train_data_all.drop("矿物类型", axis=1) # 训练集特征(用于训练模型)# 3. 统计测试集每列的缺失值数量,按从少到多排序null_mum = test_data_x.isnull().sum()null_num_sorted = null_mum.sort_values(ascending=True)# 存储已处理的特征列filling_feature = []# 4. 按缺失值从少到多的顺序,用训练集模型填充测试集缺失值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.矿物类型
6.随机森林算法填充
def rf_train_fill(train_data, train_label):"""训练集随机森林填充函数:使用随机森林回归模型,基于其他特征预测并填充缺失值参数:train_data: pandas.DataFrame,训练集特征数据(可能包含缺失值)train_label: pandas.DataFrame/Series,训练集标签数据(含"矿物类型"列)返回:tuple: 填充后的训练特征数据和标签数据- 第一个元素:无缺失值的训练特征数据- 第二个元素:训练标签数据("矿物类型"列)"""# 1. 合并特征数据与标签数据为完整数据集,重置索引避免混乱train_data_all = pd.concat([train_data, train_label], axis=1)train_data_all = train_data_all.reset_index(drop=True)# 2. 分离特征数据(移除标签列"矿物类型")train_data_x = train_data_all.drop("矿物类型", axis=1)# 3. 统计每列缺失值数量,并按缺失值从少到多排序# 排序原因:缺失值少的列先填充,后续可作为其他列的预测特征,保证特征完整性null_num = train_data_x.isnull().sum() # 计算每列缺失值数量null_num_sorted = null_num.sort_values(ascending=True) # 升序排序(缺失值少→多)# 存储已处理的特征列(用于构建模型的输入特征池)filling_feature = []# 4. 按缺失值从少到多的顺序,逐列用随机森林回归填充缺失值for i in null_num_sorted.index:# 将当前列加入已处理特征列表filling_feature.append(i)# 若当前列无缺失值,直接跳过(无需填充)if null_num_sorted[i] != 0:# 构建模型输入(X)和目标(y)# X:已处理特征中排除当前列的其他列(无缺失或已填充,可作为预测变量)x = train_data_x[filling_feature].drop(i, axis=1)# y:当前需要填充的列(作为预测目标)y = train_data_x[i]# 定位当前列中缺失值所在的行索引row_number_mg_null = train_data_x[train_data_x[i].isnull()].index.tolist()# 划分训练数据(无缺失值的样本)和预测数据(有缺失值的样本)x_train = x.drop(row_number_mg_null) # 输入特征的训练集(无缺失值)y_train = y.drop(row_number_mg_null) # 目标列的训练集(无缺失值)x_test = x.iloc[row_number_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_number_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: pandas.DataFrame,训练集特征数据train_label: pandas.DataFrame/Series,训练集标签数据test_data: pandas.DataFrame,测试集特征数据(可能包含缺失值)test_label: pandas.DataFrame/Series,测试集标签数据返回:tuple: 填充后的测试特征数据和标签数据- 第一个元素:无缺失值的测试特征数据- 第二个元素:测试标签数据("矿物类型"列)"""# 1. 合并训练集和测试集的特征与标签,重置索引确保一致性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)# 2. 分离特征数据(移除标签列)test_data_x = test_data_all.drop("矿物类型", axis=1) # 测试集特征train_data_x = train_data_all.drop("矿物类型", axis=1) # 训练集特征(用于训练模型)# 3. 统计测试集每列缺失值数量,按从少到多排序null_num = test_data_x.isnull().sum()null_num_sorted = null_num.sort_values(ascending=True)# 存储已处理的特征列filling_feature = []# 4. 按缺失值从少到多的顺序,用训练集模型填充测试集缺失值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 = 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.矿物类型
对于前四个填充我们理解起来并不困难,最后两个我们是把缺失的一列当做y值,其他的为x也就是我们用到了回归算法,而缺失的一行当做测试集去通过训练后的模型进行预测结果后用来填充。
四、保存新数据集
下面是矿物数据预处理和准备的完整流程,主要用于为后续的机器学习模型训练做准备。下面我将详细分析代码的各个部分:
1. 导入必要的库
import pandas as pd # 数据处理库
import fill_data # 自定义的数据填充库
from sklearn.preprocessing import StandardScaler # 数据标准化工具
from sklearn.model_selection import train_test_split # 数据集拆分工具
2. 数据读取与初步筛选
data = pd.read_excel("矿物数据.xls") # 读取Excel数据
data = data[data["矿物类型"] != 'E'] # 筛选掉"矿物类型"为'E'的样本
这部分代码读取了矿物数据,并排除了类型为 'E' 的样本,可能是因为 'E' 类型的数据不适合当前的分析任务。
3. 空值检查
null_num = data.isnull() # 检查每个单元格是否为空
null_total = null_num.sum() # 计算每列的空值总数
这里只是计算了空值数量,但没有做处理,后续会通过专门的填充函数处理空值。
4. 特征与标签分离
# 从数据中分离出特征X(排除"矿物类型"和"序号"列)
x = data.drop("矿物类型", axis=1).drop('序号', axis=1)
y = data["矿物类型"] # 标签y为"矿物类型"列
5. 标签编码
label_dict = {'A':0, 'B':1, "C":2, "D":3} # 定义标签到数字的映射
encoded_label = [label_dict[label] for label in y] # 将标签转换为数字
y = pd.Series(encoded_label, name="矿物类型") # 转换为Series格式
将文本类型的矿物类型(A、B、C、D)转换为数字(0、1、2、3),这是大多数机器学习算法的要求。
6.数据类型转换
for column_name in x.columns:x[column_name] = pd.to_numeric(x[column_name], errors='coerce')
确保所有特征列都是数值类型,无法转换的值会被设为 NaN,方便后续统一处理。
7. 数据标准化
scaler = StandardScaler() # 创建标准化器
x_z = scaler.fit_transform(x) # 对特征进行标准化处理
x = pd.DataFrame(x_z, columns=x.columns) # 转换回DataFrame格式
标准化处理可以使不同量级的特征具有相同的尺度,有助于提高模型的训练效果。
8. 数据集拆分
x_train_w, x_test_w, y_train_w, y_test_w = train_test_split(x, y, test_size=0.3, random_state=50000
)
将数据集拆分为训练集(70%)和测试集(30%),random_state
确保拆分结果可重现。
9. 空值填充(核心部分)
# 空值删除
# x_train_fill,y_train_fill=fill_data.cca_train_fill(x_train_w,y_train_w)
#
# x_test_fill,y_test_fill=fill_data.cca_test_fill(x_test_w,y_test_w)# 中位数
# x_train_fill,y_train_fill=fill_data.median_train_fill(x_train_w,y_train_w)
#
# x_test_fill,y_test_fill=fill_data.median_test_fill(x_train_fill,y_train_fill,x_test_w,y_test_w)#众数
# x_train_fill,y_train_fill=fill_data.mode_train_fill(x_train_w,y_train_w)
# x_test_fill,y_test_fill=fill_data.mode_test_fill(x_train_fill,y_train_fill,x_test_w,y_test_w)#平均数
# x_train_fill,y_train_fill=fill_data.mean_train_fill(x_train_w,y_train_w)
# x_test_fill,y_test_fill=fill_data.mean_test_fill(x_train_fill,y_train_fill,x_test_w,y_test_w)# 线性回归
# x_train_fill,y_train_fill=fill_data.linear_train_fill(x_train_w,y_train_w)
# x_test_fill,y_test_fill=fill_data.linear_test_fill(x_train_fill,y_train_fill,x_test_w,y_test_w)# 随机森林
x_train_fill,y_train_fill=fill_data.rf_train_fill(x_train_w,y_train_w)
x_test_fill,y_test_fill=fill_data.rf_test_fill(x_train_fill,y_train_fill,x_test_w,y_test_w)
10. 处理类别不平衡
from imblearn.over_sampling import SMOTE
oversampler = SMOTE(k_neighbors=1, random_state=42) # 创建SMOTE过采样器
os_x_train, os_y_train = oversampler.fit_resample(x_train_fill, y_train_fill) # 对训练集进行过采样
使用 SMOTE 算法解决类别不平衡问题,通过生成少数类样本的合成样本,使各类别样本数量趋于平衡。
11. 数据整理与保存
# 将标签和特征合并,并打乱训练集顺序
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(r'shuju\训练集数据(随机森林).xlsx', index=False)
data_test.to_excel(r'shuju\测试集数据(随机森林).xlsx', index=False)
总结
我们最终得到12个数据集文件,分别为不同方式的填充得到的训练集与测试集数据,为后面机器学习模型的训练做好准备。