数据集的六种填充方法——上(保留完整数据集+使用平均数填充+使用中位数填充)
文章目录
前言
在真实的数据挖掘项目中,原始数据往往并不完美——缺失值是最常见的问题之一。如果直接使用含有缺失值的数据训练模型,可能会导致:
-
模型无法计算(很多算法不支持缺失值)
-
统计偏差(缺失并非随机)
-
信息浪费(直接删除行会丢失有用信息)
因此,在建模之前,我们需要对缺失值进行合理的填充或删除。即数据预处理。
一、准备工作:数据与工具
我们从往年的人工智能竞赛上面获取一道真题:
假设我们有一份矿物数据,包含序号、矿物类型(A/B/C/D)以及若干数值型特征。其中部分样本存在空值,我们现在需要对这份矿物数据做数据预处理,包括且不限于去除不合理的数据,填充数据集中空缺的数据。
数据集预览:
为了对这份数据集进行预处理,我们需要在额外新建一个py文件,用来存放函数。
二、完整代码
import pandas as pd
import matplotlib.pyplot as plt
import fill_data
data = pd.read_excel('矿物数据.xls')
data = data[data['矿物类型']!='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="矿物类型")
for column_name in X_whole.columns:
X_whole[column_name] = pd.to_numeric(X_whole[column_name],errors='coerce')
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)
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=10000)
# #1.只保留完整数据集
# 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_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)
# x_test_fill,y_test_fill = fill_data.medain_teat_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)
# x_test_fill,y_test_fill = fill_data.mode_teat_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)
# 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)
from imblearn.over_sampling import SMOTE
oversampler = SMOTE(random_state=42, k_neighbors=1)
os_x_train,os_y_train = oversampler.fit_resample(x_train_fill,y_train_fill)
y_whole = pd.concat([os_y_train,y_test_fill])
# label_count = pd.value_counts(y_whole)
label_count = y_whole.value_counts()
fig, ax =plt.subplots()
bars = ax.bar(label_count.index,label_count.values)
for bar in bars:
yval = bar.get_height()
ax.text(bar.get_x()+bar.get_width()/2,yval,
round(yval,2),
va = 'bottom',
ha = 'center',
fontsize = 10,
color = 'black'
)
plt.xlabel('labels')
plt.ylabel('numbers')
plt.title('The number of data for each category after removing empty data')
plt.show()
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)
data_train.to_excel(r'.//temp_data//训练数据集[随机森林填充].xlsx', index=False)
data_test.to_excel(r'.//temp_data//测试数据集[随机森林填充].xlsx', index=False)
可以看到我们有六种方法处理空缺的数据集,本文仅对前三种方式做介绍和讲解。
三、方法1:只保留完整数据
这种方法的主旨是:既然有的数据是空缺的,那么有空缺的数据我们就删除所有包含缺失值的行,仅使用完整的数据。
他的优点是简单快速,不引入额外偏差且保留真实数据分布。
但是缺点也很明显:样本量大幅减少,可能丢失重要信息,会使数据集的大小明显变小。
在我们新建的fill_data.py文件中写入两种函数,一种针对训练集,一种针对测试集。
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(test_data,test_label):
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 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(test_data,test_label):
合并,排序
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.矿物类型
四、方法2:使用平均数填充
这个方法主旨是用每一列的已有数据的平均数,填充到空缺的位置上。
和只是用完整数据的方法不同的是,我们需要首先需要按照矿物类型分类好,每一类使用各自的平均值,然后将各自的平均值填充到空缺的位置。
def mean_train_method(data):
fill_values = data.mean()
return data.fillna(fill_values)
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_train_method(data):
fill_values = data.mean()
return data.fillna(fill_values)
然后我们再写入一个划分各个类型的函数
导入训练集的标签和特征,合并并排序
def mean_train_fill(train_data,train_label):
data = pd.concat([train_data,train_label],axis=1)
data = data.reset_index(drop=True)
分别提取出矿物类型为0,1,2,3的数据,后面导入到计算平均值的函数中。
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)
最后将填充好的A,B,C,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 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.矿物类型
五、方法3:使用中位数填充
本质上是和计算平均值是同一个思路去处理,只是将计算平均值的函数替换为计算中位数的函数。
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 medain_teat_meyhod(train_data,test_data):
fill_values = train_data.median()
return test_data.fillna(fill_values)
def medain_teat_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 = medain_teat_meyhod(A_train, A_test)
B = medain_teat_meyhod(B_train, B_test)
C = medain_teat_meyhod(C_train, C_test)
D = medain_teat_meyhod(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.矿物类型
六、三种方法的直观对比
| 方法 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| 删除法 | 缺失率<5%,且缺失完全随机 | 简单,无数据扭曲 | 样本减少,浪费信息 |
| 均值填充 | 数值型,分布对称,无异常值 | 保持均值,速度快 | 受异常值影响,降低方差 |
| 中位数填充 | 数值型,偏态分布或有异常值 | 稳健,不受极端值影响 | 同样降低方差,计算略慢 |
总结
小样本数据:优先考虑中位数或均值填充,避免删除后样本不足。
数据量充足且缺失少:直接删除法最简单可靠。
下一篇文章我们将会介绍更高级的填充方法:众数填充、逻辑回归预测填充和随机森林填充。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐


所有评论(0)