机器学习--数据清洗
一·数据清洗通常涉及以下步骤:
- 完整性:检查单条数据是否存在空值,统计的字段是否完善。
- 全面性:观察某一列的全部数值,可以通过比较最大值、最小值、平均值、数据定义等来判断数据是否全面。
- 合法性:检查数值的类型、内容、大小是否符合预设的规则。例如,人类的年龄超过 1000 岁这个数据就是不合法的。
- 唯一性:检查数据是否重复记录,例如一个人的数据被重复记录多次。
- 类别是否可靠。
任务:实现智能的矿物识别系统
一、搞定数据集
1、处理异常数据
2、填充数据:2 种方法:
a、根据对应类型填充:平均值、众数、中位数。
b、算法进行填充。如何实现??
c、不填充数据,只用有效的数据进行训练。
利用已有的数据来进行训练???分类还是回归??
二、训练模型
1、使用多种模型训练,lr、RF、svm、xgboost... 每种模型都需要调参。
2、对比每种模型的效果
二·代码
import pandas as pd
import matplotlib.pyplot as plt
import fill_datadata = pd.read_excel("矿物数据.xls")
data = data[data['矿物类型'] != 'E'] # 删除特殊的类别E。整个数据集中只存在1个E数据。
null_num = data.isnull()
null_total = null_num.sum() # 检测每列中的缺失值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='矿物类型') # 将列表转换为Pandas Series# 数据中存在大量字符串数值、\、空格等异常数据。字符串数值直接转换为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',会将无法转换的值设置为NaN。"""数据标准化:Z标准化"""
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) # Z标准化处理后为numpy数据,这里再转换回pandas数据'''数据集的切分'''
from sklearn.model_selection import train_test_split
x_train_w, x_test_w, y_train_w, y_test_w = \train_test_split(X_whole, y_whole, test_size=0.3, random_state=50000)'''数据中存在空缺,进行6种方法进行填充'''
## 1、只保留完整数据集
# x_train_fill,y_train_fill = fill_data.cca_train_fill(x_train_w,y_train_w)#调用自己写的implement_data.py
# 测试集的填充
# x_test_fill,y_test_fill = fill_data.cca_test_fill(x_train_fill, y_train_fill, x_test_w, y_test_w)#调用#2、使用平均值的方法对数据进行填充
# 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)##3、使用中位数的方法对数据进行填充
# x_train_fill,y_train_fill = fill_data.median_train_fill(x_train_w,y_train_w)#调用自己写的implement_data
##测试集的填充
# x_test_fill,y_test_fill = fill_data.median_test_fill(x_train_fill, y_train_fill, x_test_w, y_test_w)##4、使用众数的方法对数据进行填充
# x_train_fill,y_train_fill = fill_data.mode_train_fill(x_train_w,y_train_w)#调用自己写的implement_data.p
###测试集的填充
# x_test_fill,y_test_fill = fill_data.mode_test_fill(x_train_fill, y_train_fill, x_test_w, y_test_w)#调## 5、线性回归算法实现训练数据集、测试数据集的填充
# x_train_fill,y_train_fill = fill_data.lr_train_fill(x_train_w,y_train_w)#调用自己写的implement_data.py文
#测试集的填充
# x_test_fill,y_test_fill = fill_data.lr_test_fill(x_train_fill, y_train_fill, x_test_w, y_test_w)#调用自## 6、随机森林算法实现训练数据集、测试数据集的填充
#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)#调用自己'''smote算法实现数据集的拟合,解决样本不均衡'''
from imblearn.over_sampling import SMOTE
oversampler = SMOTE(k_neighbors=1, random_state=42)#数据对象
os_x_train, os_y_train = oversampler.fit_resample(x_train_fill, y_train_fill)#人工拟合的只是训练集。'''数据保存为excel文件'''
data_train = pd.concat([os_y_train,os_x_train],axis=1).sample(frac=1, random_state=4)#sample()方法用于从 `DataFrame` 中随机抽取行,`frac` 表示抽取行的比例。
data_test = pd.concat([y_test_fill,x_test_fill],axis=1)#测试集不用传入模型训练,无需打乱顺序。data_train.to_excel(r'.//temp_data//训练数据集[平均值填充].xlsx', index=False)
data_test.to_excel(r'.//temp_data//测试数据集[平均值填充].xlsx', index=False)
创建一个fill.data.py
import pandas as pd'''此文档用不同的算法实现缺失数据的填充,算法使用到
1、CCA(Complete Case Analysis)只考虑包含完整数据的行
2、平均值、
3、中位数、
4、众数、
5、机器学习算法'''#-----------------------考虑包含完整行的数据-----------------------------#
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)#用于重置索引的。当你对数据进行了排序、筛选或其他操作后df_filled = data.dropna()#用于删除(或过滤掉)包含缺失值(NaN)的行或列。pandas里面有大量return df_filled.drop('矿物类型', axis=1), df_filled.矿物类型def cca_test_fill(train_data, train_label, test_data, test_label):'''CCA(Complete Case Analysis)只考虑包含完整数据的行'''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.矿物类型'''---------------------使用平均值的方法对数据进行填充---------------------'''
def mean_train_method(data):'''平均值的计算方法'''fill_values = data.mean()return data.fillna(fill_values) # 使用均值填充缺失值,pandas读取表格数据,数据清洗。
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])
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_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.矿物类型'''---------------------使用中位数的方法对数据进行填充---------------------'''
def median_method(data):'''数据集中的空值使用每列的中位数替代'''fill_values = data.median()return data.fillna(fill_values) # 使用中位数填充缺失值def median_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 = median_method(A) # 按照每个类别的数据进行填充B = median_method(B) # 按照每个类别的数据进行填充C = median_method(C) # 按照每个类别的数据进行填充D = median_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.矿物类型
def median_test_method(train_data, test_data):'''根据训练集获取每个类别的中位数,并将训练集的每个类别中位数填充到测试中'''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_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 = median_test_method(A_train, A_test) # 按照每个类别的数据进行填充B = median_test_method(B_train, B_test) # 按照每个类别的数据进行填充C = median_test_method(C_train, C_test) # 按照每个类别的数据进行填充D = median_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.矿物类型
'''---------------------使用众数的方法对数据进行填充---------------------'''
def mode_method(data):'''数据集中的空值使用每列的众数替代,pandas中的mode方法获取众数'''fill_values = data.apply(lambda x: x.mode().iloc[0] if len(x.mode()) > 0 else None)# data.apply对DataFrame的每一列应用一个函数,用于找出每列中出现次数最多的值(即众数)。如果某一列没有众数(即每个值a = data.mode()#获取每列的众数,氯中的众数比较多,一共10个,因此会列出10行,其他列不够,则用nan表示。return data.fillna(fill_values) # 使用众数填充缺失值def mode_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 = mode_method(A) # 按照每个类别的数据进行填充B = mode_method(B) # 按照每个类别的数据进行填充C = mode_method(C) # 按照每个类别的数据进行填充D = mode_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.矿物类型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) # 使用众数填充缺失值def mode_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 = mode_test_method(A_train, A_test) # 按照每个类别的数据进行填充B = mode_test_method(B_train, B_test) # 按照每个类别的数据进行填充C = mode_test_method(C_train, C_test) # 按照每个类别的数据进行填充D = mode_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.矿物类型'''---------------------线性回归算法实现训练数据集、测试数据集的填充---------------------'''
def lr_train_fill(train_data, train_label):'''使用线性回归填充训练数据集中的缺失值,主要是基于这样一个思想:特征和目标变量之间存在一定的关系以下是使用线性填充缺失值的一般步骤:1、首先,确定哪些特征包含缺失值。2、对于包含缺失值的特征,将其作为目标变量,而其他特征(可以包括原始的目标变量)作为输入特征。注意,如果多个特征都有缺失值,通常建议按照缺失值的数量从小到大进行处理。因为缺失值较少的特征对预测的要求较低,准确性可能更高。3、在处理某个特征的缺失值时,将该特征中的已知值(即非缺失值)作为训练集,而缺失值作为需要预测的目标。此时,其他特征的已知值作为输入。4、使用线性回归模型进行训练,并对缺失值进行预测。5、将预测得到的值填充到原始数据中的相应位置。6、重复上述步骤,直到处理完所有包含缺失值的特征。需要注意的是,使用线性回归填充缺失值时,可能会受到模型选择和过拟合等因素的影响。因此,在实际应用中,建议对数据进行适当的预处理,如特征选择、异常值处理等,以提高填充的准确性和稳定性。同时,也可以使用交叉验证等方法来评估填充的效果。'''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: # 当前特征是否有空缺的内容。用来判断是否开始训练模型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.矿物类型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_predprint('完成测试数据集中的\'{}\'列数据的填充'.format(i))return test_data_X, test_data_all.矿物类型
一、数据加载与初步清洗
数据读取与过滤
读取 “矿物数据.xls”,删除仅有的 1 条 “矿物类型 = E” 的特殊数据(避免异常类别影响)。python
data = pd.read_excel("矿物数据.xls") data = data[data['矿物类型'] != 'E'] # 过滤特殊类别
缺失值检测
统计每列缺失值数量,为后续填充做准备:python
null_total = data.isnull().sum() # 每列缺失值统计
二、数据预处理
特征与标签分离
- 特征(X):去除 “矿物类型”(标签)和 “序号”(无意义),保留所有属性数据。
- 标签(y):将原始标签(A/B/C/D)转换为数字(0/1/2/3),便于模型识别。
python
X_whole = data.drop('矿物类型', axis=1).drop('序号', axis=1) y_whole = pd.Series([label_dict[label] for label in data.矿物类型], name='矿物类型')
异常值处理
将字符串、空格、反斜杠等异常值转换为 NaN(统一缺失值格式):python
X_whole[column_name] = pd.to_numeric(..., errors='coerce') # 无法转换的值设为NaN
标准化(Z-score)
消除特征量级差异(避免某特征对模型影响过大),将特征转换为均值 0、标准差 1 的分布:python
scaler = StandardScaler() X_whole_Z = scaler.fit_transform(X_whole) # 标准化处理
三、数据集切分
将数据按 7:3 比例分为训练集(用于模型训练)和测试集(用于评估),固定随机种子确保结果可复现:
python
x_train_w, x_test_w, y_train_w, y_test_w = train_test_split(X_whole, y_whole, test_size=0.3, random_state=50000
)
四、核心:6 种缺失值填充方法
数据中存在大量缺失值,通过fill_data
模块实现 6 种填充策略(按类别填充,因不同矿物类型的特征分布可能不同):
填充方法 | 原理 | 适用场景 |
---|---|---|
1. CCA | 只保留无缺失值的行(删除含 NaN 的样本) | 缺失值极少时,避免数据失真 |
2. 平均值填充 | 按矿物类别计算特征平均值,用同类均值填充缺失值(训练集、测试集分开算) | 特征近似正态分布,无极端值 |
3. 中位数填充 | 按类别计算中位数填充,抗极端值能力更强 | 特征含异常值时 |
4. 众数填充 | 按类别取最频繁出现的值填充 | 离散特征(如分类属性) |
5. 线性回归 | 用其他特征训练线性回归模型,预测缺失值(利用特征间线性关系) | 特征间存在明显线性关联 |
6. 随机森林 | 用随机森林模型预测缺失值(捕捉非线性关系) | 特征关系复杂(非线性、交互) |
关键逻辑:训练集填充用自身数据计算统计量 / 训练模型;测试集填充用训练集的统计量 / 模型(避免数据泄露)。
五、样本不均衡处理
若矿物类型样本数量差异大(如 A 类多、D 类少),用 SMOTE 算法过采样少数类,平衡各类别样本量:
python
oversampler = SMOTE(k_neighbors=1) # 生成少数类合成样本
os_x_train, os_y_train = oversampler.fit_resample(x_train_fill, y_train_fill)
六、数据保存
将填充、均衡后的训练集和测试集保存为 Excel,供后续建模使用:
python
data_train.to_excel("训练数据集[平均值填充].xlsx", index=False)
data_test.to_excel("测试数据集[平均值填充].xlsx", index=False)
总结
整个流程围绕 “数据清洗→预处理→缺失值填充→均衡样本→保存数据” 展开,核心是通过多种缺失值填充策略处理数据缺陷,为后续分类模型(如决策树、SVM 等)提供高质量输入,最终提高矿物类型预测的准确性。