本案例使用的是来自UCI网站上的中国台湾地区信用卡客户数据,包含了2005年4月到2005年9月客户的人口统计特征、信用数据、历史还款、账单等信息。目的是对客户下个月是否违约做出预测。原始数据格式是csv
,一共有25个列:
补充说明:
PAY_1~PAY_6
:PAY_1
为2005年9月的还款情况;PAY_2
为2005年8月的还款情况;…;PAY_6
为2005年4月的还款情况。BILL_AMT1~BILL_AMT6
和PAY_AMT1~PAY_AMT6
中数字标识的含义也是如此。PAY_1~PAY_6
的取值含义为:0 = 及时还;1 = 还款延迟一个月;2 = 还款延迟两个月;3 = 还款延迟三个月;…;9 = 还款延迟九个月及以上。 每月的支付金额PAY_AMT
不能低于银行规定的当月最低还款额,否则就是违约。如果支付金额PAY_AMT
大于上月账单金额BILL_AMT
则视为及时还,剩余金额存入信用卡留做下次消费;如果支付金额小于上月账单金额但高于最低还款额则视为延迟还款。
导入我们需要的包:
import pandas as pd
import matplotlib
import matplotlib.pyplot as plt
import seaborn as sns
%matplotlib inline
查看数据集的大小,并预览前几行数据,确认数据是否正确导入。
print("信用卡客户数据,共有%d行,%d列。" % (client_info.shape[0], client_info.shape[1]))
client_info.head()
通过describe
方法大致了解数据的统计特征。
client_info.describe()
通过上面的统计结果,我们知道数据集中一共有30,000个信用卡客户。从信用额度看,平均信用额度是167,484台币,最小的是1万台币,最大的达到了100万台币,波动非常大;从年龄来看,平均年龄是35.4岁。default.payment.next.month
的平均值为0.221,由于下个月不违约记为0,违约记为1,所以平均值0.221意味着有22.1%的客户下个月将会违约(会在接下来的分析中验证)。
检查数据是否存在缺失现象。
total = client_info.isnull().sum().sort_values(ascending = False)
percent = (client_info.isnull().sum()/client_info.count()*100).sort_values(ascending = False)
pd.concat([total, percent], axis=1, keys=['Total', 'Percent']).transpose()
数据非常完整,所有的变量都不存在缺失。
检验目标变量 default.payment.next.month
的正负样本数量是否大致相等。
client_info["default.payment.next.month"].value_counts()
有6636个客户下个月将会违约,占比22%,远小于未违约用户数量,所以数据中存在着明显的正负样本不平衡问题。
接下来我们将进行双变量分析,观察不同特征对违约是否有显著影响。
2.4.1 信用额度
观察信用额度的分布。
# 初始化图表的画布、字体等参数
sns.set(rc={'figure.figsize':(13,5),"font.size":15,'font.sans-serif':['SimHei'],"axes.titlesize":15,"axes.labelsize":15})
plt.title('信用额度分布图')
sns.set_color_codes("pastel")
sns.distplot(client_info['LIMIT_BAL'],kde=True,bins=200, color="blue")
plt.show()
从这个图可以看到大部分客户的信用额度集中在1万到20万。不过我们更关心的是:按照下个月是否会违约将客户分组,这两个组客户的信用额度的分布情况。
ax = sns.boxplot(x='default.payment.next.month', y='LIMIT_BAL', data=client_info)
ax.set_xticklabels(['未违约', '违约'])
plt.show()
可以观察到,违约的客户其整体信用额度偏低,也就是说信用额度这个变量对预测客户下月是否违约是有帮助的。
2.4.2 性别
先通过饼状图观察男女比例:
sex_count = client_info['SEX'].value_counts()
sex_count.plot(kind='pie', labels=['女', '男'], autopct='%1.1f%%', textprops={'fontsize': 12})
plt.show()
女性客户更多,那男女客户的违约比例是否有差异呢,我们也用可视化的方法来描绘:
ax = sns.countplot(x='SEX',hue='default.payment.next.month',data = client_info)
ax.set_xticklabels(['男', '女'])
plt.show()
从上图可以观察到,女性违约客户在绝对数量上要比男性多,但是比例上比男性少。如果视觉上不好观察比例,可以将客户按性别分组进一步计算违约比例:
def func(group):
default_count = group['default.payment.next.month'].value_counts().get(1, 0)
return default_count/group['default.payment.next.month'].count()
client_info.groupby(by=['SEX']).apply(func)
男性客户的违约比例为24.2%,女性为20.8%,这表明男性比女性更容易违约。
2.4.3 教育程度
先观察客户教育程度的分布占比:
education_count = client_info['EDUCATION'].value_counts()
education_count.plot(kind='pie', autopct='%1.1f%%', title='客户不同教育程度占比',labels=['大学生', '研究生及以上', '高中生', '其他'], textprops={'fontsize': 12})
plt.show()
从教育水平角度来看,大学生所占比重最大,研究生次之。再观察不同教育程度的客户的违约情况:
ax = sns.countplot(x='EDUCATION',hue='default.payment.next.month',data = client_info)
ax.set_xticklabels(['研究生及以上', '大学生', '高中生', '其他'])
plt.show()
从图中观察不出明显的差别,计算违约比例:
client_info.groupby(by=['EDUCATION']).apply(func)
教育程度为高中生的客户违约比例高达25.2%,高于本科23.7%,高于研究生19.2%,受教育程度越高违约率越低,所以教育程度也是一个有帮助的变量。(注:4代表其他,不做讨论。)
2.4.4 婚姻状态
客户婚姻状态的分布比例:
marriage_count = client_info['MARRIAGE'].value_counts()
marriage_count.plot(kind='pie', autopct='%1.1f%%', title='婚姻状态占比',labels=['未婚', '已婚', '其它'],textprops={'fontsize': 12})
plt.show()
从婚姻角度来看,未婚所占比重大于已婚所占比重。我们再来观察已婚和未婚客户的违约比例是否有差别:
ax = sns.countplot(x='MARRIAGE',hue='default.payment.next.month',data = client_info)
ax.set_xticklabels(['已婚', '未婚', '其他'])
plt.show()
虽然已婚和未婚的人数差距明显,但是违约人数相差不大,进一步计算一下不同婚姻状态的违约占比:
client_info.groupby(by=['MARRIAGE']).apply(func)
已婚记为1,未婚记为2,上面的数据表明已婚的客户更容易违约。
数据探索部分就先到这里了,上面这些分析已经足够了,当然你也可以对其余的变量做更多的分析,但这不是这篇案例的重点。
数据中存在多个分类变量:EDUCATION
, MARRIAGE
, PAY_1~PAY_6
,可以先将它们转换成哑变量。
cat_features = ['EDUCATION', 'MARRIAGE', 'PAY_1', 'PAY_2', 'PAY_3', 'PAY_4','PAY_5', 'PAY_6']
client_info_dummy = pd.get_dummies(client_info, columns = cat_features)
client_info_dummy.columns
现在我们来定义目标变量和预测变量,目标变量是default.payment.next.month
,除去ID
剩下的变量都作为预测变量。
client_info_dummy.drop('ID', axis=1, inplace=True)
predictors = client_info_dummy.columns.tolist()
predictors.remove('default.payment.next.month')
print('一共使用了%d个特征。'% len(predictors))
y = client_info_dummy['default.payment.next.month']
X = client_info_dummy[predictors]
一共使用了74个特征。
我们一共使用了74个变量作为预测目标值的特征。
将数据集划分为训练集和测试集,分别用于模型训练和评估,比例是4:1,所以将test_size
设为0.2,通过设置shuffle
为True
将数据随机打乱。并将 random_state
(随机数种子)设置为固定的值,保证每次随机的结果都是一样的。
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=2019, shuffle=True )
4.1.1 模型建立和训练
我们先尝试逻辑回归模型,它是相对简单的线性模型。逻辑回归模型要调试的参数主要是惩罚系数C
,我们选择带有交叉验证的逻辑回归模型LogisticRegressionCV
来选择最合适的惩罚系数。默认设置下,该模型从0.0001~10000选择十个值作为惩罚系数,通过交叉验证选择表现最好的。惩罚项设置为l2
正则化。由于数据量不是很大,求解器选择lbfgs
,训练速度更快。并行任务设置为4。
from sklearn.linear_model import LogisticRegressionCV
clf = LogisticRegressionCV(penalty='l2',solver='lbfgs',n_jobs=4)
clf.fit(X_train, y_train)
4.1.2 模型预测和评价
评价指标
我们使用AUC作为评价模型表现的指标,AUC(Area under Curve)是指ROC曲线下的面积,越大越好,最大是1。ROC曲线是根据混淆矩阵得来的,横坐标是假正率(False positive rate,FPR),纵坐标是真正率(True positive rate,TPR)。ROC曲线有个很好的特性:当测试集中的正负样本的分布变化的时候,ROC曲线能够保持不变即AUC保持不变,这意味着评价结果会更准确。《机器学习之分类性能度量指标 : ROC曲线、AUC值、正确率、召回率》有关于ROC和AUC更详细的介绍。
from sklearn.metrics import roc_auc_score
利用sklearn自带的roc_auc_score
计算这个AUC时,需要两个参数:测试集的真实类别标签,模型预测的正类概率。在我们的例子中,正类概率就是客户下月违约的概率。在sklearn中,模型训练好后,调用模型的predict_proba
方法能够获得测试集的正类概率。注意:部分分类模型不支持这个方法。
preds = clf.predict_proba(X_test)[:,1]
roc_auc_score(y_test, preds)
Logistic回归模型的AUC得分是0.642。
4.1.3 混淆矩阵
利用sklearn的confusion_matrix
方法可以方便地计算出混淆矩阵,观察分类器在不同类别上的表现。
from sklearn.metrics import confusion_matrix
preds = clf.predict(X_test)
cm = confusion_matrix(y_test, preds)
fig, ax = plt.subplots(ncols=1, figsize=(5,5))
sns.heatmap(cm, annot=True,ax=ax, fmt='d',
xticklabels=['未违约', '违约'],
yticklabels=['未违约', '违约'],
linewidths=.2,linecolor="Darkblue", cmap="Blues",annot_kws={'size': 15})
plt.title('混淆矩阵', fontsize=14)
plt.show()
从混淆矩阵可以观察到,逻辑回归模型的表现非常糟糕,它几乎把所有点都预测为未违约,这是数据不平衡时模型最容易犯的的错误:倾向于将样本预测为样本占比多的一类。
4.1.4 标准化的力量
有的同学可能发现在前面的数据预处理没有做标准化,是的,将数值变量标准化对于线性模型或者使用了梯度下降的模型来说非常重要。
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
numeric_features = ['LIMIT_BAL', 'AGE','BILL_AMT1', 'BILL_AMT2', 'BILL_AMT3',
'BILL_AMT4', 'BILL_AMT5', 'BILL_AMT6', 'PAY_AMT1', 'PAY_AMT2',
'PAY_AMT3', 'PAY_AMT4', 'PAY_AMT5', 'PAY_AMT6']
client_info_dummy[numeric_features] = scaler.fit_transform(client_info_dummy[numeric_features])
X = client_info_dummy[predictors]
y = client_info_dummy['default.payment.next.month']
X_train,X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=2019, shuffle=True )
我们再次利用逻辑回归模型来训练和预测、。
clf.fit(X_train, y_train)
preds = clf.predict_proba(X_test)[:,1]
roc_auc_score(y_test, preds)
这个提升非常明显,AUC提高到了0.763。同样我们用混淆矩阵来验证一下。
preds = clf.predict(X_test)
cm = confusion_matrix(y_test, preds)
fig, ax = plt.subplots(ncols=1, figsize=(5,5))
sns.heatmap(cm,
xticklabels=['未违约', '违约'],
yticklabels=['未违约', '违约'],
annot=True,ax=ax, fmt='d',
linewidths=.2,linecolor="Darkblue", cmap="Blues",annot_kws={'size': 15})
plt.title('混淆矩阵', fontsize=14)
plt.show()
可以看到,通过标准化逻辑回归模型的表现得到了显著的提升。不过,由于算法的差异,下面的随机森林和AdaBoost不受标准化的影响。有兴趣的同学可以自己实验一下。
4.2.1 模型建立和训练
先初始化随机森林模型,参数设置:n_estimators
是指决策树的数量,一般越大越好,但是超过一定数量后对模型提升并不显著并且训练需要花费更多时间,我们将其设置为400;分类问题中,使 max_features
= sqrt(n_features)
是比较好的默认值,其中 n_features
是特征的个数,我们一共有86个特征,所以将我们将 max_features
设置为8。由于数据不平衡,我们可以设置class_weight
来提高将违约样本的权重,未违约样本的数量大概是违约样本的3.5倍,所以我们尝试将违约样本的权重设置为3.5。
from sklearn.ensemble import RandomForestClassifier
clf = RandomForestClassifier(n_jobs=4,
random_state=2019,
n_estimators=400,
max_features=8,
class_weight={1:3.5}
)
clf.fit(X_train, y_train)
4.2.2 模型预测和评价
利用测试集来计算AUC。
preds = clf.predict_proba(X_test)[:,1]
roc_auc_score(y_test, preds)
随机森林的AUC得分是0.774。
4.2.3 特征重要性
随机森林还可以让我们知道不同变量的重要性,我们将其可视化出来。
tmp = pd.DataFrame({'Feature': predictors, 'Feature importance': clf.feature_importances_})
tmp = tmp.sort_values(by='Feature importance',ascending=False)
plt.figure(figsize = (15,10))
plt.title('特征重要性',fontsize=14)
# 只展示前二十个
s = sns.barplot(x='Feature importance',y='Feature',data=tmp.loc[:20],orient='h')
plt.show()
最重要的特征是LIMIT_BAL
,AGE
,BILL_AMT1
,BILL_AMT2
,PAY_AMT1
。
4.2.4 混淆矩阵
preds = clf.predict(X_test)
cm = confusion_matrix(y_test, preds)
fig, ax = plt.subplots(ncols=1, figsize=(5,5))
sns.heatmap(cm,
xticklabels=['未违约', '违约'],
yticklabels=['未违约', '违约'],
annot=True,ax=ax, fmt='d',
linewidths=.2,linecolor="Darkblue", cmap="Blues",annot_kws={'size': 15})
plt.title('混淆矩阵', fontsize=14)
plt.xlabel('预测')
plt.ylabel('真实')
plt.show()
随机森林要比逻辑回归表现得好一些,但还是倾向于将大部分违约用户预测成不违约。
4.3.1 模型建立和训练
AdaBoost常被译作自适应增强,它的思想是利用一堆弱学习器,通过不断的迭代,修改样本的权重,最终将这些弱学习器组合成能力很强的分类器,在sklearn中对应的模型是AdaBoostClassifier。学习器的个数设置为400,与随机森林保持一致。学习率设置为0.8。
from sklearn.ensemble import AdaBoostClassifier
clf = AdaBoostClassifier(random_state=2019,
learning_rate=0.8,
n_estimators=400)
clf.fit(X_train, y_train)
4.3.2 模型预测和评估
preds = clf.predict_proba(X_test)[:,1]
roc_auc_score(y_test, preds)
AdaBoost的AUC得分是0.770,比随机森林表现相差一些。
4.3.3 特征重要性
tmp = pd.DataFrame({'Feature': predictors, 'Feature importance': clf.feature_importances_})
tmp = tmp.sort_values(by='Feature importance',ascending=False)
plt.figure(figsize = (15,10))
plt.title('特征重要性',fontsize=14)
# 只展示前二十个
s = sns.barplot(x='Feature importance',y='Feature',data=tmp.loc[:20],orient='h')
plt.show()
最重要的特征是BILL_AMT1
,PAY_AMT2
,PAY_AMT3
,BILL_AMT3
,BILL_AMT2
。
4.3.4 混淆矩阵
preds = clf.predict(X_test)
cm = confusion_matrix(y_test, preds)
fig, ax = plt.subplots(ncols=1, figsize=(5,5))
sns.heatmap(cm,
xticklabels=['未违约', '违约'],
yticklabels=['未违约', '违约'],
annot=True,ax=ax, fmt='d',
linewidths=.2,linecolor="Darkblue", cmap="Blues",annot_kws={'size': 15})
plt.title('混淆矩阵', fontsize=14)
plt.show()
跟逻辑回归和随机森林相比,AdaBoost预测的违约客户数量更少。
在这个案例中,我们对银行卡客户数据进行了数据探索,检查了数据中是否存在不平衡现象,并使用了sklearn中的三个分类模型对客户下个月是否会违约进行了预测:
由于数据存在不平衡,通过混淆矩阵可以观察到这些分类器都倾向将客户预测为不违约。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。