一、案例背景
在上一篇文章网络贷款违约预测案例中,我们在分析属性关系时发现:FICO信用评分与分类标签之间存在极强的相关关系。如果说要选择一个属性来区分客户是否违约贷款的话,FICO评分是最理想之选。
FICO评分的主要思路是:对大量拥有多个属性的用户数据进行收集、分析、转换,使用各项统计指标对属性进行取舍、赋权、组合,最终得到一个量化的、综合的、可用于比对的分值。分值的高低既反映了用户历史信用记录的好坏,又暗示未来违约可能性的大小。
类似的,我们要对数据清洗之后才能进行模型训练和预测。其中,首先要重点说到的是数据分箱。
二、前置知识
2.1 数据分箱
数据分箱就是将采集到的某一个属性的数据取值划分为若干段,落在同一个箱体范围内的数据,用一个统一的数字代替。例如,{1,2,3,4,5,6,7,8,9}这个数据集,若分为3段,则1:{1,2,3}、2:{4,5,6}、3:{7,8,9};取均值代替原来的数据,最终为:箱1:{2,2,2}、箱2:{5,5,5}、箱1:{8,8,8}。
数据分箱的目的是:变量变换到了相似的尺度上,便于比较;后续逻辑回归计算量减少,降低模型过拟合风险;模型更稳定,不会因为少量数据的变化导致结果大幅波动。
2.2 属性选择
在数据分析中,为了降低复杂度,往往要对数据集中的属性进行取舍,排除冗杂多余的属性,除了相关关系之外,还可以用Woe(迹象权重)和IV(信息值)指标来考察属性对目标变量的重要程度。
其中,pctlGood(pctlBad)表示好的、分类标签为正常的(坏的、分类标签为违约的)占所有的比重。
然后根据IV的取值来判断研究的属性与目标变量之间的关系:
IV | 关系 |
---|---|
(0,0.02) | 极弱 |
[0.02,0.1) | 弱 |
[0.1,0.3) | 一般 |
[0.3,0.5) | 强 |
[0.5,1.0) | 极强 |
最后,根据自己的需求,舍弃关系为弱以下或极弱的属性。
2.3 回归方程
由于信用评分是一个数值,二分类标签是正常/违约这样离散的量,因此需要用到之前逻辑回归中学习到的Sigmoid函数:
将连续值结果与阀值大小对比的结果转化为离散的分类标签,以此为基础得到用于计算信用评分的表达式。
三、数据处理
案例数据下载地址为:https://www.kaggle.com/c/GiveMeSomeCredit/data
数据包含11个属性:
变量名称 | 变量描述 | 数据类型 |
---|---|---|
SeriousDlqin2yrs | 逾期90天及以上 | 布尔值 |
RevolvingUtilizationOfUnsecuredLines | 信用卡和个人信用额度的总余额除以总信用额度,除了房地产和没有分期付款债务,如汽车贷款 | 浮点型 |
age | 借款人的年龄 | 整数型 |
NumberOfTime30-59DaysPastDueNotWorse | 借款人逾期处于到期日后30-59天内的逾期次数 | 整数型 |
DebtRatio | 每月还债、赡养费和生活费除以月总收入 | 浮点型 |
MonthlyIncome | 月收入 | 整数型 |
NumberOfOpenCreditLinesAndLoans | 贷款数量,如分期付款,汽车贷款或抵押贷款以及信用贷款(如信用卡) | 整数型 |
NumberOfTimes90DaysLate | 借款人逾期90天或以上的次数 | 整数型 |
NumberRealEstateLoansOrLines | 抵押贷款和房地产贷款的数目,包括房屋净值信贷额度 | 整数型 |
NumberOfTime60-89DaysPastDueNotWorse | 借款人逾期处于到期日后60-89天的逾期次数 | 整数型 |
NumberOfDependents | 家庭中不包括自己的受抚养人数(配偶、子女等) | 整数型 |
其中,第一项为分类标签,其他为变量参数。
3.1 数据清洗
首先,观察数据:
data = pd.read_csv('ch17_cs_training.csv')
print('原始数据概况:')
data.info()
发现部分属性数据缺失严重,需要进行处理:
from sklearn.ensemble import RandomForestRegressor
# 用随机森林对MonthlyIncome月收入缺失值进行预测填充
#参数: df-Daraframe,Pandas数据框
#返回值:df-Daraframe,填充了MonthlyIncome缺失值的数据框
def set_missing(df):
#将第5列MonthlyIncome提前到第0列,便于后续划分数据
print('随机森林回归填充0值:')
process_df = df.iloc[:,[5,0,1,2,3,4,6,7,8,9]]
#分成有数值/缺失值两组
known = process_df.loc[process_df['MonthlyIncome']!=0].values
unknown = process_df.loc[process_df['MonthlyIncome']==0].values
X = known[:, 1:]
y = known[:, 0]
#用X,y训练随机森林回归算法
rfr = RandomForestRegressor(random_state=0, n_estimators=200,max_depth=3,n_jobs=-1)
rfr.fit(X,y)
#得到的模型进行缺失值预测
predicted = rfr.predict(unknown[:, 1:]).round(0)
#用得到的预测结果填补原缺失数据
df.loc[df['MonthlyIncome']==0, 'MonthlyIncome'] = predicted
return df
这里选择了用随机森林回归算法对缺失值进行填充。
#离群点检测、删除,删除标准为:
#最小阈值=第一四分位点-1.5*(第三四分位点-第一四分位点)
#最大阈值=第三四分位点+1.5*(第三四分位点-第一四分位点)
#小于最小阈值,大于最大阈值的行将会被删除
#参数: df-Daraframe,Pandas数据框
# cname-字符串,进行离群点删除的列名
#返回值:df-Daraframe,完成了离群点检测删除的数据框
def outlier_processing(df,cname):
s=df[cname]
oneQuoter=s.quantile(0.25)
threeQuote=s.quantile(0.75)
irq=threeQuote-oneQuoter
min=oneQuoter-1.5*irq
max=threeQuote+1.5*irq
df=df[df[cname]<=max]
df=df[df[cname]>=min]
return df
然后,定义函数对属性中的离群数据点(异常值)进行删除,如对MonthlyIncome
列进行处理:
#对MonthlyIncome列进行数据整理
print('MonthlyIncome属性离群点原始分布:')
data[['MonthlyIncome']].boxplot()
plt.savefig('ch17_cs01.png', dpi=300, bbox_inches='tight')
plt.show()
print('删除离群点、填充缺失数据:')
data=outlier_processing(data,'MonthlyIncome')
data=set_missing(data)
print('处理MonthlyIncome后数据概况:')
data.info()
data[['MonthlyIncome']].boxplot()
plt.savefig('ch17_cs02.png', dpi=300, bbox_inches='tight')
plt.show()
同样,也可对其他的属性进行处理,但是如果处理之后发现被错误的删除大量的数据,则不能用函数处理,需要手工处理:
#以下三个属性取值过于集中,三个四分位点的值都相等。
#直接使用outlier_processing函数会导致所有取值被删除,
#因此观察分布后,手工处理
Features=['NumberOfTime30-59DaysPastDueNotWorse',
'NumberOfTime60-89DaysPastDueNotWorse',
'NumberOfTimes90DaysLate']
Features_labale=['30-59Days','60-89Days','90+Days']
print('NumberOfTime30-59DaysPastDueNotWorse,\
NumberOfTime60-89DaysPastDueNotWorse,\
NumberOfTimes90DaysLate原始分布:')
data[Features].boxplot()
plt.xticks([1,2,3],Features_labale)
plt.savefig('ch17_cs03.png', dpi=300, bbox_inches='tight')
plt.show()
print('删除离群点后:')
data= data[data['NumberOfTime30-59DaysPastDueNotWorse'] < 90]
data= data[data['NumberOfTime60-89DaysPastDueNotWorse'] < 90]
data= data[data['NumberOfTimes90DaysLate'] < 90]
data[Features].boxplot()
plt.xticks([1,2