一、项目背景与目的

数据来源于Kaggle某项目:Bank Customer Churn | Kaggle

本数据集包括10000条匿名跨国银行的客户数据。数据分析的目的是预测客户流失的概率,然后通过预测模型的建立,相应地去提高用户的活跃度,实现挽留客户,降低挽留关怀工作的成本。

二、明确问题

项目关键问题是用户流失,在数据集中有一个Exited字段是我们需要预测的目标变量,Exited=1代表客户流失,Exited=0代表客户未流失,明显这是个监督学习的分类预测问题。构建模型要达到的目的是在精确度尽可能高的情况下,召回率也要尽可能高。

三、数据理解

在下面图中可以清晰看到各特征值和其含义

其中CreditScore、Geography、Gender、Age、Tenure、Balance等指标对客户流失有一定影响。

四、EDA-数据探索性分析

数据总览

import pandas as pd
import numpy as np
import warnings
warnings.filterwarnings("ignore")
import matplotlib.pyplot as plt
plt.rcParams['font.sans-serif']= ['SimHei'] #用来正常显示中文标签
plt.rcParams['axes.unicode_minus']= False #用来正常显示负号
import seaborn as sn
%matplotlib inline
pd.options.display.max_rows = None
pd.options.display.max_columns = Nonedf=pd.read_csv('C:/Users/koo/Desktop/archive/Customer_Churn.csv')
df.info()

我们可以看到有10000条样本数据,18个特征并无缺失值,4个类别变量,数据还比较干净。其中也存在无关特征,我们需要删除,以保证后面更好的训练模型。

剔除无关特征

#统计目标变量Exited列
df['Exited'].value_counts()
# 去掉RowNumber、CustomerId和Surname列
df = df.drop(["RowNumber", "CustomerId", "Surname"], axis = 1)
# 再一次检查数据
df.head()

上面的数据中,有些问题显露:

1. 数据并没有日期,没有对Banlance(余额)字段说明清楚;以及有些客户流失,余额也存在。

2. 其中IsActiveMember指的是客户是否活跃,我们只知道他/她是否活跃,这个变量的意义不明确,有没有信用卡对用户流失与否影响也不大。

数据有18个变量,其中RowNumber、CustomerId和Surname是无意义的变量,我们把这些变量  移除以便更好的分析数据。其中目标变量统计如下:

0    7962
1    2038
Name: Exited, dtype: int64

目标变量分析

目标变量占比:

labels = '流失', '维持'# 定义标签
sizes = [df.Exited[df['Exited']==1].count(), df.Exited[df['Exited']==0].count()]# 每一块的比例
colors = ['red', 'gold']
explode = (0.1, 0)# 突出显示
f, ax1 = plt.subplots(figsize=(7, 4))
ax1.pie(sizes, explode=explode, labels=labels, colors=colors, autopct='%1.1f%%',shadow=True, startangle=90)
ax1.axis('equal')# 显示为圆(避免比例压缩为椭圆)
plt.title("客户流失占比", size = 15)
plt.show()

扇形图中,流失的客户占比是20.4%,未流失的客户占比是79.6%。

目标变量与其他变量的关系:

f, axar = plt.subplots(2, 2, figsize=(20, 12))
sn.countplot(x=df['Geography'], hue =df['Exited'], ax=axar[0][0])
sn.countplot(x=df['Gender'], hue =df['Exited'],palette="Set1", ax=axar[0][1])
sn.countplot(x=df['HasCrCard'], hue =df['Exited'],palette="Set2", ax=axar[1][0])
sn.countplot(x=df['IsActiveMember'], hue =df['Exited'],palette="Set3",ax=axar[1][1])

图中我们可以看出以下问题:

  1. 来自于德国客户最少,法国最多,但流失客户的比例却是反的。说明银行在客户较少的地区可能没有分配足够多的客户服务资源。
  2. 男性客户总量高于女性,流失比例却低于女性,说明银行服务策略不够全面。
  3. 有信用卡的客户流失人数比没有信用卡的客户流失人数多。
  4. 可以看到不活跃的客户流失率更高。而不活跃的客户的总体比例相当高,银行应该针对不活跃客户给予相对优惠政策,将不活跃的客户转变为活跃的客户,来减少客户的流失。
