由于数据采集设备、传输线路故障等机械原因或者记录失误等认为原因,数据缺失通常难以避免,造成缺失的原因主要有以下几种
此处使用的Lending Club贷款数据集
Lending Club 创立于2006年,主营业务是为市场提供P2P贷款的平台中介服务,公司总部位于旧金山。因此合理地对用户进行信用等级划分对贷款业务有着至关重要的意义。
1.贷款标准
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)
## 查看数据缺失情况
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-直接删除 适合缺失值比例过大的特征
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])
(此处有点问题)
以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())
数值特征通常采用其他样本的均值或中位数进行填充。以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())
由于数据量太大了,电脑带不动,这里只用了部分数据
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())
异常值是数据集中存在的非正常的值,也成为了离群点,异常值有可能是不正确的“脏数据”,也可能是正确的异常数据,例如人的身高100m属于不正确的脏数据。
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)
以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])
以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])
数据预处理
https://work.datafountain.cn/forum?id=68&type=2&source=1