机器学习案例——预测矿物类型(数据处理部分)
一、数据集预处理
1. 数据读取与过滤
data = pd.read_excel('矿物数据改1.xls')
# 过滤掉矿物类型为'E'的样本
data = data[data['矿物类型'] != 'E']
具体说明:
- 使用pandas的read_excel函数读取名为"矿物数据改1.xls"的Excel文件
- 原始数据可能包含多种矿物类型,其中E类的样本数量明显少于其他类别(例如可能只有个位数样本)
- 为避免类别不平衡问题影响模型训练效果,将E类样本全部移除
- 过滤后保留的矿物类型可能包括A、B、C、D等主要类别
2. 特征与标签分离
# 特征矩阵:排除第1列(ID)和最后1列(标签)
X = data.iloc[:, 1:-1]
# 标签向量:取最后一列
y = data.iloc[:, -1]
详细说明:
- 数据集中第一列通常是样本ID或编号,不具有特征意义,因此排除
- 最后一列是目标变量(矿物类型标签),需要单独提取
- 中间的所有列都被视为特征变量
- 例如,特征可能包括矿物的化学成分(SiO2、Fe2O3等含量)、物理性质(硬度、密度等)等测量指标
3. 标签编码
from sklearn.preprocessing import LabelEncoder# 创建标签编码器实例
le = LabelEncoder()
# 对类别标签进行编码转换
y_encoded = le.fit_transform(y)
处理细节:
- 由于原始标签可能是字符串形式(如"A"、"B"等),需要转换为数值形式
- LabelEncoder会将类别标签映射为0到n_classes-1的整数
- 例如:
- 原始标签["A","B","C","A"]
- 编码后变为[0,1,2,0]
- 编码器会保存类别映射关系,便于后续结果解码还原
- 这一步是为后续机器学习算法做准备,因为大多数算法要求输入为数值型数据
空缺值填充
在进行上述步骤后我们需要对训练集和测试集中的缺失数据进行填充,为了方便这里使用了一个类
进行填充
CaaDataProcess 类详细说明文档
类概述
CaaDataProcess 是一个专门用于处理数据集中缺失值的 Python 类,它封装了多种缺失值填充策略,特别适合在机器学习项目中保持训练集和测试集数据处理的一致性。
详细功能特点
1. 多种填充策略支持
统计方法填充:
- 众数填充(mode):特别适合分类特征
- 均值填充(mean):适合正态分布的数值特征
- 中位数填充(median):适合有偏分布的数值特征
机器学习方法填充:
- 线性回归预测(line):适用于线性相关的连续特征
- 随机森林预测(forest):适用于复杂非线性关系的数据
极端处理方式:
- 直接删除(drop):简单但可能导致数据损失
2. 类别感知处理机制
类能够根据不同类别分别计算填充值,这在处理分类问题时的数据填充尤为重要。例如:
- 对于"收入"特征,可以分别计算"男性"和"女性"群体的平均值进行填充
- 对于"产品评分",可以按不同产品类别计算中位数填充
3. 训练/测试一致性保障
通过保存训练集的填充规则,确保:
- 测试集使用与训练集完全相同的填充值
- 避免数据泄露问题
- 保持特征分布的一致性
4. 自动化流程设计
将所有复杂处理逻辑封装在类中,对外提供简洁的API:
caa_train_process()
:训练集处理入口caa_test_process()
:测试集处理入口
核心方法深度解析
1. 初始化方法 (__init__
)
def __init__(self, model='mode'):"""初始化数据处理器参数:model (str): 选择数据处理模式,默认为'mode'(众数填充)可选值及其适用场景:'mode' - 分类特征或离散数值特征'mean' - 连续数值特征且分布对称'median' - 连续数值特征且有偏分布'line' - 特征间存在线性关系'forest' - 特征间存在复杂非线性关系'drop' - 缺失值很少且可接受数据损失"""self.model = model # 存储用户选择的处理模式self.fillna = [] # 用于存储训练集计算的填充值(测试集处理时使用)self.models = {} # 存储训练好的预测模型(机器学习填充方式使用)
2. 基础填充方法实现
众数填充 (mode_method
)
def mode_method(self, frame: tuple[pd.DataFrame, ...]) -> tuple[pd.DataFrame, ...]:"""按类别分组进行众数填充处理流程:1. 对每个类别分组单独计算各特征的众数2. 保存计算出的众数值供测试集使用3. 使用计算出的众数填充缺失值参数:frame: 按类别分组后的DataFrame元组返回:填充后的DataFrame元组示例:# 原始数据按标签分为3类labels_list = (df[df['label']==0], df[df['label']==1], df[df['label']==2])filled_data = mode_method(labels_list)"""fills = []for data in frame:# 计算每列的众数(处理多众数情况取第一个)fill_values = data.apply(lambda x: x.mode().iloc[0] if len(x.mode()) > 0 else np.nan)self.fillna.append(fill_values) # 保存填充值filled_data = data.fillna(fill_values)fills.append(filled_data)return tuple(fills)
均值/中位数填充
def mean_method(self, frame: tuple[pd.DataFrame, ...]) -> tuple[pd.DataFrame, ...]:"""按类别分组进行均值填充与mode_method结构类似,但使用mean()计算填充值处理注意事项:- 自动跳过非数值列- 对全空列填充NaN"""fills = []for data in frame:# 只对数值列计算均值numeric_cols = data.select_dtypes(include=[np.number]).columnsfill_values = data[numeric_cols].mean()# 非数值列保持原样for col in data.columns:if col not in numeric_cols:fill_values[col] = np.nanself.fillna.append(fill_values)filled_data = data.fillna(fill_values)fills.append(filled_data)return tuple(fills)
3. 高级填充方法实现
线性回归预测填充 (lr_train_fill
)
@staticmethod
def lr_train_fill(x, y):"""使用线性回归迭代预测填充缺失值算法步骤:1. 统计各列缺失值数量并按升序排序2. 从缺失最少的列开始处理3. 使用已填充的特征训练模型预测当前列缺失值4. 重复直到所有列处理完成参数:x: 特征DataFramey: 目标Series返回:(填充后的特征, 目标)"""data = pd.concat([x, y], axis=1).reset_index(drop=True)x_data = data.drop(y.name, axis=1)# 统计缺失情况并按缺失量排序null_num = x_data.isnull().sum()null_num_sort = null_num.sort_values(ascending=True)features_filled = [] # 记录已填充的特征for col in null_num_sort.index:features_filled.append(col)if null_num_sort[col] > 0: # 当前列有缺失值# 准备训练数据(使用已填充的特征)train_cols = [f for f in features_filled if f != col]x_train = x_data[train_cols].dropna()y_train = x_data[col].dropna()# 缺失行索引row_idx = x_data[x_data[col].isnull()].indexif len(x_train) > 0: # 有可用的训练数据# 训练线性回归模型lr = LinearRegression()lr.fit(x_train, y_train)# 预测并填充缺失值x_pred = x_data[train_cols].iloc[row_idx]y_pred = lr.predict(x_pred)x_data.loc[row_idx, col] = y_predreturn x_data, data[y.name]
随机森林预测填充
@staticmethod
def rf_train_fill(x, y):"""使用随机森林预测填充缺失值基本流程与线性回归类似,但使用随机森林模型,更适合处理:- 非线性关系- 特征交互作用- 高维稀疏数据"""# 实现细节与lr_train_fill类似# 主要区别在于使用RandomForestRegressorrf = RandomForestRegressor(n_estimators=100,max_depth=10,random_state=42)# ...其余实现与线性回归版本类似
4. 训练集处理入口 (caa_train_process
)
def caa_train_process(self, features: pd.DataFrame, labels: pd.Series):"""训练集数据处理主入口根据初始化时选择的模式调用相应的处理方法处理流程:1. 合并特征和标签2. 按模式选择处理方式3. 保存必要的填充规则/模型4. 返回处理后的数据参数:features: 训练特征DataFramelabels: 训练标签Series返回:(处理后的特征, 处理后的标签)"""data = pd.concat([features, labels], axis=1)if self.model == 'mode':# 按类别分组处理labels_list = tuple(data[data[labels.name] == i] for i in sorted(labels.unique()))group = self.mode_method(labels_list)processdata = pd.concat(group)elif self.model == 'drop':processdata = data.dropna()elif self.model == 'mean':# 类似mode处理,使用mean_methodlabels_list = tuple(data[data[labels.name] == i] for i in sorted(labels.unique()))group = self.mean_method(labels_list)processdata = pd.concat(group)elif self.model == 'line':# 使用线性回归填充features, labels = self.lr_train_fill(features, labels)processdata = pd.concat([features, labels], axis=1)elif self.model == 'forest':# 使用随机森林填充features, labels = self.rf_train_fill(features, labels)processdata = pd.concat([features, labels], axis=1)# 确保返回顺序与输入一致return processdata.drop(labels.name, axis=1), processdata[labels.name]
5. 测试集处理 (caa_test_process
)
def caa_test_process(self, x_train, y_train, x_test, y_test):"""测试集数据处理入口确保使用与训练集相同的处理规则:- 统计方法: 使用训练集计算的填充值- 机器学习方法: 使用训练集训练的模型参数:x_train: 原始训练特征(未处理)y_train: 原始训练标签x_test: 测试特征y_test: 测试标签返回:(处理后的测试特征, 处理后的测试标签)"""test_data = pd.concat([x_test, y_test], axis=1)if self.model in ['mode', 'mean', 'median']:# 使用训练集保存的填充值for fill_values in self.fillna:test_data = test_data.fillna(fill_values)elif self.model == 'line':# 使用线性回归模型填充x_train_filled, _ = self.lr_train_fill(x_train, y_train)x_test_filled, _ = self.lr_train_fill(x_test, y_test)processdata = pd.concat([x_test_filled, y_test], axis=1)elif self.model == 'forest':# 使用随机森林模型填充x_train_filled, _ = self.rf_train_fill(x_train, y_train)x_test_filled, _ = self.rf_train_fill(x_test, y_test)processdata = pd.concat([x_test_filled, y_test], axis=1)return processdata.drop(y_test.name, axis=1), processdata[y_test.name]
完整使用示例
场景1:分类问题中的众数填充
# 准备数据
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_splitdata = load_iris()
X, y = data.data, data.target
X = pd.DataFrame(X, columns=data.feature_names)
y = pd.Series(y, name='target')# 人为添加缺失值
X.iloc[10:15, 0] = np.nan
X.iloc[20:25, 2] = np.nan# 划分训练测试集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)# 初始化处理器(众数填充)
processor = CaaDataProcess(model='mode')# 处理训练集
X_train_filled, y_train_filled = processor.caa_train_process(X_train, y_train)# 处理测试集(使用训练集的填充规则)
X_test_filled, y_test_filled = processor.caa_test_process(X_train, y_train, X_test, y_test)
场景2:回归问题中的随机森林填充
# 准备波士顿房价数据
from sklearn.datasets import load_boston
boston = load_boston()
X, y = boston.data, boston.target
X = pd.DataFrame(X, columns=boston.feature_names)
y = pd.Series(y, name='MEDV')# 添加缺失值
X.iloc[50:70, 5] = np.nan # RM特征
X.iloc[80:100, 10] = np.nan # PTRATIO特征# 划分数据集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42)# 初始化处理器(随机森林填充)
rf_processor = CaaDataProcess(model='forest')# 处理训练集
X_train_rf, y_train_rf = rf_processor.caa_train_process(X_train, y_train)# 处理测试集
X_test_rf, y_test_rf = rf_processor.caa_test_process(X_train, y_train, X_test, y_test
)
将结果保存为excel文件
为了系统性地测试不同填充方法对训练模型的效果,我们设计了一个完整的实验流程:
- 循环测试多种填充方法:
key_list=['drop','mean','median','mode','line','forest']
for key in key_list:
- 测试6种不同的缺失值处理方法:
drop
:直接删除包含缺失值的样本(适用于缺失比例较小的情况)mean
:使用特征列的均值填充(适合连续型正态分布数据)median
:使用特征列的中位数填充(对异常值更鲁棒)mode
:使用特征列的众数填充(适用于分类变量)line
:基于线性回归预测填充(考虑特征间的线性关系)forest
:基于随机森林预测填充(捕捉非线性关系)
- 数据填充处理:
caa = CaaDataProcess(key)
x_train_intact,y_train_intact = caa.caa_train_process(x_train,y_train)
x_test_intact,y_test_intact = caa.caa_test_process(x_train,y_train,x_test,y_test)
- 处理流程说明:
- 初始化特定填充方法的数据处理器
- 对训练集进行填充处理,保持特征和标签的对应关系
- 对测试集使用与训练集相同的方式处理(如使用训练集计算的统计量)
- 确保特征工程的一致性,避免数据泄露
- 类别不平衡处理:
sm = SMOTE(k_neighbors=1, random_state=1000)
x_train_intact_os, y_train_intact_os = sm.fit_resample(x_train_intact, y_train_intact)
- SMOTE过采样配置说明:
k_neighbors=1
:使用最近邻算法,基于单个最近邻样本生成新样本random_state=1000
:固定随机种子保证实验可复现- 仅对训练集进行过采样,保持测试集的原始分布
- 适用于少数类样本占比<20%的严重不平衡场景
- 结果保存:
x_train_e.to_excel(rf'./数据/{caa.model}[训练集].xlsx', index=False)
x_test_e.to_excel(rf'./数据/{caa.model}[测试集].xlsx', index=False)
- 文件存储规范:
- 按填充方法分类存储(如
forest[训练集].xlsx
) - 存储路径为项目目录下的
/数据/
子目录 - 去除pandas默认索引(
index=False
) - 保留完整的特征工程处理结果,便于后续分析比较
- 按填充方法分类存储(如
补充说明:
- 每次实验会生成12个文件(6种方法×训练测试集)
- 建议配合实验日志记录各方法的处理时间和内存消耗
- 对于大规模数据,可考虑分块处理或使用
feather
格式加快IO速度