f, axar = plt.subplots(3, 2, figsize=(20, 12))
sn.boxplot(y='CreditScore',x = 'Exited', hue = 'Exited',data = df, ax=axar[0][0])
sn.boxplot(y='Age',x = 'Exited', hue = 'Exited',data = df , ax=axar[0][1])
sn.boxplot(y='Tenure',x = 'Exited', hue = 'Exited',data = df, ax=axar[1][0])
sn.boxplot(y='Balance',x = 'Exited', hue = 'Exited',data = df, ax=axar[1][1])
sn.boxplot(y='NumOfProducts',x = 'Exited', hue = 'Exited',data = df, ax=axar[2][0])
sn.boxplot(y='EstimatedSalary',x = 'Exited', hue = 'Exited',data = df, ax=axar[2][1])

图中,我们可以看出:

  1. 流失和未流失客户之间的信用分数分布规律没有显著差异。
  2. 年龄较大的客户比年龄小流失更多,因此银行需要针对不同年龄段客户人群调整挽留策略。
  3. 就任期而言,处于极端状态的客户更容易流失。
  4. 银行正在流失拥有大量银行结余的客户,银行可能会缺少借贷资金,利润空间会被压缩。
  5. 产品和薪水对流失的可能性没有明显影响。

五、特征工程

把数据分为训练集和测试集,训练集占70%,测试集占30%
# 设置训练集和测试集
dftrain = df.sample(frac=0.7,random_state=200)
dftest = df.drop(dftrain.index)#构造对流失率可能产生影响的特征
dftrain['Balance_Salary'] = dftrain.Balance/dftrain.EstimatedSalary
sn.boxplot(y='Balance_Salary',x = 'Exited', hue = 'Exited',data = dftrain)
plt.ylim(-1, 6)

可以看出,虽然估计工资对流失没有太大影响,但余额与工资比对流失率有影响。具有较高余额工资比率的客户流失率更大,可能会影响银行放贷。

dftrain['Tenure_Age'] = dftrain.Tenure/(dftrain.Age)
sn.boxplot(y='Tenure_Age',x = 'Exited', hue = 'Exited',data = dftrain)
plt.ylim(-0.5, 0.5)

仔细观察,任期与年龄比率越低流失率越大,不过不明显,基本对流失率没有影响。

#创建一个变量来获取给定年龄的信用评分
dftrain['CreditScore_Age'] = dftrain.CreditScore/(dftrain.Age)
dftrain.head()

六、模型及最优参训练

数据准备

#选取指定的变量,为了更好的训练模型
continuous_vars = ['CreditScore',  'Age', 'Tenure', 'Balance','NumOfProducts', 'EstimatedSalary', 'Balance_Salary','Tenure_Age','CreditScore_Age'] #选取连续型变量
cat_vars = ['HasCrCard', 'IsActiveMember','Geography','Gender','Card Type'] #选取类别型变量
dftrain = dftrain[['Exited'] + continuous_vars + cat_vars] #训练集
dftest = dftest[['Exited'] + continuous_vars + cat_vars] #测试集#将0更改为-1,方便模型捕获变量之间的负相关关系
dftrain.loc[dftrain.HasCrCard == 0, 'HasCrCard'] = -1
dftrain.loc[dftrain.IsActiveMember == 0, 'IsActiveMember'] = -1
dftest.loc[dftest.HasCrCard == 0, 'HasCrCard'] = -1
dftest.loc[dftest.IsActiveMember == 0, 'IsActiveMember'] = -1
dftrain.head()

# 类别变量one-hot
ist = ['Geography', 'Gender', 'Card Type']
remove = list()
for i in ist:if (dftrain[i].dtype == np.str or dftrain[i].dtype == np.object):for j in dftrain[i].unique():dftrain[i+'_'+j] = np.where(dftrain[i] == j,1,-1)remove.append(i)if (dftest[i].dtype == np.str or dftest[i].dtype == np.object):for j in dftest[i].unique():dftest[i+'_'+j] = np.where(dftest[i] == j,1,-1)remove.append(i)
dftrain = dftrain.drop(remove, axis=1)
dftest = dftest.drop(remove, axis=1)
dftrain.head()

#连续型变量极差标准化,将数据归一到[0,1]之间
minVec = dftrain[continuous_vars].min().copy()
maxVec = dftrain[continuous_vars].max().copy()
dftrain[continuous_vars] = (dftrain[continuous_vars]-minVec)/(maxVec-minVec)
MinVec = dftest[continuous_vars].min().copy()
MaxVec = dftest[continuous_vars].max().copy()
dftest[continuous_vars] = (dftest[continuous_vars]-MinVec)/(MaxVec-MinVec)
dftrain.head()

训练模型

from sklearn.model_selection import GridSearchCV #网格搜索
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import PolynomialFeatures
from sklearn.linear_model import LogisticRegression #逻辑回归
from sklearn.svm import SVC
from sklearn.ensemble import RandomForestClassifier #随机森林
from sklearn.metrics import classification_report
from sklearn.metrics import roc_curve
from sklearn.metrics import roc_auc_score#构造函数方便得出最优模型得分和模型参数
def best_model(model):print(model.best_score_)    print(model.best_params_)print(model.best_estimator_)
def get_scores(y_true,method,method1):auc_score = roc_auc_score(y_true, method)fpr, tpr, _ = roc_curve(y_true, method1)return (auc_score, fpr, tpr)

在训练逻辑回归模型时,设置以下参数:

  • C:正则化系数的倒数,必须为正数,默认为1。值越小,正则化越强
  • max_iter: 算法收敛的最大迭代次数
  • tol: 迭代终止判据的误差范围
  • solver=lbfgs:使用了拟牛顿法的一种算法,利用损失函数二阶导数矩阵迭代优化损失函数

GridSearchCV函数,网格搜索和交叉验证的参数为:

  • param_grid: 即需要最优化的参数的取值
  • CV:交叉验证参数
  • refit=True, 再一次训练一遍全部数据集
  • verbose=0时, 不输出训练过程
#逻辑回归最优模型:
log=LogisticRegression(solver='lbfgs')
param={'C':[0.1,0.5,1,10,50,100],"tol":[0.000001,0.00001,0.0001],'max_iter':[250]}
grid=GridSearchCV(log,param_grid=param,cv=10,refit=True,verbose=0)
grid.fit(dftrain.loc[:,dftrain.columns !='Exited'],dftrain.Exited)
best_model(grid)
0.8142857142857143
{'C': 50, 'max_iter': 250, 'tol': 1e-06}
LogisticRegression(C=50, max_iter=250, tol=1e-06)

训练逻辑回归时,最后最好模型的交叉验证平均得分为0.814,最好的估计量是C为50,最大迭代次数为250,迭代终止判据的误差范围为1e-06

在SVM模型时,设置以下参数:

  • C:为错误项的惩罚系数,C越大,即对分错样本的惩罚程度越大
  • gamma:核函数系数,默认为auto,代表其值为样本特征数的倒数,1/n_features
  • probability: 是否起用概率估计
  • kernel=rbf, 核函数类型为径向核函数、高斯核
  • kernel=poly, 为多项式核函数
  • degree:多项式为poly时,函数的维度默认为3,选择其它核函数时会被忽略
#支持向量机结合高斯核最优模型:
param={'C':[0.5,100,150],'kernel':['rbf'],'gamma':[0.1,0.01,0.001],'probability':[True]}
grid=GridSearchCV(SVC(),param_grid=param,cv=3,verbose=0,refit=True)
grid.fit(dftrain.loc[:,dftrain.columns!='Exited'],dftrain.Exited)
best_model(grid)
0.8334293808406711
{'C': 100, 'gamma': 0.1, 'kernel': 'rbf', 'probability': True}
SVC(C=100, gamma=0.1, probability=True)

SVM结合高斯核,训练模型之后,模型的交叉验证平均得分为0.833,最好的估计量是C为100,gamma为0.1,probability为True

#支持向量机结合多项式核最优模型:
param={ 'C':[0.5,100,150],'kernel':['poly'],'degree':[2,3],'gamma':[0.1,0.01,0.001],'probability':[True] }
grid=GridSearchCV(SVC(),param_grid=param,cv=3,verbose=0,refit=True)
grid.fit(dftrain.loc[:,dftrain.columns!='Exited'],dftrain.Exited)
best_model(grid)
0.8568587898405857
{'C': 150, 'degree': 2, 'gamma': 0.1, 'kernel': 'poly', 'probability': True}
SVC(C=150, degree=2, gamma=0.1, kernel='poly', probability=True)

