案例背景
案例来源《python数据科学:技术详解与商业实践》数据下载地址
该案例使用一套来自某银行真实数据集构建贷款违约预测模型
本案例遵循数据挖掘项目通用流程CRISP-DM进行建模,流程如下:
- 项目理解
- 数据理解
- 数据准备
- 模型构建与评价
- 模型监控
1.项目理解
原始数据中Loans.status表示贷款客户的还款状态,其中A代表合同终止,没问题;B代表合同终止,贷款没有支付;C代表合同处于执行期,至今 正常;D代表合同处于执行期,欠债状态。A类客户表示客户正常履行完合同无违约行为,BD类客户表示有违约行为,通过ABD类客户的个人信息,状态信息以及行为信息,预测C类客户在还款期内是否会发生违约行为
项目预测变量为客户是否违约,(违约为1,不违约为0)为二分类变量,因此需要构建一个排序类分类模型,在排序类分类模型中,由于逻辑回归容易实现,可解释性强,为最常用排序类分类模型
2.数据理解
数据提供9张表,涉及客户主记录、帐号、交易、业务和信 用卡数据;
-
账户表(Accounts)
每条记录描述一个账户的静态信息,共4500条
-
客户信息表(Clients)
每条记录描述了一个客户的特征信息,共 5369 条
-
权限分配表(Disp)
每条记录描述了客户和账户之间的关系,以及客户操作账户的权限 ,共5369条
-
支付订单表 (Orders)
每条记录代表描述了一个支付命令,共6471条
-
交易表 (Trans)
每条记录代表每个账户上的一条交易,共1056320条
-
贷款表(Loans)
每条记录代表某个账户的上的一条贷款信息,共682条
-
信用卡(Cards)
每条记录描述了一个账户上的信用卡信息 ,共 892 条
-
人口地区统计表 (District)
每条记录描述了一个地区的人口统计学信息,共77条
3.数据准备
在筛选前我们要考虑一个时间窗口的问题,简单讲就是银行给用户放款后,在下一个还款期银行再记录用户是否发生逾期,由于客户一年前的状态信息比较有参考价值,这里根据放款时间来划分时间窗口,选取的记录为放款时间之前一年,构建每个表的放款前特征。
基于业务理解及数据可靠度,可信度提取出如下解释变量
os.chdir(r'C:\Users\Administrator\Desktop\Python_book\19Case\19_1Bankcredit')
createVar = locals()
for i in os.listdir():
if i.split('.')[1] == 'csv':
createVar[i.split('.')[0]] = pd.read_csv(i,encoding='gbk')
print(i.split('.')[0])
loans['bad_good'] = loans.status.map({'B':1,'A':0,'D':1,'C':2})#定义因变量
loans.head()
disp = disp[disp['type']=='所有者']
data1 = pd.merge(loans,disp,how ='left',on='account_id')#将客户贷款表作为主表,链接权限分配表
data2 = pd.merge(data1,clients,how='left',on='client_id')#通过权限表上面的client_id链接客户信息表
data3 = pd.merge(data2,card,how='left',on='disp_id')#链接信用卡信息
data3.head()
data4 = pd.merge(data3,district,how='left',left_on='district_id',right_on='A1')#链接地区状态信息
data4['date'] = pd.to_datetime(data4['date'])
data4['birth_date'] = pd.to_datetime(data4['birth_date'])#将字符串转换为datatime时间序列
data4['age'] = round((data4['date']-data4['birth_date'])/np.timedelta64(365,'D'),0)#筛选出年龄
data4.head()
#筛选出所需的个人信息和状态信息
data4.columns
data4 = data4[['account_id','amount', 'duration','payments','bad_good','sex','type_y', 'GDP',
'A4', 'A10', 'A11', 'A12', 'A13', 'A14', 'A15', 'a16', 'age']]
data4.head()
#筛选出贷款前1年的交易数据
df = pd.merge(trans,loans,on='account_id')
df['date_x']= pd.to_datetime(df['date_x'])
df['date_y']= pd.to_datetime(df['date_y'])
#amount_x和balance为字符串类型,将其转化为数值类型
df['amount_x'] = df['amount_x'].map(lambda x:''.join(x[1:].split(','))).astype(int)
df['balance'] = df['balance'].map(lambda x:''.join(x[1:].split(','))).astype(int)
#筛选出放款日前1年内的至前一天的交易记录
df = df[(df['date_x']df['date_y'])]
#筛选用户一年内结息总额
df1 = df[df['k_symbol']=='利息所得']
df1 = pd.DataFrame(df1.groupby('account_id')['amount_x'].sum())
df1.columns =['interest']
#赛选在本行是否有养老金和房屋贷款
df2 = df[(df['k_symbol']=='养老金')| (df['k_symbol']=='房屋贷款')]
#标记是否在本行有房屋贷款
df2 = pd.DataFrame(df2.groupby('account_id')['k_symbol'].count())
df2['house_loan'] = '1'
del df2['k_symbol']
#筛选客户一年内收入和支出(总和)
df3 = pd.DataFrame(df.pivot_table(values='amount_x',index='account_id',columns='type',aggfunc=np.sum))
df3.columns = ['out','income']
#筛选客户一年内余额的均值和标准差
df4 = pd.DataFrame(df.groupby('account_id')['balance'].agg(['mean','std']))
df4.columns = ['balance_mean','balance_std']
#合并数据
data = pd.merge(df1,df2,how='left',left_index=True,right_index=True)
data = pd.merge(data,df3,left_index=True,right_index=True)
data = pd.merge(data,df4,left_index=True,right_index=True)
len(data)#查看数据条数是否与贷款表条数一致
data_modle = pd.merge(data4,data,left_on='account_id',right_index=True)
4.模型构建与评价
该步骤按照SEMMA标准算法,分为数据采样,变量分析探索,修改变量,构建逻辑回归,评价模型的优劣,由于原始数据已经准备好,直接跳过第一步。
4.1变量分析探索
查看数据缺失情况
data_modle.isnull().sum()/len(data_modle)
其中type_y信用卡类型缺失率75%,该变量没有可解释性,故删除
A12 1995年失业率 和 A15 1995犯罪率(千人) 有极小部分数据缺失,填充中位数
house_loan 是否有房屋贷款,缺失值为没有房屋贷款,填充字符’0‘
data_modle['A12'].fillna(data_modle['A12'].median(),inplace=True)
data_modle['A15'].fillna(data_modle['A12'].median(),inplace=True)
data_modle['house_loan'].fillna('0',inplace=True)
del data_modle['type_y']
将变量按照变量类型进行分类
#因变量
y= 'bad_good'
#连续变量
var_c = ['amount', 'duration', 'payments', 'GDP',
'A4', 'A10', 'A11', 'A12', 'A13', 'A14', 'A15', 'a16', 'age',
'interest', 'out', 'income', 'balance_mean',
'balance_std']
#分类变量
var_d = ['sex','house_loan']
两个分类变量sex和house_loan 属于二分类变量,将其二值化
data_modle['sex'] = data_modle['sex'].map({'男':1,'女':0})
data_modle['house_loan'] = data_modle['house_loan'].map({'1':1,'0':0})
查看各变量间的相关性
corr = data_modle[var_c+var_d].corr()
plt.figure(figsize=(12,9))
sns.heatmap(corr,vmax=1,annot=True)
4.2修改变量
从热力图中可以看出贷款信息,居住地信息,经济状况信息内各变量具有高相关性,需要通过选择其中一个变量或者变量转换筛选变量进入模型
贷款信息中,保留amount贷款金额变量(应该通过变量转换 (月供*贷款期数-贷款金额)/贷款金额 计算出贷款利率,但是本案例通过计算利率为0,所以只保留贷款金额)
居住地信息,同过转换保留两个变量人均GDP(反应当地经济状况),失业增长率(一定程度上反应当地GDP增长水平)
经济状况信息,通过变量转换保留三个变量,客户放款前近一年总结息(一定程度上反应客户实际存款数额大小),收支比(消费占收入比重,一定程度反应客户消费水平),可用余额变异系数(客户结余的波动,一定程度上反应客户生活状态稳定系数)
data_modle['GDP_per'] = data_modle['GDP']/data_modle['A4']#人均GDP人民生活水平的一个标准
data_modle['unemployment'] = data_modle['A13']/data_modle['A12']#失业增长率一定程度上反应经济增长率
data_modle['out/in'] = data_modle['out']/data_modle['income']#消费占收入比重,一定程度反应客户消费水平
data_modle['balance_a'] = data_modle['balance_std']/data_modle['balance_mean']#可用余额变异系数
var = ['sex','age','amount','GDP_per','unemployment','out/in','balance_a']
4.3构建逻辑回归
- 提取状态为2(合同为履行完正常还款客户)的样本用于预测,其他样本随机采样,建立训练集和测试集,比例4:1
data_model = data_modle[var+[y]]
for_predict = data_model[data_model[y]==2]
data_model = data_model[data_model[y]!=2]
#定义自变量和因变量
X= np.array(data_model[var])
Y = np.array(data_model[y])
#将样本数据建立训练集和测试集
from sklearn.model_selection import train_test_split
x_train, x_test, y_train, y_test = train_test_split(X, Y, test_size = 0.2, random_state = 1234)
- 使用逻辑回归L1正则化参数,建模
L1正则化可以是一些不重要的变量回归系数变零,达到筛选变量的目的
参考:https://zhuanlan.zhihu.com/p/25707761
from sklearn.linear_model import LogisticRegression
LR = LogisticRegression(penalty='l1')
clf = LR.fit(x_train,y_train)
y_pred = clf.predict(x_test)#预测测试集数据
clf.coef_#查看各变量的回归系数
通过查看各变量的回归系数可以看到'sex'和'GDP_per'字段被删除
- 模型结果评价
from sklearn.metrics import classification_report
print(classification_report(y_test, y_pred))
模型的精确率0.87,召回率0.84,f1_score为0.82
绘制ROC曲线
from sklearn.metrics import roc_curve, auc
fpr, tpr, threshold = roc_curve(y_test, y_pred)
roc_auc = auc(fpr, tpr)
plt.plot(fpr, tpr, color='darkorange',label='ROC curve (area = %0.2f)' % roc_auc)
plt.plot([0, 1], [0, 1], color='navy', linestyle='--')
plt.xlim([0.0, 1.0])
plt.ylim([0.0, 1.0])
plt.xlabel('False Positive Rate')
plt.ylabel('True Positive Rate')
plt.title('ROC_curve')
plt.legend(loc="lower right")
plt.show()
AUC面积76%模型的解释力度,一般要求在75%以上
总结
- 本项目按照CRISP-DM进行建模,建模过程遵循SEMMA标准算法
- 本项目重点在宽表展开,数据提取以及衍生变量的选择.进一步提升模型解释度,变量的选择很重要,选取的几个衍生变量也是根据自己的理解选择的.实际中还是要对业务有深刻理解.
- 特征变量的筛选还可以选择filter法和Wrapper法,参考https://blog.csdn.net/cymy001/article/details/79425960
- 提高模型精度可选用组合算法或者强模型,但可解释度低