个人贷款用户分析(特征分析&逻辑回归)

1  项目需求背景

为了扩大贷款业务的客户人数,该银行去年针对现有的存款用户但未办理贷款业务的客户展开了一项推广活动,促使其办理个人贷款业务,取得了较好的转化效果。今年希望通过识别出更有贷款潜力的客户,提高活动转化率,降低营销费用

2  数据收集

去年营销活动的用户信息,共包含5000条记录,14个字段,对应字段含义如下:

ID - 客户

Age - 客户年龄

Experience - 客户工作经验

Income - 客户年收入(单位:千美元)

ZIPCode - 家庭地址邮政编码

Family - 客户的家庭规模

CCAvg - 每月信用卡消费额(单位:千美元)

Education - 教育水平 (1: 本科; 2: 研究生; 3: 高级)

Mortgage - 房屋抵押价值(如有)(单位:千美元)

Personal Loan - 此客户是否接受上一次活动中提供的个人贷款?(1:是 0:否)

Securities Account - 是否有证券账户?(1:是 0:否)

CD Account - 是否有存款证明(CD)帐户吗(1:是 0:否)

Online - 是否开通网上银行?(1:是 0:否)

CreditCard - 是否有信用卡?(1:是 0:否)

#导入相关库
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns

#设置全部行输出
from IPython.core.interactiveshell import InteractiveShell
InteractiveShell.ast_node_interactivity = "all"

#读取数据
data=pd.read_csv('Personal_Loan.csv')data.head()

3  数据探索和清洗

3.1  查看数据的形状、类型,处理缺失值、重复值、异常值

#数据整体情况

data.info()

-- 无缺失值,需要把Mortgage和Income更改为float类型,ZIP Code改为字符型

#变量类型转换

data.Mortgage=data.Mortgage.astype('float')

data.Income=data.Income.astype('float')

data['ZIP Code']=data['ZIP Code'].astype('str')

data.info()

#查看重复值的个数

data.duplicated().sum()

-- 无重复值

#查看异常值

data.columns

#选择散点图方式

fig = plt.figure()

plt.scatter(data.values[:,0],data.values[:,2])

-- 查看各列的散点图,Experience列出现负数值,为异常数据

#把Experience负值改为0

data.loc[data.Experience<0,'Experience']=0

3.2  描述性统计

data.describe()

4  数据预处理

4.1  去年营销活动的效果如何

# 去年营销活动效果

data.groupby('Personal Loan').size()

-- 5000名客户中,有480个客户开通了个人贷款业务,转化率9.6%,说明此次推广活动的效果还是不错的。数据源的可信度也比较高

4.2  特征筛选

4.2.1  相关系数

#把ID设为索引,ID不属于变量

data.set_index(['ID'],inplace=True)

#因变量Personal Loan跟其他变量的相关系数

cor = data.corr()['Personal Loan']

cor

cor[abs(cor)>0.05

#fig代表绘图窗口(Figure);axis代表这个绘图窗口上的坐标系(axis)

fig,axis=plt.subplots(figsize=(10,10))

#绘制热力图,颜色越深,相关性越强

ax=sns.heatmap(data.corr(),annot=True,linecolor='black',cmap="BrBG_r",fmt='.2f')

#fmt设置小数位

#bottom代表y轴下限,top表示y轴上限(matplotlib版本画热力图上下边框只显示一半)

bottom, top = ax.get_ylim()

ax.set_ylim(bottom + 0.5, top - 0.5);

由相关系数和热力图:

1.与Personal Loan强相关的变量:Income收入、CCAvg信用卡还款额和CD Account是否有是存款证明帐户

2.与Personal Loan弱相关的变量:Education受教育程度,Mortgage房屋抵押价值和Family家庭人数

3.其中Age年龄、Experience工作经验和ZIP Code家庭地址邮政编码虽然关系不大,但它们属于连续型变量,需要进一步分析。可以分箱后再做观察,看看是否有某一段存在特殊值,也可以考虑放在模型中

定性变量:CD Account是否有是存款证明帐户、Education受教育程度、Family家庭人数

定量变量:Income收入、CCAvg信用卡还款额、Mortgage房屋抵押价值

4.2.2  定性变量

# rc参数

plt.rcParams['font.sans-serif']=['SimHei']

plt.rcParams['axes.unicode_minus'] = False

#CD Account

#开通CD Account的用户贷款比例

data.groupby('CD Account')['Personal Loan'].agg(['mean','count'])

#可视化

f,ax1= plt.subplots()

sns.countplot(x='CD Account',hue='Personal Loan',data=data,ax=ax1)

ax1.set_title('用户贷款比例')#标题

-- 开通了CD Account的客户,贷款的比例远高于是没有开通的客户,说明开通CD Account的客户成为贷款业务的潜在客户可能性更大;且大量客户没有开通CD Account,找到方法让客户开通CD Account也是一个提高贷款率的可能选项

#Education

data.groupby('Education')['Personal Loan'].agg(['mean','count'])

-- 受教育水平越高,贷款的比例越高,尤其在本科以上学历差距明显,说明受教育水平高的用户成为贷款业务的潜在客户可能性更大

#Family

data.groupby('Family')['Personal Loan'].agg(['mean','count'])

-- 单身人士与没有孩子的家庭的贷款率都比较低,有孩子的家庭用户相对而言更有意愿转化为贷款用户,特别是独生子女家庭

4.2.3  定量分析-分箱

#Income收入

data['Income_bins']=pd.qcut(data.Income,20)

data.groupby('Income_bins')['Personal Loan'].agg(['mean','count'])

data.groupby('Income_bins')['Personal Loan'].mean().plot()

-- 高收入人群明显申请贷款的,收入小于64时,贷款率几乎为零,收入超过98时,贷款率急剧上升,超过170时,贷款意愿达到50%

#CCAvg信用卡还款额

data['CCAvg_bins']=pd.qcut(data.CCAvg,20)

data.groupby('CCAvg_bins')['Personal Loan'].agg(['mean','count'])

data.groupby('CCAvg_bins')['Personal Loan'].mean().plot()

-- CCAvge在2.8时,贷款率会增大将近4倍;在4.3-6.0区间会贷款率会达到45%;大于6时,贷款率会稍有回落,但贷款意愿依旧比较强

#Mortgage房屋抵押价值

data['Mortgage_bins']=pd.cut(data.Mortgage,10)  #等深分箱会有重复边界值,为0的数据占比太多

data.groupby('Mortgage_bins')['Personal Loan'].agg(['mean','count'])

plt.subplots(figsize=(15,5))

data.groupby('Mortgage_bins')['Personal Loan'].mean().plot()

-- 当Mortgage大于190.5时,贷款申请的意愿有明显的提升,总体来看,抵押值越高,贷款转化率越高。但房屋抵押价值高的客户很少

4.2.4  对相关系数低的连续型变量分箱观察

#Age年龄

data['Age_bins']=pd.qcut(data.Age,6)

data.groupby('Age_bins')['Personal Loan'].agg(['mean','count'])

data.groupby('Age_bins')['Personal Loan'].mean().plot()

-- Age在32-39区间时贷款率较高,但总体年龄跟贷款率的相关性不高

#Experience工作经验

data['Experience_bins']=pd.qcut(data.Experience,6)

data.groupby('Experience_bins')['Personal Loan'].agg(['mean','count'])

data.groupby('Experience_bins')['Personal Loan'].mean().plot()

-- Experience在7-14区间时贷款率较高,但总体工作经验跟贷款率的相关性不高

#ZIP Code家庭地址邮政编码

#邮政编码的第一个数字代表美国各州,第二个和第三个数字一起代表一个地区(或一个大城市),所以按照第2、3个数字分区

data['ZIP Code_bins']=data['ZIP Code'].str.extract('9(\d\d)',expand=False)

plt.subplots(figsize=(15,5))

data.groupby('ZIP Code_bins')['Personal Loan'].agg(['mean','count'])

data.groupby('ZIP Code_bins')['Personal Loan'].mean().plot()

-- ZIP Code各地区与贷款率相关性不高

5  模型搭建

用连续型变量和分箱两种方式测试模型的效果

5.1  用原始数据的变量

data.head(2)

最终选取加入模型的6个变量:Income收入、Family家庭人数、CCAvg信用卡还款额、Education受教育程度、Mortgage房屋抵押价值和CD Account是否有是存款证明帐户(已尝试把Experience、Age、ZIP Code加入模型,没有改善模型)

data.iloc[:,[2,4,5,6,7,10]].head()

# model_selection模型选择功能

#train_test_split 训练测试分区

#cross_val_score 交叉验证得分

from sklearn.model_selection import train_test_split,cross_val_score

#划分训练集和测试集

xtrain,xtest,ytrain,ytest=train_test_split(data.iloc[:,[2,4,5,6,7,10]],data.iloc[:,8]

                                          ,test_size=0.3)

#test_size=0.2,指定训练测试集随机抽样20%的数据

#random_state=0 随机种子数的确定

xtrain.shape

#导入包和类

from sklearn.linear_model import LogisticRegression

#实例化

log=LogisticRegression(solver="lbfgs",C=0.04,max_iter=400)

#拟合数据

log.fit(xtrain,ytrain)

log.score(xtrain,ytrain)#训练集R方

log.score(xtest,ytest)#测试集R方

y_log=log.predict(xtest)#predict_proba 预测评分,y的估计值。根据预测的概率,大于0.5的返回1

-- 正确率都是94%,考虑到数据集0、1分布极不均衡,还要看下f1指标

5.2  使用连续型变量分箱后的数据

df=data.copy()

df=df.loc[:,['Income_bins','Family','CCAvg_bins','Education','Mortgage_bins','CD Account','Personal Loan']]

df.head()

#数字编码

from sklearn.preprocessing import LabelEncoder

encoder= LabelEncoder().fit(df["Income_bins"])

df["Income_bins"] = encoder.transform(df["Income_bins"])

encoder= LabelEncoder().fit(df["CCAvg_bins"])

df["CCAvg_bins"] = encoder.transform(df["CCAvg_bins"])

encoder= LabelEncoder().fit(df["Mortgage_bins"])

df["Mortgage_bins"] = encoder.transform(df["Mortgage_bins"])

df.info() #需要的变量是数值型了

# model_selection模型选择功能

#train_test_split 训练测试分区

#cross_val_score 交叉验证得分

from sklearn.model_selection import train_test_split,cross_val_score

#划分训练集和测试集

xtrain_new,xtest_new,ytrain_new,ytest_new=train_test_split(df.iloc[:,:6],df.iloc[:,-1]

                                          ,test_size=0.3,random_state=2)

#test_size=0.2,指定训练测试集随机抽样20%的数据

#random_state=0 随机种子数的确定

xtrain_new.shape

log_new=LogisticRegression(solver="lbfgs",C=0.3,max_iter=100)

#拟合数据

log_new.fit(xtrain_new,ytrain_new)

log_new.score(xtrain_new,ytrain_new)

log_new.score(xtest_new,ytest_new)

y_log_new=log_new.predict(xtest_new)

-- 正确率也是94%,跟方法一差不多

6  模型评估

6.1  混淆矩阵预测

对模型进行评价

#方法一:

from sklearn.metrics import confusion_matrix #混淆矩阵包

from sklearn.metrics import classification_report #分类报告包

cm=confusion_matrix(ytest,y_log)#行放y,列放y的预测值,形成2*2的交叉表

print(classification_report(ytest,y_log,target_names=['非贷款','贷款']))

sns.heatmap(cm,fmt="d",cmap="icefire",annot=True)#annot将数值显示在单元格里

-- 方法一:预测正确率95%,召回率53%,精准率88%,f1是66%,该数据中1和0的比例差距较大,所以不能看正确率,主要看f1,模型总体得分较低

#方法二:

from sklearn.metrics import confusion_matrix #混淆矩阵包

from sklearn.metrics import classification_report #分类报告包

cm_new=confusion_matrix(ytest_new,y_log_new)#行放y,列放y的预测值,形成2*2的交叉表

print(classification_report(ytest_new,y_log_new,target_names=['非贷款','贷款']))

sns.heatmap(cm_new,fmt="d",cmap="icefire",annot=True)#annot将数值显示在单元格里

-- 方法二:预测正确率95%,召回率57%,精准率82%,f1是68%,比方法一得分高一点,但差距不大

6.2  交叉验证

评估模型的稳定性

#方法一:

from sklearn.model_selection import cross_val_score,LeaveOneOut,KFold,GroupKFold

x,y=data.iloc[:,[1,2,4,5,6,7,10]],data.iloc[:,8]

scores=cross_val_score(log,x,y,cv=3,scoring="f1")

print('交叉验证:%s'%scores)

print('平均交叉验证得分:%s'% np.mean(scores))

-- 方法一:交叉验证得分之差超过2%,模型过拟合

#方法二:

x_new,y_new=df.iloc[:,:6],df.iloc[:,-1]

scores_new=cross_val_score(log_new,x_new,y_new,cv=3,scoring="f1")

print('交叉验证:%s'%scores_new)

print('平均交叉验证得分:%s'% np.mean(scores_new))


-- 方法二:得分之差减小,分组后的数据让模型更稳定了

6.3  解释模型

输出回归系数和OR值

log.coef_

(np.exp(log.coef_)-1)*100

#顺序:Income Family CCAvg Education Mortgage CD Account

#输出回归系数

log_new.coef_

(np.exp(log_new.coef_)-1)*100#方法二OR值

#顺序:Income_bins Family CCAvg_bins Education Mortgage_bins CD Account

-- 方法一和方法二的OR值差别很大

6.4  用SGD模型 结合网格搜索进行调参

#建模

from sklearn.linear_model import SGDClassifier

sgd_clf=SGDClassifier(loss="log")

sgd_clf.fit(xtrain,ytrain)#拟合训练集数据

sgd_clf.score(xtest,ytest)#非监督模型是transform

from sklearn.model_selection import GridSearchCV

#参数设置

parameters=[{'penalty':['l2','l1'],'alpha':[0.04,0.05,0.06]},

            {'l1_ratio':[0.1,0.5,0.9,1]}

          ]

#线性回归中叫algha系数,logistic和svm叫C。惩罚系数的倒数,值越小,正则化越大(惩罚越大),修正过拟合,共线性

grid_search=GridSearchCV(sgd_clf,parameters,cv=3,scoring='accuracy')#scoring='accuracy'

grid_search.fit(xtrain,ytrain)

print("测试得分:%s" %grid_search.score(xtest,ytest))

print("全部及最优系数:%s" %grid_search.best_estimator_)

-- 方法一最优正则化系数是0.04,迭代正则化系数C

#建模

sgd_clf=SGDClassifier(loss="log")

sgd_clf.fit(xtrain_new,ytrain_new)#拟合训练集数据

sgd_clf.score(xtest_new,ytest_new)#非监督模型是transform


#参数设置

parameters=[{'penalty':['l2','l1'],'alpha':[0.04,0.05,0.06]},

            {'l1_ratio':[0.1,0.5,0.9,1]}

          ]

#线性回归中叫algha系数,logistic和svm叫C。惩罚系数的倒数,值越小,正则化越大(惩罚越大),修正过拟合,共线性

grid_search=GridSearchCV(sgd_clf,parameters,cv=3,scoring='accuracy')#scoring='accuracy'

grid_search.fit(xtrain_new,ytrain_new)

print("测试得分:%s" %grid_search.score(xtest_new,ytest_new))

print("全部及最优系数:%s" %grid_search.best_estimator_)

-- 方法二最优正则化系数是0.0001,迭代正则化系数C(但是这个系数迭代过去效果并不好,疑问?)

7  结论

7.1  相关性部分

更容易转化为贷款客户的用户有如下特征:

· 开通了银行账户的用户相对于没有开通银行账户贷款意愿更强

· 教育水平越高的客户越容易接受贷款

· 家庭人口较多的家庭贷款意愿较强,尤其是独生子女的家庭

· 年龄区间在30-40岁的客户相对贷款意愿更强

· 相对收入越高,贷款的意愿越强烈, 当年收入超过82时,贷款意愿会有5倍以上的上升,超过98时,贷款意愿达到17%以上,超过170时,贷款意愿达到一半

· 当房屋抵押值大于190.5千美元时,贷款申请的意愿有明显的提升,但房屋抵押价值高的客户很少

· 每月消费额在2.8千美元以上的客户,贷款申请的意愿有明显的提升

7.2  模型部分

模型总体得分较低,OR值过高,还需要找原因优化,以下只是基于目前不成熟分析的结论

分组后的数据稳定性更好,目前得到的预测正确率95%,召回率57%,精准率82%,f1是68%

在其他条件不变的情况下:

· Income_bins增加一个单位,贷款率提高143%;

· Family增加一个单位,贷款率提高130%;

· CCAvg_bins增加一个单位,贷款率提高6%;

· Education增加一个单位,贷款率提高460%;

· Mortgage_bins增加一个单位,贷款率提高4%;

· CD Account增加一个单位,贷款率提高614%

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,012评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,628评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,653评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,485评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,574评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,590评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,596评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,340评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,794评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,102评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,276评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,940评论 5 339
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,583评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,201评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,441评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,173评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,136评论 2 352