SVM结合多项式核,训练模型之后,模型的交叉验证平均得分为0.857,最好的估计量是C为100,degree为2,gamma为0.1,probability为True

训练随机森林模型,我们设置参数:

  • max_depth: 树的最大深度
  • max_features: 随机森林允许单个决策树使用特征的最大数量
  • n_estimators: 想要建立子树的数量,也就是学习器的个数
  • min_sample_split: 最小样本划分的数目
#随机森林最优模型:
param={'max_depth':[3,5,6,7,8],'max_features':[2,4,6,7,8,9],'n_estimators':[50,100],'min_samples_split':[3,5,6,7]}
grid=GridSearchCV(RandomForestClassifier(),param_grid=param,cv=5,verbose=0,refit=True)
grid.fit(dftrain.loc[:,dftrain.columns!='Exited'],dftrain.Exited)
best_model(grid)
0.8628571428571428
{'max_depth': 8, 'max_features': 9, 'min_samples_split': 3, 'n_estimators': 100}
RandomForestClassifier(max_depth=8, max_features=9, min_samples_split=3)

随机森林模型训练,模型的交叉验证平均得分为0.863,最好的估计量是max_depth为8,max_feature为9,min_sample_split为3,n_estimators为100

训练最优模型

# 训练逻辑回归模型的最优参数
log= LogisticRegression(C=50, max_iter=250, tol=1e-06)
log.fit(dftrain.loc[:,dftrain.columns!='Exited'],dftrain.Exited)# 训练支持向量机结合高斯核的最优参数
sv_rbf=SVC(C=100, kernel='rbf', gamma=0.1, probability=True)
sv_rbf.fit(dftrain.loc[:,dftrain.columns!='Exited'],dftrain.Exited)#训练支持向量机模型结合多项式核的最优参数
sv_poly=SVC(C=150, degree=2, kernel='poly', gamma=0.1, probability=True)
sv_poly.fit(dftrain.loc[:,dftrain.columns!='Exited'],dftrain.Exited)#训练随机森林的最优参数
rf = RandomForestClassifier(max_depth=8, max_features=8, min_samples_split=6, n_estimators=50)
rf.fit(dftrain.loc[:, dftrain.columns != 'Exited'],dftrain.Exited)
LogisticRegression(C=50, max_iter=250, tol=1e-06)
SVC(C=100, gamma=0.1, probability=True)
SVC(C=150, degree=2, gamma=0.1, kernel='poly', probability=True)
RandomForestClassifier(max_depth=8, max_features=8, min_samples_split=6,n_estimators=50)
print(classification_report(dftrain.Exited,log.predict(dftrain.loc[:,dftrain.columns!='Exited'])))
               precision    recall  f1-score   support0       0.83      0.97      0.89      55571       0.64      0.23      0.34      1443accuracy                           0.81      7000macro avg       0.73      0.60      0.62      7000
weighted avg       0.79      0.81      0.78      7000
print(classification_report(dftrain.Exited,sv_rbf.predict(dftrain.loc[:,dftrain.columns!='Exited'])))
              precision    recall  f1-score   support0       0.88      0.98      0.93      55571       0.87      0.48      0.62      1443accuracy                           0.88      7000macro avg       0.87      0.73      0.77      7000
weighted avg       0.88      0.88      0.86      7000
print(classification_report(dftrain.Exited,sv_poly.predict(dftrain.loc[:,dftrain.columns!='Exited'])))
              precision    recall  f1-score   support0       0.86      0.98      0.92      55571       0.83      0.40      0.54      1443accuracy                           0.86      7000macro avg       0.85      0.69      0.73      7000
weighted avg       0.86      0.86      0.84      7000
print(classification_report(dftrain.Exited,rf.predict(dftrain.loc[:,dftrain.columns!='Exited'])))
              precision    recall  f1-score   support0       0.89      0.98      0.93      55571       0.89      0.53      0.66      1443accuracy                           0.89      7000macro avg       0.89      0.76      0.80      7000
