📌引言
大家好呀,我是生鱼同学。从本博文开始,我们就从数据处理到机器模型构建的文章专栏撰写,具体的更新和目录都在文末,感兴趣的同学可以支持一下。
在进行机器学习的数据处理时,我们首先要做的就是进行的就是缺失值的相关处理。
在收集到的数据中,通常会存在一些不同程度的数据缺失,如何发现它们以及如何处理它们便成为我们需要处理的第一个问题。
在本文中,我们将会以实际数据为例,以不同方式应对不同类型的数据缺失情况。
📌发现缺失值
想要处理缺失值,首先需要我们发现数据的缺失。
这里就需要我们观察到缺失值的同时,利用统计手段观察不同数据缺失的类型,以方便我们初步制定后续数据处理方案。
当然,为了更好的展示缺失情况,这部分还会存在一些缺失可视化的内容。话不多说,我们开始吧。
(下面的数据来自Kaggle泰坦尼克存活预测数据,链接将会在文末)
🏷️加载数据及初步观察
首先加载数据集,然后调用相关函数对其进行初步观察。代码如下:
# 载入数据
data = pd.read_csv('train.csv', index_col=0)
调用 info()和 describe()进行初步的数据观察。代码和结果如下所示:
data.info()
结果如下所示:
在上图中,我们可以看到Cabin和Age存在一定程度的缺失情况。与此同时,Ticket、Name、Sex都是object的类别,可能在后续的数据处理中需要我们进行编码。
data.describe()
结果如下:
我们可以发现,上述有几中特征的频率存在一定的偏向,所以我们在后续的数据处理中需要注意。
🏷️缺失值可视化
缺失值可视化是我们不可忽视的重要部分,想要更好的表现出我们的分析结果,就需要我们借助一些可视化的工具来更好地展现出各类分析情况。
当然,缺失值可视化也有很多种方式,包括柱状图、矩阵图等,下面我们分别介绍几种不同的缺失值可视化的方法。
📄柱状图可视化缺失值
首先,我们尝试使用柱状图可视化缺失值,具体代码如下:
# 统计缺失值
missing = data.isnull().sum()
# 筛选出大于0的属性
missing = missing[missing>0]
# 排序
missing.sort_values(inplace=True)
missing.plot.bar()
📄missingno矩阵图可视化缺失值
missingno 是一个可以将缺失值情况进行可视化的库,十分便捷,下面我们使用此库进行缺失值的可视化。
import missingno as msno
# 在数据中抽样100个单位
data_sample = data.sample(100)
msno.matrix(data_sample)
📄missingno柱状图可视化缺失值
data_sample = data.sample(100)
msno.bar(data_sample)
📌缺失值处理
数据预处理对于我们最终模型的效果的提高具有很重要的作用,其中缺失值处理是我们首先要考虑的问题。
数据缺失的原因也有很多种,我们在处理缺失值之前要根据不同的情况选择不同的处理方式,做到灵活运用各种方法。数据缺失的原因主要有以下两种:
- 随机缺失:由于采集数据不完全导致的数据缺失,这种缺失是相对随机的。
- 非随机缺失:业务逻辑上的缺失,可能是周期性缺失,或者在业务逻辑上分析确实存在缺失问题。
下面是几种常见的缺失值处理方法,我们需要根据实际情况选择。
🏷️不处理缺失值
- 优点:不用考虑缺失值的情况,直接忽悠,节省时间。
- 缺点:只适用于有限的模型。
这种情况只适用于一些特殊模型,如KNN、决策树、随机森林、神经网络、朴素贝叶斯、DBSCAN等。
因为上述几种类型对于数据的缺失值有很强的包容度,所以我们可以不必处理缺失值,把它当作一种类别。
🏷️删除缺失缺失值
- 优点:简单粗暴
- 缺点:当缺失值的占比较大时,可能会导致很多关键信息被忽略,可能会影响最终的模型拟合效果。
首先我们统计一下本文例数据的缺失值个数:
missing = data.isnull().sum()
missing = missing[missing>0]
missing
上述几个属性的缺失值中,Cabin缺失值达到了687个,因为其占比较多所以我们可以考虑把其删除。
代码如下:
# inplace表示是否在原数据进行填充
data.dropna(axis=0, how='any',subset=['Cabin'], inplace=True)
dropna 参数:
axis: default 0指行,1为列
how: {‘any’, ‘all’}, default ‘any’指带缺失值的所有行;'all’指清除全是缺失值的
thresh: int,保留含有int个非空值的行
subset: 对特定的列进行缺失值删除处理
inplace: 这个很常见,True表示直接在原数据上更改
其他的数据我们就可以考虑对其进行其他处理,通过不同方式进行填充。
🏷️缺失值填充
缺失值的填充方式非常多,本文主要介绍几种常用的缺失值填充方法。
📄手动填充
这部分我认为是相对来说最有效的方法,就是根据专业的知识水平判断该种情况的数值。
但是此种填充方法需要的专业性比较强,大多数情况下,我们使用这种方法的能力有限。
📄统计量填充
根据业务逻辑以及各特征的不同含义进行分析,使用平均数,众数,中位数等直接对缺失部分进行填充。
平均值填充
这里以上述数据举例,例如上述数据中的年龄,基本可以进行平均数填充。具体代码如下:
data['Age'].fillna(data['Age'].mean().values[0], inplace=True)
当数据近正态分布的时候,所有观测值都较好的聚集在平均值周围,这时适合填充平均值。
平均数更多的针对连续的类型填充,例如如果某属性表示一个产品的价格,那么我们可以对缺失值填充该属性其它值的平均数。
中位数填充
data['Age'].fillna(data['Age'].median().values[0], inplace=True)
偏态分布和有离群点的分布,可以使用中位数填充。
偏态分布的大部分值都聚集在变量分布的一侧,中位数可以很好的表示中心趋势。
众数填充
train_data['fuelType'].fillna(train_data['fuelType'].mode().values[0], inplace=True)
名义变量更适用于填充众数,因为此种属性一般没有大小且顺序,可以填充众数。
众数更多的针对于某种类别的填充。
📄统计量填充选择指南
我们主要针对不同的情况选择不同的统计填充。我们要根据不同数据特征的具体情况选择不同的统计填充,主要有以下几种:
- 平均值填充:最好验证一下数据特征是否符合正态分布,可以使用QQ图展示,或者直接一个直方图怼上去,后面我会撰写相关文章介绍相关内容。
- 中位数填充:有时候数据不符合正态分布,但是其基本是规律的。我们就观察其是否是某种偏态分布,因为数据是集中在中位数附近的,所以我们可以填充中位数。
- 众数填充:某些类别变量缺失时,我们可以使用众数填充。
- 其它:上述所有的填充方法一定要结合我们实际的先验知识,由于数据采样的不均衡,很有可能特征表面上看是不符合某种分布的。但是实际上这类数据我们明确的知道他就是正态分布,我们就可以使用相关手段填充。更多的,有些时候某些数据不符合正态分布时候,我们可以先对特征进行一些变化,例如对数变化等。这里就涉及到特征工程的内容,这里就不再赘述了。
📄多重插值
考虑该数据前后类似数据的填充,适合时序特征,随着时间变化的数据的填充,这里的填充是需要我们观察特征的具体变化情况选择某种方式。
例如:
# 向后填充线性插值
data.interpolate(method='linear', limit_direction='backward', axis=0)
DataFrame.interpolate(self, method=‘linear’, axis=0, limit=None, inplace=False, limit_direction=‘forward’, limit_area=None, downcast=None, **kwargs)
- method:{“线性”,“时间”,“索引”,“值”,“最近”,“零”,“线性”,“二次”,“三次”,“重心”,“克罗格”,“多项式”,“样条”,“ piecewise_polynomial”,“ from_derivatives”,“ pchip”,“ akima”}
- limit_direction:{“前进”,“后退”,“两者”},默认为“前进”
- inplace:如果可能,更新NDFrame。
📄建模预测
使用不同的模型对缺失值进行预测,下面使用KNN近邻方法对缺失的值进行预测,这部分有两种实现方法,首先是sklearn的实现:
import sklearn
# 创建该属性的存在部分作为训练集,不存在部分作为测试集
known_train_data = train_data[train_data.fuelType.notnull()]
unknown_train_data = train_data[train_data.fuelType.isnull()]
#选择目标属性作为训练集的y
y = known_train_data.loc[:, 'fuelType']
y_train = np.array(y)
#选择其它属性作为训练集的x
x = known_train_data.loc[:, 'v_1':]
x_train = np.array(x)
#在不存在的部分作为测试集
x_test = np.array(unknown_train_data.loc[:, 'v_1':])
y_test = np.array(unknown_train_data.loc[:, 'fuelType'])
#使用KNN模型进行预测,n_neighbors表示邻居个数
clf = sklearn.neighbors.KNeighborsClassifier(n_neighbors = 14, weights = "distance").fit(x_train,y_train)
test = clf.predict(x_test)
test
后边看了一下分布,整得不错,看来比较适合KNN拟合,但是要根据目标的情况改换成分类或者回归模型。
使用fancyimpute也能够帮我们完成这部分工作,当然其还提供了很多其他方法:
(这个方法我没调用成功,可能是因为有空值)
from fancyimpute import KNN, NuclearNormMinimization, SoftImpute, IterativeImputer, BiScaler
X_filled_knn = KNN(k=3).fit_transform(X_incomplete)
📄高维映射
将属性映射到高维空间,例如某属性表示性别存在部分缺失,我们直接映射到高维表示为三个离散属性分别是,是否男,是否女,是否缺失。
(若属性为连续变量,可选择先进行分箱再做后续处理。)
# train_data['notRepairedDamage'] = train_data['notRepairedDamage'].apply(pd.to_numeric, errors='coerce')
#创建两列新变量
train_data['notRepairedDamage_yes'] = 0
train_data['notRepairedDamage_none'] = 0
#train_data['notRepairedDamage_no'] = 0
# train_data.loc[ (train_data[lambda x:x['notRepairedDamage'] == 1.0]['notRepairedDamage']), 'notRepairedDamage_yes' ] = 1
# train_data.loc[ (train_data[lambda x:x['notRepairedDamage'] == 0.0]['notRepairedDamage']), 'notRepairedDamage_no' ] = 1
train_data.loc[ (train_data.notRepairedDamage.isnull()), 'notRepairedDamage_none' ] = 1
train_data.loc[ (train_data.notRepairedDamage.notnull()), 'notRepairedDamage_yes' ] = 1
print(train_data['notRepairedDamage_yes'].value_counts())
train_data['notRepairedDamage_none'].value_counts()
但是此种方法会提高我们的数据集维度,只有在样本量非常大的时候效果还好,否则会因为数据过于稀疏,效果很差。
📘总结
本次文章介绍了几种常用的缺失值填充的方法,还需要多实践多做相关工作,才能信手拈来。当然还有很多更复杂的方法,大家根据自己的业务逻辑灵活选择。
另外,从数据处理到构建高效模型系列已经全部更新完毕,感兴趣的朋友可以点击下列链接进行支持一下: