kaggle数据地址:https://www.kaggle.com/sakshigoyal7/credit-card-customers
导入数据
#导入模块 import pandas as pd import numpy as np import matplotlib.pyplot as plt import seaborn as sns BankChurners = pd.read_csv(\'D:\\python_home\\预测客户流失\\bankchurners\\BankChurners.csv\')
简单的数据查看
BankChurners.columns \'\'\' Index([\'CLIENTNUM\', \'Attrition_Flag\', \'Customer_Age\', \'Gender\', \'Dependent_count\', \'Education_Level\', \'Marital_Status\', \'Income_Category\', \'Card_Category\', \'Months_on_book\', \'Total_Relationship_Count\', \'Months_Inactive_12_mon\', \'Contacts_Count_12_mon\', \'Credit_Limit\', \'Total_Revolving_Bal\', \'Avg_Open_To_Buy\', \'Total_Amt_Chng_Q4_Q1\', \'Total_Trans_Amt\', \'Total_Trans_Ct\', \'Total_Ct_Chng_Q4_Q1\', \'Avg_Utilization_Ratio\', \'Naive_Bayes_Classifier_Attrition_Flag_Card_Category_Contacts_Count_12_mon_Dependent_count_Education_Level_Months_Inactive_12_mon_1\', \'Naive_Bayes_Classifier_Attrition_Flag_Card_Category_Contacts_Count_12_mon_Dependent_count_Education_Level_Months_Inactive_12_mon_2\'], dtype=\'object\') \'\'\'
拿到百度翻译了一下
\'\'\' \'CLIENTNUM\',\'消耗标志\',\'客户年龄\',\'性别\', “受抚养人数量”、“受教育程度”、“婚姻状况”, \'收入类别\',\'卡片类别\',\'账面上的月份\', \'总关系数\',\'月数\'u不活跃\'u 12个月\', “联系人数量12个月”,“信用额度”,“总周转余额”, \'平均开盘价到买入价\',\'总金额\', \'总交易量\',\'第4季度总交易量\',\'平均利用率\', \'天真的_Bayes_分类器_消耗_标志_卡片_类别_联系人_Count_12 _mon_依赖性_Count_u教育程度_u个月_不活跃_12 _mon_1\', \'天真的_Bayes_分类器_消耗_标志_卡片_类别_联系人_Count_12 _mon_依赖性_Count_受教育程度_u月_不活跃_12_mon_2\'], dtype=\'object\' \'\'\'
简单看一下缺失情况
#木有空值,但是不代表没有其他表达类型的空值,比如说\'-\' BankChurners.isnull().sum()
我们按照数据类型分一下字段
#数据特征 numeric_features = BankChurners.select_dtypes(include=[np.number]) print(numeric_features.columns) #类别特征 categorical_features = BankChurners.select_dtypes(include=[np.object]) print(categorical_features.columns)
Index([\'CLIENTNUM\', \'Customer_Age\', \'Dependent_count\', \'Months_on_book\',
\'Total_Relationship_Count\', \'Months_Inactive_12_mon\',
\'Contacts_Count_12_mon\', \'Credit_Limit\', \'Total_Revolving_Bal\',
\'Avg_Open_To_Buy\', \'Total_Amt_Chng_Q4_Q1\', \'Total_Trans_Amt\',
\'Total_Trans_Ct\', \'Total_Ct_Chng_Q4_Q1\', \'Avg_Utilization_Ratio\',
\'Naive_Bayes_Classifier_Attrition_Flag_Card_Category_Contacts_Count_12_mon_Dependent_count_Education_Level_Months_Inactive_12_mon_1\',
\'Naive_Bayes_Classifier_Attrition_Flag_Card_Category_Contacts_Count_12_mon_Dependent_count_Education_Level_Months_Inactive_12_mon_2\'],
dtype=\'object\')
Index([\'Attrition_Flag\', \'Gender\', \'Education_Level\', \'Marital_Status\',
\'Income_Category\', \'Card_Category\'],
dtype=\'object\')
看看类别型变量的唯一值
for i in list(categorical_features.columns): print(\'*************************\') print(\'{0}+的唯一值如下:{1}\'.format(i,BankChurners[i].nunique())) print(BankChurners[i].unique())
************************* Attrition_Flag+的唯一值如下:2 [\'Existing Customer\' \'Attrited Customer\'] ************************* Gender+的唯一值如下:2 [\'M\' \'F\'] ************************* Education_Level+的唯一值如下:7 [\'High School\' \'Graduate\' \'Uneducated\' \'Unknown\' \'College\' \'Post-Graduate\' \'Doctorate\'] ************************* Marital_Status+的唯一值如下:4 [\'Married\' \'Single\' \'Unknown\' \'Divorced\'] ************************* Income_Category+的唯一值如下:6 [\'$60K - $80K\' \'Less than $40K\' \'$80K - $120K\' \'$40K - $60K\' \'$120K +\' \'Unknown\'] ************************* Card_Category+的唯一值如下:4 [\'Blue\' \'Gold\' \'Silver\' \'Platinum\']
看来没有乱七八糟的空值
我们在看一下数值型变量的分布
plt.figure(figsize=(20,50)) col = list(numeric_features.columns) for i in range(1,len(numeric_features.columns)+1): ax=plt.subplot(9,2,i) sns.kdeplot(BankChurners[col[i-1]],bw=1.5) plt.xlabel(col[i-1]) plt.show()
我们看上面变量,第一个变量CLIENTNUM 其实就是客户ID,我们可以不用理会
仔细看上面的图片,我们就会发现,其实有些字段其实是类别比较少的数值型变量,但是还是写代码看好,眼睛有时也会出卖自己
for i in list(numeric_features.columns): print(\'{0}+的唯一值如下:{1}\'.format(i,BankChurners[i].nunique()))
CLIENTNUM+的唯一值如下:10127 Customer_Age+的唯一值如下:45 Dependent_count+的唯一值如下:6 Months_on_book+的唯一值如下:44 Total_Relationship_Count+的唯一值如下:6 Months_Inactive_12_mon+的唯一值如下:7 Contacts_Count_12_mon+的唯一值如下:7 Credit_Limit+的唯一值如下:6205 Total_Revolving_Bal+的唯一值如下:1974 Avg_Open_To_Buy+的唯一值如下:6813 Total_Amt_Chng_Q4_Q1+的唯一值如下:1158 Total_Trans_Amt+的唯一值如下:5033 Total_Trans_Ct+的唯一值如下:126 Total_Ct_Chng_Q4_Q1+的唯一值如下:830 Avg_Utilization_Ratio+的唯一值如下:964 Naive_Bayes_Classifier_Attrition_Flag_Card_Category_Contacts_Count_12_mon_Dependent_count_Education_Level_Months_Inactive_12_mon_1+的唯一值如下:1704 Naive_Bayes_Classifier_Attrition_Flag_Card_Category_Contacts_Count_12_mon_Dependent_count_Education_Level_Months_Inactive_12_mon_2+的唯一值如下:640
我们把它归为一类
num_cate_col = [ \'Dependent_count\', \'Total_Relationship_Count\', \'Months_Inactive_12_mon\', \'Contacts_Count_12_mon\', ]
剩下的归为一类
numeric_col = [ x for x in list(numeric_features.columns) if x not in num_cate_col] numeric_col.remove(\'CLIENTNUM\') numeric_col \'\'\' [\'Customer_Age\', \'Months_on_book\', \'Credit_Limit\', \'Total_Revolving_Bal\', \'Avg_Open_To_Buy\', \'Total_Amt_Chng_Q4_Q1\', \'Total_Trans_Amt\', \'Total_Trans_Ct\', \'Total_Ct_Chng_Q4_Q1\', \'Avg_Utilization_Ratio\', \'Naive_Bayes_Classifier_Attrition_Flag_Card_Category_Contacts_Count_12_mon_Dependent_count_Education_Level_Months_Inactive_12_mon_1\', \'Naive_Bayes_Classifier_Attrition_Flag_Card_Category_Contacts_Count_12_mon_Dependent_count_Education_Level_Months_Inactive_12_mon_2\'] \'\'\'
我们在看看类别比较少的数值型变量
for i in num_cate_col: print(\'*************************\') print(\'{0}+的唯一值如下:{1}\'.format(i,BankChurners[i].nunique())) print(BankChurners[i].unique())
*************************
Dependent_count+的唯一值如下:6
[3 5 4 2 0 1]
*************************
Total_Relationship_Count+的唯一值如下:6
[5 6 4 3 2 1]
*************************
Months_Inactive_12_mon+的唯一值如下:7
[1 4 2 3 6 0 5]
*************************
Contacts_Count_12_mon+的唯一值如下:7
[3 2 0 1 4 5 6]
看看y值Attrition_Flag
BankChurners[\'Attrition_Flag\'].value_counts().plot.pie(explode=[0,0.1],autopct=\'%1.1f%%\')
比例为16%,还好
画类别变量的直方图以及逾期概率图
cate_cold = list(categorical_features.columns)+num_cate_col for i in cate_cold: tmp = pd.crosstab(BankChurners[i], BankChurners[\'Attrition_Flag\']) tmp[\'总人数\'] = tmp.sum(axis=1) tmp[\'流失率\'] = tmp[\'Attrited Customer\']/tmp[\'总人数\'] fig, ax1 = plt.subplots() ax1.bar(tmp.index,tmp[\'总人数\'],color=\'green\') ax2 = ax1.twinx() ax2.plot(tmp.index,tmp[\'流失率\'],color=\'red\') plt.show()
构建特征
2021.1.15来补充一下建模的部分
具体过程就不写了,直接附上代码,以及结果
# -*- coding: utf-8 -*- """ Created on Fri Jan 15 09:18:46 2021 @author: Administrator """ import pandas as pd import numpy as np import matplotlib.pyplot as plt import seaborn as sns import pycard as pc #%%导入数据 BankChurners = pd.read_csv(\'D:\\python_home\\预测客户流失\\bankchurners\\BankChurners.csv\') BankChurners.isnull().sum() #%%区分数值型变量和类别型变量 num_col = [\'Customer_Age\', \'Months_on_book\', \'Credit_Limit\', \'Total_Revolving_Bal\', \'Avg_Open_To_Buy\', \'Total_Amt_Chng_Q4_Q1\', \'Total_Trans_Amt\', \'Total_Trans_Ct\', \'Total_Ct_Chng_Q4_Q1\', \'Avg_Utilization_Ratio\', \'Naive_Bayes_Classifier_Attrition_Flag_Card_Category_Contacts_Count_12_mon_Dependent_count_Education_Level_Months_Inactive_12_mon_1\', \'Naive_Bayes_Classifier_Attrition_Flag_Card_Category_Contacts_Count_12_mon_Dependent_count_Education_Level_Months_Inactive_12_mon_2\'] cate_col = [ i for i in list(BankChurners.columns) if i not in num_col] cate_col.remove(\'CLIENTNUM\') cate_col \'\'\' [\'Attrition_Flag\', \'Gender\', \'Dependent_count\', \'Education_Level\', \'Marital_Status\', \'Income_Category\', \'Card_Category\', \'Total_Relationship_Count\', \'Months_Inactive_12_mon\', \'Contacts_Count_12_mon\'] \'\'\' #%%cate_col.unqion() for i in cate_col: print(i,BankChurners[i].unique()) \'\'\' Attrition_Flag [\'Existing Customer\' \'Attrited Customer\'] Gender [\'M\' \'F\'] Dependent_count [3 5 4 2 0 1] Education_Level [\'High School\' \'Graduate\' \'Uneducated\' \'Unknown\' \'College\' \'Post-Graduate\' \'Doctorate\'] Marital_Status [\'Married\' \'Single\' \'Unknown\' \'Divorced\'] Income_Category [\'$60K - $80K\' \'Less than $40K\' \'$80K - $120K\' \'$40K - $60K\' \'$120K +\' \'Unknown\'] Card_Category [\'Blue\' \'Gold\' \'Silver\' \'Platinum\'] Total_Relationship_Count [5 6 4 3 2 1] Months_Inactive_12_mon [1 4 2 3 6 0 5] Contacts_Count_12_mon [3 2 0 1 4 5 6] \'\'\' #%%处理目标变量,赋值0和1 流失就是1 BankChurners.Attrition_Flag = BankChurners.Attrition_Flag.map({\'Existing Customer\':0,\'Attrited Customer\':1}) #%%类别变量的iv计算 cate_iv_woedf = pc.WoeDf() clf = pc.NumBin() for i in cate_col[1:]: cate_iv_woedf.append(pc.cross_woe(BankChurners[i] ,BankChurners.Attrition_Flag)) cate_iv_woedf.to_excel(\'tmp11\') #尝试使用二者组合变量,发现并没有作用 BankChurners[\'Gender_Dependent_count\'] = BankChurners.apply(lambda x :x.Gender+ str(x.Dependent_count),axis=1) pc.cross_woe(BankChurners.Gender_Dependent_count,BankChurners.Attrition_Flag) BankChurners[\'Education_Level_Income_Category\'] = BankChurners.apply(lambda x :x.Education_Level+ str(x.Income_Category),axis=1) pc.cross_woe(BankChurners.Education_Level_Income_Category,BankChurners.Attrition_Flag) BankChurners.pop(\'Gender_Dependent_count\') BankChurners.pop(\'Education_Level_Income_Category\') #%%数值变量的iv值计算 num_iv_woedf = pc.WoeDf() clf = pc.NumBin() for i in num_col: clf.fit(BankChurners[i] ,BankChurners.Attrition_Flag) clf.generate_transform_fun() num_iv_woedf.append(clf.woe_df_) num_iv_woedf.to_excel(\'tmp12\') #%%简单处理一下变量 #Total_Relationship_Count,Months_Inactive_12_mon,Contacts_Count_12_mon,类别型变量就不需要管了,保留原来的分区即可 #Credit_Limit from numpy import * BankChurners[\'Credit_Limit_bin\'] = pd.cut(BankChurners.Credit_Limit,bins=[-inf, 1438.65, 1638.5, 1900.5, 2477.5, 3398.5, 5061.5, inf]) BankChurners[\'Total_Revolving_Bal_bin\'] = pd.cut(BankChurners.Total_Revolving_Bal,bins=[-inf, 66.0, 581.5, 979.5, 2512.5, inf]) BankChurners[\'Avg_Open_To_Buy_bin\'] = pd.cut(BankChurners.Avg_Open_To_Buy,bins=[-inf, 447.5, 1038.5, 1437.0, 1944.5, 2229.5, inf]) BankChurners[\'Total_Amt_Chng_Q4_Q1_bin\'] = pd.cut(BankChurners.Total_Amt_Chng_Q4_Q1,bins=[-inf, 0.3685, 0.4355, 0.5045, 0.5325, 1.0625, inf]) BankChurners[\'Total_Trans_Amt_bin\'] = pd.cut(BankChurners.Total_Trans_Amt,bins=[-inf, 1001.0, 2010.5, 2729.5, 2932.5, 3152.0, 5365.0, 11093.0, inf]) BankChurners[\'Total_Trans_Ct_bin\'] = pd.cut(BankChurners.Total_Trans_Ct,bins=[-inf, 20.5, 37.5, 47.5, 54.5, 57.5, 64.5, 78.5, inf]) BankChurners[\'Total_Ct_Chng_Q4_Q1_bin\'] = pd.cut(BankChurners.Total_Ct_Chng_Q4_Q1,bins=[-inf, 0.4075, 0.4875, 0.504, 0.6015, 0.6565, inf]) BankChurners[\'Avg_Utilization_Ratio_bin\'] = pd.cut(BankChurners.Avg_Utilization_Ratio,bins=[-inf, 0.0205, 0.4505, 0.7985, inf]) BankChurners[\'Naive_Bayes1_bin\'] = pd.cut(BankChurners.Naive_Bayes_Classifier_Attrition_Flag_Card_Category_Contacts_Count_12_mon_Dependent_count_Education_Level_Months_Inactive_12_mon_1,\ bins=[-inf, 0.4736, inf]) BankChurners[\'Naive_Bayes2_bin\'] = pd.cut(BankChurners.Naive_Bayes_Classifier_Attrition_Flag_Card_Category_Contacts_Count_12_mon_Dependent_count_Education_Level_Months_Inactive_12_mon_2,\ bins=[-inf, 0.5264, inf]) iv_col = [i for i in [\'Total_Relationship_Count\',\'Months_Inactive_12_mon\',\'Contacts_Count_12_mon\', ] + list(BankChurners.columns)[-10:]] cate_iv_woedf = pc.WoeDf() clf = pc.NumBin() for i in iv_col: cate_iv_woedf.append(pc.cross_woe(BankChurners[i] ,BankChurners.Attrition_Flag)) cate_iv_woedf.to_excel(\'tmp11\') BankChurners.Contacts_Count_12_mon[BankChurners.Contacts_Count_12_mon ==6] =5 #%%解决方案如下: #解决方案:后面这两个就不入模型了,作为规则即可,不然会导致过拟合,上面的出现了无穷的变量要合并区间 iv_col = [i for i in [\'Total_Relationship_Count\',\'Months_Inactive_12_mon\',\'Contacts_Count_12_mon\', ] + list(BankChurners.columns)[-10:-2]] cate_iv_woedf = pc.WoeDf() clf = pc.NumBin() for i in iv_col: cate_iv_woedf.append(pc.cross_woe(BankChurners[i] ,BankChurners.Attrition_Flag)) cate_iv_woedf.to_excel(\'tmp11\') #%%woe转换 pc.obj_info(cate_iv_woedf) cate_iv_woedf.bin2woe(BankChurners,iv_col) model_col = [i for i in [\'CLIENTNUM\', \'Attrition_Flag\']+list(BankChurners.columns)[-11:]] #%%建模 import pandas as pd import matplotlib.pyplot as plt #导入图像库 import matplotlib import seaborn as sns import statsmodels.api as sm from sklearn.metrics import roc_curve, auc from sklearn.model_selection import train_test_split X = BankChurners[list(BankChurners.columns)[-11:]] Y = BankChurners[\'Attrition_Flag\'] x_train,x_test,y_train,y_test=train_test_split(X,Y,test_size=0.3,random_state=0) #(10127, 44) X1=sm.add_constant(x_train) #在X前加上一列常数1,方便做带截距项的回归 logit=sm.Logit(y_train.astype(float),X1.astype(float)) result=logit.fit() result.summary() result.params X3 = sm.add_constant(x_test) resu = result.predict(X3.astype(float)) fpr, tpr, threshold = roc_curve(y_test, resu) rocauc = auc(fpr, tpr) plt.plot(fpr, tpr, \'b\', label=\'AUC = %0.2f\' % rocauc) plt.legend(loc=\'lower right\') plt.plot([0, 1], [0, 1], \'r--\') plt.xlim([0, 1]) plt.ylim([0, 1]) plt.ylabel(\'真正率\') plt.xlabel(\'假正率\') plt.show()
模型的参数如下:
const -1.662204
Total_Relationship_C_woe -2.307529
Months_Inactive_12_woe -1.050666
Contacts_Count_12_woe -0.742126
Credit_Limit_woe -0.633916
Total_Revolving_Bal_woe -0.914706
Avg_Open_To_Buy_woe -0.120073
Total_Amt_Chng_Q4_Q1_woe -0.845637
Total_Trans_Amt_woe -0.924220
Total_Trans_Ct_woe -0.601850
Total_Ct_Chng_Q4_Q1_woe -0.493193
Avg_Utilization_Ratio_woe -0.265479
测试集的auc图片如下:
0.9651589169400466
看看训练集的auc
resu_1 = result.predict(X1.astype(float)) fpr, tpr, threshold = roc_curve(y_train, resu_1) rocauc = auc(fpr, tpr) plt.plot(fpr, tpr, \'b\', label=\'AUC = %0.2f\' % rocauc) plt.legend(loc=\'lower right\') plt.plot([0, 1], [0, 1], \'r--\') plt.xlim([0, 1]) plt.ylim([0, 1]) plt.ylabel(\'真正率\') plt.xlabel(\'假正率\') plt.show()
0.9683602097734862
放一下入模变量的iv值
总结:
1.变量的逾期率不单调,没有关系,但是分箱时每个箱子不能太小,太小需要合并,最好挑选iv值比较好(2%以上)的入魔,使用woe去训练模型
2.关于iv值为无穷时候的做法:
IVi无论等于负无穷还是正无穷,都是没有意义的。
由上述问题我们可以看到,使用IV其实有一个缺点,就是不能自动处理变量的分组中出现响应比例为0或100%的情况。那么,遇到响应比例为0或者100%的情况,我们应该怎么做呢?建议如下:
(1)如果可能,直接把这个分组做成一个规则,作为模型的前置条件或补充条件;
(2)重新对变量进行离散化或分组,使每个分组的响应比例都不为0且不为100%,尤其是当一个分组个体数很小时(比如小于100个),强烈建议这样做,因为本身把一个分组个体数弄得很小就不是太合理。
(3)如果上面两种方法都无法使用,建议人工把该分组的响应数和非响应的数量进行一定的调整。如果响应数原本为0,可以人工调整响应数为1,如果非响应数原本为0,可以人工调整非响应数为1.
本次也出现了两种iv值为无穷的情况
我的处理方法是:
解决方案:后面这两个就不入模型了,作为规则即可,不然可能导致过拟合,上面的出现了无穷的变量要合并区间
还有一些个人信息的特征,比如年龄,婚姻等等,由于iv值很低就没有放进去,但是还是值得探讨一下,不过本次的模型的效果很好,就不做太复杂的模型了
但是还有一个疑惑是:
Naive_Bayes_Classifier_Attrition_Flag_Card_Category_Contacts_Count_12_mon_Dependent_count_Education_Level_Months_Inactive_12_mon_1
Naive_Bayes_Classifier_Attrition_Flag_Card_Category_Contacts_Count_12_mon_Dependent_count_Education_Level_Months_Inactive_12_mon_2
最后两个变量实在是太厉害了,不知道是不是别人训练好的y?
需要查资料看看
最后在官网找到了答案