weighted avg       0.89      0.89      0.88      7000
y = dftrain.Exited
X = dftrain.loc[:, dftrain.columns != 'Exited']
auc_log, fpr_log, tpr_log = get_scores(y, log.predict(X),log.predict_proba(X)[:,1])
auc_sv_rbf, fpr_sv_rbf, tpr_sv_rbf = get_scores(y, sv_rbf.predict(X),sv_rbf.predict_proba(X)[:,1])
auc_sv_poly, fpr_sv_poly, tpr_sv_poly = get_scores(y, sv_poly.predict(X),sv_poly.predict_proba(X)[:,1])
auc_rf, fpr_rf, tpr_rf = get_scores(y, rf.predict(X),rf.predict_proba(X)[:,1])
plt.figure(figsize = (10,5), linewidth= 1)
plt.plot(fpr_log, tpr_log, label = 'log Score: ' + str(round(auc_log, 5)))
plt.plot(fpr_sv_rbf, tpr_sv_rbf, label = 'sv_rbf Score: ' + str(round(auc_sv_rbf, 5)))
plt.plot(fpr_sv_poly, tpr_sv_poly, label = 'sv_poly Score: ' + str(round(auc_sv_poly, 5)))
plt.plot(fpr_rf, tpr_rf, label = 'rf score: ' + str(round(auc_rf, 5)))
plt.plot([0,1], [0,1], 'k--', label = 'Random: 0.5')
plt.xlabel('False positive rate')
plt.ylabel('True positive rate')
plt.title('ROC Curve')
plt.legend(loc='best')
plt.show()

  1. 扇形图中得知有20.4%的客户流失率,召回率高于30.6%,说明模型对流失客户的预测能立提高很多,但我们希望在保持模型高精度的同时尽可能地提高召回率
  2. 在这些模型中,最好的模型是随机森林模型,其次是高斯核函数的支持向量机,而且随机森林的召回率和精确度相对接近,使随机森林模型的预测能力要高于其它模型。根据训练集的拟合度,随机森林"1"精确度为0.89,在模型预测精确度为89%的情况下客户依旧会流失,召回率为0.53,这53%的客户就是流失客户,我们需要对这些流失客户制定相应的挽留策略。

七、模型评估

通过测试集对模型泛化能力进行检验:

print(classification_report(dftest.Exited,rf.predict(dftest.loc[:,dftest.columns != 'Exited'])))
              precision    recall  f1-score   support0       0.89      0.92      0.91      24001       0.64      0.54      0.59       594accuracy                           0.85      2994macro avg       0.76      0.73      0.75      2994
weighted avg       0.84      0.85      0.84      2994

总结

  1. 使用逻辑回归、支持向量机不同内核、随机森林四种模型做对比分析。从表现上看,随机森林效果最优。随机森林模型测试数据的准确率为64%,召回率为54%。 随机森林模型在预测为流失客户的准确性上较低,但客户流失较少。 通过对预测流失客户的存款,年龄,工资收入等调查分析,通过提高服务质量,增加产品种类等针对性措施降低客户流失率
  2. 缺失值处理很关键,根据数据和模型选择是否需要独热编码和标准化。筛选特征的方法有很多种,比如标准差、相关系数等,但在我们的数据中,数据都是差不多构造好的,数据也不需要特殊处理。特征工程决定了机器学习效果的上限,模型优化只能无限接近
  3. 我们也会遇见各种各样的数据类型、数据特征采集和处理的问题,但是为了更好的建立一个预测模型,我们要根据业务的需求精准定位,综合分析,去采集我们需要的数据,这样建立的模型才更有意义

