📮 求内推
👩 个人简介:听障人,96年,计算机专业(本科),从事数据分析工作
🛠 语言与工具:包括但不限于Python、SQL、tableaubi、finebi、powerbi。
💻 期望岗位:数据分析、数据运营、报表开发等(hc偏技术方向)
📍 期望城市:杭州
背景问题
在商业数据分析中,客户流失预测与留存研究通过收集客户数据、清洗、选择关键特征、建立预测模型、评估和优化模型等步骤,帮助企业了解客户行为模式,并采取相应措施来最大程度地减少流失、提高留存。
这包括分析客户个人信息、交易记录、使用习惯等数据,建立机器学习模型预测客户流失概率,然后根据预测结果制定针对性的保留策略,如推出个性化服务、提供优惠活动等,从而实现企业的持续发展。
本数据集为模拟数据集,包含了客户资料,包括人口统计、产品互动和银行行为等丰富数据信息,可以用于模拟识别高风险客户并制定有针对性的客户挽留策略。
问题描述
探索性数据分析:通过多维度客户数据的深度挖掘,揭示潜在关联、趋势与异常,直观呈现客户特征与流失风险之间的内在关系。
客户细分:依据客户的人口统计特征、金融行为及产品偏好等信息,划分出具有不同流失倾向的群体,为精细化营销与服务策略提供依据。
特征重要性分析:计算模型中各特征的贡献度或重要性得分,揭示影响客户流失的关键因素。
流失预测建模:利用包含目标变量(Exited)的数据集训练预测模型,准确评估每个客户的未来流失概率,为预防性干预措施提供精准导向。
数据说明
数据清洗及预览
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
import seaborn as sns
# 机器学习的必备模块
from sklearn.compose import ColumnTransformer # 该类允许你同时应用不同的预处理步骤到数据集的不同列。例如下一行对数据集的预处理
from sklearn.preprocessing import StandardScaler,OneHotEncoder # StandardScaler用于数据的标准化处理,OneHotEncoder用于对分类变量进行独热编码
from sklearn.metrics import classification_report, confusion_matrix, accuracy_score,recall_score, precision_score, roc_auc_score, roc_curve, auc # 评估模型性能的一些指标
from sklearn.model_selection import train_test_split,GridSearchCV # train_test_split用于数据集分割成训练集和测试集,GridSearchCV是用于模型选择和超参数调优的工具
# 机器学习的聚类算法之一
from sklearn.cluster import KMeans
# 机器学习的随机森林算法
from sklearn.ensemble import RandomForestClassifier
# 统计方法
from scipy import stats
df = pd.read_csv(r'Customer Churn Dataset.csv',index_col=0)
print('-'*50)
print('数据集存在重复值个数:',df.duplicated().sum())
print('-'*50)
print('数据集存在缺失值个数:\n',df.isna().sum())
print('-'*50)
print('数据集信息:')
print(df.info())
# CustomerId数据类型改为字符类型
df['CustomerId'] = df['CustomerId'].astype('str')
# HasCrCard、IsActiveMember、Exited数据类型改为逻辑类型
# df[['HasCrCard','IsActiveMember','Exited']] = df[['HasCrCard','IsActiveMember','Exited']].ne(0)
# 检查下
print(df.info())
print('-'*100)
display(df.describe())
# 对姓氏数据进行脱敏处理
# 只保留姓氏的首字母,其余用星号代替
def anonymize_surname(surname):
new_surname = surname[0]+(len(surname)-1)*'*'
return new_surname
df['Surname'] = df['Surname'].map(anonymize_surname)
display(df.head())
探索性数据分析
01 描述性可视化
我们先来分析客户基本信息和流失与否的关系
fig = plt.figure(figsize=(15,6))
ax1 = fig.add_subplot(1,3,1)
ax2 = fig.add_subplot(1,3,2)
ax3 = fig.add_subplot(1,3,3)
# 客户流失与否和国家/地区的关系
geography_exited_pivot = df.pivot_table(index='Geography',columns='Exited',aggfunc='size',fill_value=0)
geography_exited_table = geography_exited_pivot.div(geography_exited_pivot.sum(axis=1),axis=0)*100
# display(geography_exited_table)
# 可视化
sns.barplot(x='Geography',y=geography_exited_table[1],data=geography_exited_table,ax=ax1,width=0.4)
ax1.set_title('客户流失与否和国家/地区的关系图',fontsize=12,pad=20)
ax1.set_ylabel('ExitedRate')
# 添加标签
for bar in ax1.patches:
x_loc = bar.get_x() + bar.get_width() / 2
y_loc = bar.get_height()
ax1.text(x_loc,y_loc,f'{y_loc:.1f}%',ha='center',va='bottom')
# 客户流失与否和性别的关系图
gender_exited_pivot = df.pivot_table(index='Gender',columns='Exited',aggfunc='size',fill_value=0)
gender_exited_table = gender_exited_pivot.div(gender_exited_pivot.sum(axis=1),axis=0)*100
sns.barplot(x='Gender',y=gender_exited_table[1],data=gender_exited_table,ax=ax2,width=0.3)
ax2.set_title('客户流失与否和性别的关系图',fontsize=12,pad=20)
ax2.set_ylabel('ExitedRate')
for bar in ax2.patches:
x_loc = bar.get_x() + bar.get_width() /2
y_loc = bar.get_height()
ax2.text(x_loc,y_loc,f'{y_loc:.1f}%',ha='center',va='bottom')
# 客户流失与否和年龄的关系
sns.boxplot(x='Exited',y='Age',data=df,ax=ax3)
ax3.set_title('客户流失与否和年龄的关系图',fontsize=12,pad=20)
plt.show()
小结:
1.德国客户的流失率高于其他两个国家客户约一倍
2.女客户的流失率要高于男客户
3.流失客户的平均年龄约为45岁,而非流失客户的平均年龄约为36岁,可能年龄越大就容易流失。
接下来分析客户的财务状况与流失与否的关系
fig = plt.figure(figsize=(15,6))
ax1 = fig.add_subplot(1,2,1)
ax2 = fig.add_subplot(1,2,2)
sns.boxplot(x='Exited',y='Balance',data=df,ax=ax1,width=0.4)
ax1.set_title('客户流失状态与余额的关系图',fontsize=12,pad=20)
sns.boxplot(x='Exited',y='EstimatedSalary',data=df,ax=ax2,width=0.4)
ax2.set_title('客户流失状态与预估工资的关系图',fontsize=12,pad=20)
plt.show()
小结:
1.流失客户的卡上余额高于非流失客户的
2.流失客户和非流失客户的预估工资差别不大,可能相关性不高。
继续分析客户的信用与关系和流失与否的关系图
plt.rcParams['font.sans-serif']=['Microsoft YaHei']
fig = plt.figure(figsize=(12,10))
ax1 = fig.add_subplot(2,2,1)
ax2 = fig.add_subplot(2,2,2)
ax3 = fig.add_subplot(2,2,(3,4))
sns.boxplot(x='Exited',y='CreditScore',data=df,width=0.4,ax=ax1)
ax1.set_title('客户流失状态与信用评分的关系图',fontsize=15,pad=20)
# 客户流失状态与购买数量的关系
num_exited_pivot = df.pivot_table(index = 'NumOfProducts',columns = 'Exited',aggfunc='size',fill_value=0)
num_exited_table = num_exited_pivot.div(num_exited_pivot.sum(axis=1),axis=0)*100
sns.barplot(x='NumOfProducts',y=num_exited_table[1],data=num_exited_table,ax=ax2,width=0.4)
ax2.set_title('客户流失状态与购买数量的关系图',fontsize=12,pad=20)
ax2.set_ylabel('ExitedRate')
for bar in ax2.patches:
x_loc = bar.get_x() + bar.get_width() / 2
y_loc = bar.get_height()
ax2.text(x_loc,y_loc,f'{y_loc:.1f}%',ha='center',va='bottom')
# 客户流失状态与合作年限的关系
tenure_exited_pivot = df.pivot_table(index = 'Tenure', columns='Exited',aggfunc='size',fill_value=0)
tenure_exited_table = tenure_exited_pivot.div(tenure_exited_pivot.sum(axis=1),axis=0)*100
sns.barplot(x='Tenure',y=tenure_exited_table[1],data=tenure_exited_table,ax=ax3,width=0.4)
ax3.set_title('客户流失状态与合作年限的关系图',fontsize=12,pad=20)
ax3.set_ylabel('ExitedRate')
for bar in ax3.patches:
x_loc = bar.get_x() + bar.get_width()/2
y_loc = bar.get_height()
ax3.text(x_loc,y_loc,f'{y_loc:.1f}%',ha='center',va='bottom')
plt.tight_layout()
plt.show()
小结:
1.流失客户和非流失客户的信用评分差不多,可能相关性不高
2.拥有3-4个产品的客户流失率高,可能是银行产品的实际效用未达到客户的期望,导致客户感觉投资回报低。
3.不同合作年限的客户有不同程度的流失率,但差别不是很大,流失率最低为17.2%,最高为23.0%。
分析客户的行为与偏好和流失与否的关系
fig = plt.figure(figsize=(15,6))
ax1 = fig.add_subplot(1,2,1)
ax2 = fig.add_subplot(1,2,2)
# 客户流失与否和是否持有信用卡的关系
card_exited_pivot = df.pivot_table(index='HasCrCard',columns='Exited',aggfunc='size',fill_value=0)
card_exited_table = card_exited_pivot.div(card_exited_pivot.sum(axis=1),axis=0)*100
sns.barplot(x='HasCrCard',y=card_exited_table[1],data=card_exited_table,ax=ax1,width=0.3)
ax1.set_title('客户流失与否和是否持有信用卡的关系图',fontsize=12,pad=20)
ax1.set_ylabel('ExitedRate')
for bar in ax1.patches:
x_loc = bar.get_x() + bar.get_width() / 2
y_loc = bar.get_height()
ax1.text(x_loc,y_loc,f'{y_loc:.1f}%',ha='center',va='bottom')
# 客户流失与否和是否活跃会员的关系
ac_exited_pivot = df.pivot_table(index='IsActiveMember',columns='Exited',aggfunc='size',fill_value=0)
ac_exited_table = ac_exited_pivot.div(ac_exited_pivot.sum(axis=1),axis=0)*100
sns.barplot(x='IsActiveMember',y=ac_exited_table[1],data=ac_exited_table,ax=ax2,width=0.3)
ax2.set_title('客户流失与否和是否活跃会员的关系图',fontsize=12,pad=20)
ax2.set_ylabel('ExitedRate')
for bar in ax2.patches:
x_loc = bar.get_x() + bar.get_width() / 2
y_loc = bar.get_height()
ax2.text(x_loc,y_loc,f'{y_loc:.1f}%',ha='center',va='bottom')
plt.show()
小结:
1.是否拥有信用卡的客户的流失率比较接近
2.非活跃会员的客户流失率较大
02 皮尔逊相关系数
# 计算各个特征与客户流失与否的相关性
# 皮尔逊相关系数的计算适用于连续变量,所以要先排除非数值列
numeric_df = df.select_dtypes(include=[np.number])
numeric_df = numeric_df.drop(['IsActiveMember','HasCrCard'],axis=1) # 删掉二分类变量
correlation_matrix=numeric_df.corr()
corr_df = pd.DataFrame(data=correlation_matrix,columns=['Exited']).reset_index().sort_values('Exited',ascending=False)
corr_df = corr_df.rename(columns={'index':'Feature','Exited':'Exited_Corr'})
display(corr_df)
小结:
由此可以看出,
Age和Exited有正相关性28.5%,说明客户年龄越大流失率可能越高。
Balance和Exited有正相关性11.8%,说明客户银行卡的余额越足够流失率可能越高。
IsAciterMember和Exited有负相关性-15.6%,客户越活跃流失率可能越高。
小拓展:
皮尔逊相关系数只能说明某些连续变量和目标变量之间存在某种程序的线性关系模式。例如,账户余额和客户流失与否呈高负相关,意味着账户余额越大的客户,其流失的可能性越低。
皮尔逊相关系数揭示了变量间的关系强度和方向,但并不能直接证明因果关系。
为了确定这些变量是否对客户流失有显著影响,需要进一步采取例如逻辑回归、随机森林等建模方法,或者使用T检验、U检验、卡方检验等统计方法来测试对比流失与未流失客户在这些变量上的均值差异。
03 T检验/U检验
注:连续变量适用于T检验/U检验。
在进行T检验/U检验之前需要先用KS检验判断其特征数据是否服从正态分布(原假设为服从正态分布),服从的可以使用T检验进行计算,否则使用U检验进行计算
思路:
创建一个空DataFrame,用于存储每个特征变量的统计测试结果
分别读取流失与未流失的特征变量数据
用KS检验方法判断流失与未流失在其特征变量上是否服从正态,如果都服从正态则使用T检验否则使用U检验
把该特征变量的KS检验、U检验、T检验的结果添加到数据框里
每个特征变量重复执行以上code2、3、4一次
实现:
创建一个参数为dataframe、features_list的自定义函数。将重复执行的code使用for语句简化,来遍历特征变量列表里每一个特征变量,判断该特征变量是否服从正态分布,是的话则使用T检验否则使用U检验,接着将其结果添加到数据框里。
# ks检验
def analyze_features(data,features):
# 初始化DataFrame,用来存储检验结果的数据
results = pd.DataFrame(columns=
['Feature','KS Statistic Exited','P-value Exited','KS Statistic Not Exited','P-value Not Exited',
'T Statistic','U Statistic','P-value Test'])
# 遍历feature list的每一个特征变量,并分别读取流失组和未流失组数据
for feature in features:
exited_feature = data[data['Exited']==1][feature]
not_exited_feature = data[data['Exited']==0][feature]
# ‘norm’:设理论分布为正态分布,将数组的分布和理论分布进行比较,返回ks统计量和p值
ks_statistic_exited,p_value_exited = stats.kstest(exited_feature,'norm',args=(exited_feature.mean(),exited_feature.std()))
ks_statistic_not_exited,p_value_not_exited = stats.kstest(not_exited_feature,'norm',args=(not_exited_feature.mean(),not_exited_feature.std()))
# p_value < 0.05(常见的显著性水平),说明有足够的证据拒绝原假设(服从正态分布),即服从正态分布的假设不成立
# 流失组和未流失组只要任何一组数据不满足正态分布,就倾向不依赖于正态性的U检验,以保证检验的有效性和准确性
if p_value_exited < 0.05 or p_value_not_exited <0.05:
# U检验
u_stat,p_value_test = stats.mannwhitneyu(exited_feature,not_exited_feature)
t_stat = None
else:
# T检验
t_stat,p_value_test = stats.ttest_ind(exited_feature,not_exited_feature,equal_var=False)
u_stat = None
# 将检验结果添加到result中
results = results._append({
'Feature':feature,
'KS Statistic Exited':ks_statistic_exited,
'P-value Exited':p_value_exited,
'KS Statistic Not Exited':ks_statistic_not_exited,
'P-value Not Exited':p_value_not_exited,
'T Statistic':t_stat,
'U Statistic':u_stat,
'P-value Test':p_value_test
},ignore_index=True)
# results.set_index('Feature',inplace=True)
return results
# 创建连续变量的列表,并调用analyze_features函数
features = ['Age','Balance','EstimatedSalary','Tenure','CreditScore','NumOfProducts']
stats_df = analyze_features(df,features)
display(stats_df)
结果解读:
- Age:
流失组:KS Statistic Exited=0.0.27487,P-value Exited=0.09039。表示流失组的年龄符合正态分布。
未流失组:KS Statistic Not Exited=0.118949,P-value Not Exited接近0。表示未流失组的年龄不符合正态分布。
由于至少有一组不符合正态分布,不可用T检验,改用U检验。
U检验:U Statistic=11874649.5,P-value Test接近0。表示两组在年龄上存在明显的显著差异,表明年龄是区分客户流失行为的一个重要因素。 - Balance:
流失组:KS Statistic Exited=0.186212,P-value Exited接近0。表示流失组的账户余额不符合正态分布。
未流失组:KS Statistic Not Exited=0.267897,P-value Not Exited接近0。表示未流失组的账户余额不符合正态分布。
U检验:U Statistic=9371186.5,P-value Test接近0。表示两组在账户余额上存在明显的显著差异,意味着账户余额与客户流失存在紧密关联。 - EstimatedSalary:
流失组:KS Statistic Exited=0.062064,P-value Exited接近0。表示流失组的预估薪水不符合正态分布。
未流失组:KS Statistic Not Exited=0.055141,P-value Not Exited接近0。表示未流失组的预估薪水不符合正态分布。
U检验:U Statistic=8250768.0,P-value Test=0.2270515。表示两组在预估薪水上不存在显著差异。 - Tenure:
流失组:KS Statistic Exited=0.108587,P-value Exited接近0。表示流失组的合作年限不符合正态分布。
未流失组:KS Statistic Not Exited=0.109136,P-value Not Exited接近0。表示未流失组的合作年限不符合正态分布。
U检验:U Statistic=7948575.5,P-value Test=0.1621938。表示两组在合作年限上不存在显著差异。 - CreditScore:
流失组:KS Statistic Exited=0.020679,P-value Exited=0.3437391。表示流失组的信用评分符合正态分布。
未流失组:KS Statistic Not Exited=0.019156,P-value Not Exited=0.005718289。表示未流失组的信用评分不符合正态分布。
U检验:U Statistic=7839548.0,P-value Test=0.01986866。表示两组在合作年限上存在显著差异,但其显著性较其他特征低。 - NumOfProducts:
流失组:KS Statistic Exited=0.415074,P-value Exited接近0。表示流失组的产品持有数量不符合正态分布。
未流失组:KS Statistic Not Exited=0.352939,P-value Not Exited=0.005718289。表示未流失组的产品持有数量不符合正态分布。
U检验:U Statistic=6830625.5,P-value Test接近0。表示两组在产品持有数量上存在明显的显著差异,表明产品持有数量是预测流失客户的一个关键指标。
通过对各项特征的分析,我们可以明确年龄、账户余额、信用评分、产品数量在流失组和未流失组之间显示出显著差异,这些特征理解与预测流失客户行为至关重要
04 卡方检验
注:分类变量适用于卡方检验
def chi_square_features(data,features):
results = pd.DataFrame(columns=['Feature','Chi_Square','P_Value'])
for feature in features:
# .crosstab()主要生成分类变量的列联表,会自动按目标变量分类。因此不需要像前面T/U检验需要手动分两个数据子集。
crosstab = pd.crosstab(data[feature],data['Exited'])
chi_square,p_value,dof,expected = stats.chi2_contingency(crosstab)
row = {'Feature':feature,'Chi_Square':chi_square,'P_Value':p_value}
results = results._append(row,ignore_index=True)
# results.set_index('Feature',inplace=True)
return results
features = ['Geography','Gender','HasCrCard','IsActiveMember']
chi_square_df = chi_square_features(df,features)
display(chi_square_df)
由此可以发现,GeoGraphy、Gender、IsActiveMember在流失组和非流失组上有显著差异,表面这些分散特征变量是预防客户流失的几个关键特征,而HasCrCard没有显著差异。
客户细分(聚类算法)
我们采用机器学习的无监督学习中的聚类算法,来将客户进一步聚类,值得注意的是K-means适用于连续变量的数据聚类。在聚类之前我们需要选择一些合适的特征:
# 深度复制原数据集
df_kmeans = df.copy(deep=True)
# 选择一些合适的连续变量特征
features = df_kmeans[['CreditScore','Age','Tenure','Balance','NumOfProducts','HasCrCard','IsActiveMember']]
# 进行数据标准化处理,减少数据之间误差
scaler = StandardScaler()
scaler_features = scaler.fit_transform(features)
# 创建聚类算法对象,并训练
kmeans = KMeans(n_clusters=3,random_state=42)
clusters = kmeans.fit_predict(scaler_features)
# 将聚类结果添加到df数据框里
df_kmeans['Clusters'] = clusters
display(df_kmeans[['CustomerId','Clusters']].head())
接下来对三个客户群体进行分析各其特征和流失率
clusters_analysis = df_kmeans.groupby(by='Clusters').agg(
{'CreditScore':'mean','Age':'mean','Tenure':'mean','Balance':'mean','EstimatedSalary':'mean','Exited':'mean'}
).reset_index().round(2)
display(clusters_analysis)
小结:
客户群体0:账户平均余额较高,但平均估计工资比较低,平均流失率为16%。
客户群体1:账户平均余额和平均估计工资最低,平均流失率为16%。
客户群体2:账户平均余额和平均估计工资最高,平均流失率为30%,是这三个群体中流失率最高的。可能是因为高收入客户群体在服务品质、技术创新、个性化需求等方面上有着更高的期待,但该银行目前满足不了,因此导致客户流失。
特征重要性分析
采用随机森林算法建模,预测流失客户的一些特征的重要性。
随机森林算法虽然可以用于分类变量和连续变量,但是由于模型训练要求所有输入都是数值形式,这不意味着模型本身不能处理分类数据,而是在训练前必须对字符型分量数据进行编码转换,例如独热编码One-Hot Encoding(用于无序分类数据)或者标签编码Label Encoding(用于有序分类数据),这些编码技术将分类数据转换数值形式,使得模型能够理解并处理这些数据,同时保留了原始数据的类别信息。
由于Gender是二分类特征数据,可以直接用于模型训练,因此不需要特别转换。因为可能在预处理阶段会被编码为数字,这种简单的数值映射通常不会引起问题。模型会基于类别值的离散性进行处理,而不是视为数值大小。(但是在这个案例中我们需要手动将其编码为数字。)
但Geography是无序分类数据(超过二分类),需要使用独热编码One-Hot Encoding处理。不能使用数值映射,因为分配的数值对该模型来说具有意义,如果随机分配数值,可能会影响模型计算。
# 将Geography转换为独热编码
# 原来的Geography列会消失,取而代之的是若干新增的列,每一列对应着列中的一个唯一类别
df_encoded = pd.get_dummies(df,columns=['Geography'])
# 将Gender转换为标签编码(直接将类别映射到数字)
df_encoded['Gender'] = df_encoded['Gender'].map({'Female':1,'Male':0})
del df_encoded['Exited']
# 准备数据集,特征数据和目标数据
x,y = df_encoded.iloc[:,2:],df['Exited']
# 将数据集划分训练数据集和测试数据集
x_train,x_test,y_train,y_test = train_test_split(x,y,test_size=0.2,random_state=42)
# 选择模型并训练
model = RandomForestClassifier()
model.fit(x_train,y_train)
# 使用训练好的模型预测
pre = model.predict(x_test)
# 计算并输出该模型的预测准确率
print(f'Model prediction accuracy:{accuracy_score(y_test,pre)*100:.2f}%')
# 使用训练好的模型输出特征的重要性
feature_importance = (model.feature_importances_*100).round(2)
feature_df = pd.DataFrame(data=feature_importance,index=df_encoded.columns[2:],columns=['Importance']).sort_values(by='Importance',ascending=False)
feature_df.reset_index(inplace=True)
feature_df = feature_df.rename(columns={'index':'Feature'})
display(feature_df)
接下来把各个特征的皮尔逊相关系数、U/T检验、卡方检验和特征重要性结果数据合并起来,方便阅读和分析。
df1 = pd.merge(feature_df,corr_df,how='outer',on='Feature')
df2 = pd.merge(df1,stats_df.iloc[:,[0,-2,-1]],how='outer',on='Feature')
analyze_df = pd.merge(df2,chi_square_df,how='outer',on='Feature')
display(analyze_df)
根据以上分析结果来看,Age、GreditScore、NumOfProducts、Balance、IsActiveMember这些特征显示出了较高的特征重要性/与Exited有显著相关性/统计学差异,因此我认为它们应当成为模型的重要输入。
而EstimatedSalary、Tenure尽管相关性或U检验结构不太显著,但是考虑到他们有一定的特征重要性,也应当保留作为模型的输入,因为它们可能与其他特征一起共同贡献于预测能力。
另外,Gender、Geography的特征重要性没有那么高,但它们显示出高度的卡方检验显著性(p值接近0),意味着它们对于预测客户流失是有价值的,因此也应该保留作为模型的输入。
还有,HasCrCard在特征重要性、相关性或卡方检验上的表现不是特别突出,但是在业务理解上有其特定意义,比如信用卡持有状态可能间接反映客户的忠诚度和消费习惯,也可以考虑作为模型的输入。
综上,Age、GreditScore、NumOfProducts、Balance、IsActiveMember、EstimatedSalary、Tenure、Gender、Geography、HasCrCard这些特征应当作为流失预测模型的输入。
客户流失预测的建模
# 1.数据处理
df_sklearn = df.copy(deep=True)
df_sklearn['Gender'] = df_sklearn['Gender'].map({'Male':0,'Female':1})
# df_sklearn.info()
# 2.特征与目标变量
nfeatures = df_sklearn.iloc[:,2:-1]
target = df_sklearn['Exited']
# 3.分割数据集
x_train,x_test,y_train,y_test = train_test_split(nfeatures,target,test_size=0.2,random_state=42)
# 4.预处理:数值特征标准化,类别特征OneHot编码
# 自动识别数据类型选取指定特征变量
numeric_features = nfeatures.select_dtypes(include=[np.number]).drop(columns=['Geography','Gender','HasCrCard','IsActiveMember'],errors='ignore').columns.tolist()
categorical_features = ['Geography','Gender','HasCrCard','IsActiveMember']
# 数值特征标准化的转换器定义
numeric_transformer = StandardScaler()
# 独热编码的转换器定义
categorical_transformer = OneHotEncoder(handle_unknown = 'ignore')
# 列变换器
# ColumnTransformer根据特征类型分别应用不同的变换器到数据集
preprocessor = ColumnTransformer(
transformers=[
('num',numeric_transformer,numeric_features),
('cat',categorical_transformer,categorical_features)
]
)
# 5.预处理数据
# 应用预处理器对训练集进行拟合并转换.fit_transform会先根据训练数据学习必要的参数(比如StandardScaler的学习均值和方差),然后应用转换。
x_train_processed = preprocessor.fit_transform(x_train)
# 对测试集进行转换。因为预处理器已经通过训练集学会了转换规则,这里只需直接应用相同的转换规则到测试数据上。
x_test_processed = preprocessor.transform(x_test)
# 随机森立模型参数调优
param_grid = {
'n_estimators': [50, 100, 200],
'max_depth': [None, 10, 20, 30],
'min_samples_split': [2, 5, 10],
'min_samples_leaf': [1, 2, 4]
}
grid_search = GridSearchCV(RandomForestClassifier(random_state=42),param_grid,cv=5,scoring='roc_auc')
grid_search.fit(x_train_processed,y_train)
# 获取最佳参数
best_params = grid_search.best_params_
print('Best Parameters Found:',best_params)
# 使用最佳参数构建模型并训练
# **best_params解包字典为关键字参数,值传递给对应函数参数
optimized_rf = RandomForestClassifier(**best_params,random_state=42)
optimized_rf.fit(x_train_processed,y_train)
# 预测
predictions = optimized_rf.predict(x_test_processed)
# probs是输出正负类概率的二维数组,probs[:,1]是正类概率的一维数组
# pridict_proba()是随机森林分类器的一个方法,它接受一个特征数组作为输入,并返回每个样本属于各个类别的概率。这对于后续进ROC曲线绘制非常有用的。
probs = optimized_rf.predict_proba(x_test_processed)[:,1]
# 模型评估
# 准确率:表示分类正确的频率,越高越好.
accuracy = accuracy_score(y_test,predictions)
# 精确率:表示预测为正例的准确性
precision = precision_score(y_test,predictions)
# 召回率:表示模型识别所有正例的能力(如在医疗诊断中漏检的后果非常严重)
recall = recall_score(y_test,predictions)
# ROC曲线下面积:评估模型的整体分类能力,值越接近1表示分类器的性能越好
roc_auc = roc_auc_score(y_test, probs)
print(f'Accuracy:{accuracy}\n Precision:{precision}\n Recall:{recall}\n Roc_Auc:{roc_auc}')
# 绘制ROC曲线
fpr, tpr, _ = roc_curve(y_test, probs)
plt.figure(figsize=(8, 6))
plt.plot(fpr, tpr, label=f'ROC curve (area = {roc_auc:.2f})')
plt.plot([0, 1], [0, 1], 'k--')
plt.xlim([0.0, 1.0])
plt.ylim([0.0, 1.05])
plt.xlabel('False Positive Rate')
plt.ylabel('True Positive Rate')
plt.title('Receiver Operating Characteristic (ROC)')
plt.legend(loc="lower right")
plt.show()
小结:
准确率Accuracy:0.863,意味着该模型在预测客户是否流失方面有86.3%的正确率。
精确率Precision:0.776,表示当模型预测客户会流失时,其预测正确的比例由77.6%。
召回率Recall:0.425,说明在实际流失的客户中,模型能够识别出42.5%的比例。这个值相对较低,暗示模型在捕捉所有真实流失客户方面有待提升。
ROC_AUC:0.866,展现了模型在区分正负样本的整体能力,接近0.9的表明模型具有较好的分类性能。
该随机森林模型在客户流失预测任务上表现良好,尤其是在保持较高的准确率和ROC_AUC的同时,也展现出了一定的预测精确率。然而,较低的召回率提示我们,未来的工作应该聚焦于模型识别真正流失客户的灵敏度,可能通过引入更多与客户流失的高度相关的特征、调整模型参数或探索更复杂的模型结构来实现。