异常值与缺失值
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/e074b524dfc36d47c2c46cd2c54ae0ab.png)
1、缺失值分析与处理
1.1 造成缺失的原因
由于数据采集设备、传输线路故障等机械原因或者记录失误等认为原因,数据缺失通常难以避免,造成缺失的原因主要有以下几种
- 数据暂时无法获取
- 数据在采集过程中被遗漏或丢弃
- 某些对象的部分特征值不存在
- 获取数据比较困难
1.2 缺失的一般处理方法
- 直接删除:当少数样本存在多列特征缺失时,可以将这些样本整行删除;当某列特征大部分缺失时,可将这列属性整列删除。
- 数据填补:根据原始数据中样本分布情况可以分为以下几种
- 固定值填充:若缺失值本身含有一定的信息,则可将缺失值作为一种特征值进行标记,比如0或一些特殊字符。
- 统计值填充:将数据集中的特征分为数值特征和类别特征,数值特征可以采用其他样本的均值或中位数进行填充;类别特征可采用众数进行填充。
- 热卡填充:也称就近补齐,找到最相似的样本的特征值进行填充
- 建模填充:利用机器学习算法对缺失值进行预估,如KNN、线性回归、随机森林等。
- **不处理:**有些算法具备处理缺失值的能力,如XGBoost、LightGBM、贝叶斯网络、人工神经网络等
1.3 数据集介绍及缺失值填充
此处使用的Lending Club贷款数据集
Lending Club 创立于2006年,主营业务是为市场提供P2P贷款的平台中介服务,公司总部位于旧金山。因此合理地对用户进行信用等级划分对贷款业务有着至关重要的意义。
1.贷款标准
- FICO分数在660分以上
- .债务收入比例低于40%
- 研究影响贷款等级的相关因素,并探寻潜藏在数据背后的一些规律
2.贷款等级
贷款分为A、B、C、D、E、F、G 7 个等级,每个等级又包含了1、2、3、4、5 五个子级。
3.主要字段说明
## 导入数据集
import numpy as np # Python中进行数值计算的库
import pandas as pd # Python中进行数据处理的库
import warnings
warnings.filterwarnings('ignore') # 忽略弹出的warnings
##读取原始数据
data=pd.read_csv('./datasets/datasets/loan.csv')
data.head(10)
一共有145个特征,40万个数据
## 查看数据缺失情况
missingDf=data.isnull().sum().sort_values(ascending=False).reset_index()
missingDf.columns=['feature','miss_num']
missingDf['miss_percentage']=missingDf['miss_num']/data.shape[0] #缺失值比例
missingDf.head(10)
print('原始数据集144列特征中:\n')
print('有%d列特征含有缺失值'%missingDf[missingDf['miss_num']>0].shape[0])
print("有%d列的特征缺失值比例在30%%以上" % missingDf[missingDf['miss_percentage'] > 0.3].shape[0])
下面对数据进行填充
1.2.1 处理方法1-直接删除 适合缺失值比例过大的特征
###处理方法1-直接删除 适合缺失值比例过大的特征
thr=(1-0.3)*data.shape[0] #可以根据实际情况设定不同的阈值,此处设置为30%,则非缺失值的数量大于70%
data=data.dropna(thresh=thr,axis=1) #若某一列数据的缺失数量超过阈值就会被删除
print('去除缺失值占比大于0.3的特征之后,当前还剩%d列特征'%(data.shape[1]))
### 缺失特征过多的样本可以考虑直接整行删除
data['row_missing'] = data.apply(lambda x: x.isnull().sum(), axis=1).to_frame() # 400000万条样本,计算每条样本的缺失值列数
print(data['row_missing'].value_counts()) # 观察所有样本行的缺失值情况
data = data[data['row_missing'] < 35] # 删掉缺失值大于等于35列的样本,总计4241条,约占1%
data.drop(['row_missing'], axis=1, inplace=True) # 删掉刚刚加入原始数据集的统计列
print("去除掉缺失特征大于35列的样本之后,当前还剩%d行数据" % data.shape[0])
1.2.2 处理方法2–固定值填充
(此处有点问题)
以emp_length这列特征为例,它代表贷款客户的工作年限,统计可知其中有21805条缺失值,原始数据集中用'n/a'表示
print("原始数据集的emp_length特征取值情况:\n", data['emp_length'].value_counts(dropna=False))
data.loc[data['emp_length']=='n/a', 'emp_length'] = 'Unknown' # 将emp_length特征列中的缺失值替换为特殊字符'Unknown'
print("\n使用固定值填充后emp_length特征取值情况:\n", data['emp_length'].value_counts())
1.2.3 处理方法3-统计值填充
数值特征通常采用其他样本的均值或中位数进行填充。以dti为例,它代表贷款人每月还款占其收入的比例(去掉百分号后的值),观察其统计信息。可知该列最大值为999.000000,很明显这是一个极大的异常值,如果对该列采用均值进行填充,会受到极端值的影响,故此处选用中位数进行填充。
当然,这种简单的方法还有提升的空间,例如像dti这种特征可以结合贷款人的收入水平(annual_inc)进行先分组后填充
print("dti特征列有%d个缺失值\n" % data['dti'].isnull().sum())
print("dti特征列的统计信息:\n", data['dti'].describe())
data['dti'].fillna(data['dti'].median(), inplace=True) # 这里采用中位数填充
print("\n填充中位数后dti特征列的统计信息:\n", data['dti'].describe())
print("\n此时dti特征列有%d个缺失值\n" % data['dti'].isnull().sum())
1.2.4 处理方法4–建模填充
由于数据量太大了,电脑带不动,这里只用了部分数据
pandas.DataFrame.sample
随机选取若干行
函数名及功能
DataFrame.sample(n=None, frac=None, replace=False, weights=None, random_state=None, axis=None)[source]
输入参数说明
参数名称 | 参数说明 | 举例说明 |
---|---|---|
n | 要抽取的行数 | df.sample(n=3,random_state=1) 提取3行数据列表注意,使用random_state,以确保可重复性的例子。 |
frac | 抽取行的比例 | 例如frac=0.8,就是抽取其中80%。 df.sample(frac=0.8, replace=True, random_state=1) |
replace | 是否为有放回抽样,True:有放回抽样False:未放回抽样 | True:取行数据后,可以重复放回后再取, False:取行数据后不放回,下次取其它行数据注意: 当N>总数据容量,replace设置为值时有效 |
weights | 字符索引或概率数组 | axis=0:为行字符索引或概率数组 axis=1:为列字符索引或概率数组 |
random_state | int: 随机数发生器种子或numpy.random.RandomState | random_state=None,取得数据不重复 random_state=1,可以取得重复数据 |
axis | 选择抽取数据的行还是列 | axis=0:抽取行 axis=1:抽取列 也就是说axis=1时,在列中随机抽取n列,在axis=0时,在行中随机抽取n行。 |
print(data.shape)
rfDf=data.sample(frac=0.2)
print(rfDf.shape)
### 以revol_util(信用账户的使用率)特征为例,导入sklearn的随机森林算法预测缺失值
from sklearn.ensemble import RandomForestRegressor
# 用revol_util特征值非空的样本构建训练集,revol_util特征值缺失的样本构建测试集
rfDf_train = rfDf.loc[rfDf['revol_util'].notnull()]
rfDf_test = rfDf.loc[rfDf['revol_util'].isnull()]
col = ['loan_amnt', 'int_rate', 'installment', 'revol_bal', 'collection_recovery_fee'] # 原始数据集中的无缺失数值特征
# 划分训练数据和标签(label)
X = rfDf_train[col]
y = rfDf_train['revol_util']
# 训练过程
rf = RandomForestRegressor(n_estimators=100,n_jobs=-1) # 这里重在理解过程,因此仅简单选取部分参数
rf.fit(X, y)
# 预测过程
pred = rf.predict(rfDf_test[col])
rfDf.loc[(rfDf['revol_util'].isnull()), 'revol_util'] = pred # 填补缺失值
print("此时的revol_util特征统计指标:\n")
print(rfDf['revol_util'].describe())
2、异常值分析处理
异常值是数据集中存在的非正常的值,也成为了离群点,异常值有可能是不正确的“脏数据”,也可能是正确的异常数据,例如人的身高100m属于不正确的脏数据。
2.1 异常值的分析判别方法
- 简单统计分析
对特征进行描述性统计分析,观察是否有不合理的特征值 - 3σ原则
如图所示,如果数据服从正态分布,则与均值μ的差超过三倍标准σ的特征值,可以视为异常值,如果数据不服从正态分布,亦可以通过原理平均距离若干倍的标准差来判断,倍数的取值根据经验和实际情况确定
- 箱线图
如图所示,箱型图用到统计学中的四分位间距(IQR)概念,IQR是第三四分位数(Q3)与第一四分位数(Q1)的差值,即IQR = Q3 - Q1。 从而,低于箱型图下须(Q1-1.5IQR)或高于箱型图上须(Q3+1.5IQR)的观测值被定义为异常值。
- 建模法
可以采用线性回归(基于偏差)、聚类算法(基于密度)、KNN(基于距离)等机器学习算法进行异常值的判别
2.2 异常值的处理
- 直接删除:当异常值数量不多且易于判断时,可以直接删除
- 用均值代替异常样本
- 视为缺失值
2.2.1 异常值处理1–简单统计分析&删除异常值
Lending Club贷款数据集中的revol_util特征为例,它表示信用账户的使用率(去掉百分号),因此容易理解这列特征的值不应超过100。观察到异常样本有1300条,占总样本的比例很小,可以将这些样本直接删除
print(data.shape)
print("revol_util特征列的异常样本数为: ", data[data['revol_util'] > 100].shape[0])
#删除掉异常值
data.drop(data[data['revol_util']>100].index,inplace=True) #根据索引删除样本值
print(data.shape)
2.2.2 异常值处理2–3σ原则&均值代替异常样本
以dti特征为例,它表示贷款人每月还款占其收入的比例(去掉百分号后的值),容易理解这列特征的值不应超过100,将超过100的值视为无意义的异常值。
import seaborn as sns
import matplotlib.pyplot as plt #可视化
%matplotlib inline
ax=sns.distplot(data['dti'],kde=True,hist=False)
plt.show()
可以看出来存在很长的拖尾,根据实际意义我们可以知道大于100的都是异常值,把高于100的去掉后再观察下
#去掉高于100的部分在观察
norDf=data[data['dti']<=100]
ax=sns.distplot(norDf['dti'],kde=True,hist=False)
plt.show()
可以观察到dti的分布近似于正态分布,根据实际情况分析,大部分贷款人的dti小于40%,还款压力较小,剩下的大于40%的可能是异常值,也可能是真的存在较大的还款压力
#根据3σ原则计算异常值区间
dti_mean = norDf['dti'].mean() # 计算均值
dti_std = norDf['dti'].std() # 计算方差
print("大于 %f 的可以视为异常值" % (dti_mean + 3*dti_std))
print("小于 %f 的可以视为异常值" % (dti_mean - 3*dti_std))
data.loc[data['dti'] > 100, 'dti'] = dti_mean # 均值替代
print("此时dti特征列还有%d个大于100的特征值" % (data[data['dti'] > 100]).shape[0])
2.2.3 异常值处理3–箱型图&视为缺失值
以annual_inc为例,它表示贷款人的年收入
#绘制箱线图分析
bp_list=list(data['annual_inc'])
plt.figure(figsize=(20,4))
plt.boxplot(bp_list,
vert=False,#改变箱线图方向
flierprops = {"marker":"o","markerfacecolor":"steelblue"}
)
plt.show() # 展示箱型图
#观察annual_inc的统计值
data['annual_inc'].describe()
#计算箱型图的边界
q1=data['annual_inc'].describe()['25%']
q3=data['annual_inc'].describe()['75%']
iqr=q3-q1
print("箱型图上须:", q3 + 1.5*iqr)
print("箱型图下须:", q1 - 1.5*iqr)
可以看出大多数贷款人的年收入都在100万以下,与Lending Club的主业中小额贷款也比较相符,可以将箱型图中的大于100万的用固定值进行替代,统一定为100万
data.loc[data['annual_inc'] > 1000000.0, 'annual_inc'] = 1000000.0 # 固定值替代
print("此时annual_inc特征列还有%d个大于100万的特征值" % (data[data['annual_inc'] > 1000000.0]).shape[0])
3、优化思路
数据预处理
- 缺失值与异常值:对原始数据进行缺失值和异常值分析并进行处理
- 特征工程:特征工程对数据进行特征提取和特征选择