银行用户流失分析与预测相关推荐

  1. 电信用户流失分析与预测

    电信用户流失分析与预测 一. 研究背景 二. 分析结论与建议 三. 任务与实现 四. 数据集解析 五. 数据分析套餐 1.准备工作 导入相关的库 导入数据集 2.数据预处理 类型转换 缺失值处理 重复 ...

  2. 【数据分析与挖掘实战】电信用户流失分析与预测

    背景 关于用户留存有这样一个观点,如果将用户流失率降低5%,公司利润将提升25%-85%.如今高居不下的获客成本让电信运营商遭遇"天花板",甚至陷入获客难的窘境.随着市场饱和度上升 ...

  3. 实战 | 电信客户流失分析与预测

    本文所有代码都通过运行! 将从以下方面进行分析:1.背景 2.提出问题 3.理解数据 4.数据清洗 5.可视化分析 6.用户流失预测 7.结论和建议 本项目带你根据以上过程详细分析电信客户数据! 01 ...

  4. 实战案例 :电信客户流失分析与预测

    本文所有代码都通过运行! 将从以下方面进行分析:1.背景 2.提出问题 3.理解数据 4.数据清洗 5.可视化分析 6.用户流失预测 7.结论和建议 本项目带你根据以上过程详细分析电信客户数据! 01 ...

  5. 运营商客户流失分析和预测--数据分析项目

    背景 关于用户留存有这样一个观点,如果将用户流失率降低5%,公司利润将提升25%-85%.如今高居不下的获客成本让电信运营商遭遇"天花板",甚至陷入获客难的窘境.随着市场饱和度上升 ...

  6. 干货:如何进行用户流失分析与预警?

    在做用户运营的时候,除了要了解用户,为用户建立画像外,另一个重要的方向就是用户流失分析,对可能流失的用户进行分析.预警,及时采取措施进行用户挽留,最大可能的减少企业的损失. 通常情况下,企业挽留一个老 ...

  7. 数据分析案例——客户流失分析与预测

    客户流失分析与预测 一.数据来源 https://www.kaggle.com/blastchar/telco-customer-churn 二.数据整理 1.导入函数包 import pandas ...

  8. 电信客户流失分析与预测

    一.项目背景 近年来,不论是传统行业还是互联网行业,都面临着用户流失问题.研究表明,企业可以在一周内失去100个用户,而同时又得到另外一个用户,从表明上来看业绩没有受到任何影响,而实际上争取这些新用户 ...

  9. 通信运营商用户流失分析与预测

    1.通信运营商现状与需求 随着业务的快速发展.移动业务市场的竞争愈演愈烈.如何最大程度地挽留在网用户.吸取新客户,是电信企业最关注的问题之一.竞争对手的促销.公司资费软着陆措施的出台和政策法规的不断变 ...

最新文章

  1. 小程序在wxml使用indexOf
  2. Attribute在拦截机制上的应用
  3. 无锡朗贤获B+轮融资,辰韬资本、兴韬投资领投
  4. c#常用函数和方法集
  5. 【opencv】14.cv::cvtColor函数转换颜色空间时使用CV_RGB2BGR与CV_BGR2RGB结果一样的原因
  6. 一文了解自然语言生成演变史!
  7. eureka服务失效剔除时长配置
  8. cadence自动生成铺铜_干货 | 国内铜湿法冶金工艺现状分析
  9. 综述 | Google-斯坦福发布~深度学习统计力学
  10. cs go linux游戏吧,玩家分享:让CSGO画面变成舒服的暖色调
  11. oracle电梯案例,Oracle技术嘉年华的一个案例,redo的那些事,连载一
  12. fatal error C1010: 是否忘记了向源中添加“#include stdafx.h”?
  13. face_recognition初始
  14. 如何将Eclipse设置为中文版
  15. DEDECMS三级导航动态调用
  16. MATLAB自带函数实现经验模态分解总结
  17. 转速传感器隔离放大器输出信号隔离变送器正弦波转方波信号隔离器
  18. scratch编程钟表
  19. Going Deeper with Convolutions——GoogLeNet论文翻译——中文版
  20. 通用计数器的检定方案

热门文章

  1. code-push热部署开发者端
  2. web的demo制造_demoshow - webdemo展示助手
  3. 于浩java_巅峰之战 三款最热java浏览器横评
  4. 科技论文写作中结果与讨论的区别
  5. JavaScript解除事件绑定处理程序 js事件绑定解除
  6. Java中的LocalDateTime plusMinutes()方法
  7. SAP中财务凭证类型与物料凭证类型的不同决定方式
  8. BurntSushi/toml
  9. 每日学习,泊松分布公式推导
  10. 贪心算法求解一个序列中出现次数最多的元素问题