当前位置: 首页 > news >正文

基于Cox风险比例模型的会员用户流失预测研究

一、问题

你是某银行的数据分析师,近日运营部发现用户的次日留存率较市场平均用户留存率相比偏低且持续下降,于是向你询问,分析是什么原因导致这批用户的每日留存率持续下降?

二、方法论

明确目标:用户的次日留存率持续下降

目标拆解:5W2H

* what:用户次日留存率是否真的呈持续下降走势?
数据异动分析:
1. 数据传输是否异常
2. 季节效应
3. 营销活动、重大事件影响
4. 统一口径

* who:什么样的用户次日留存率下降了?
流失用户定义:核心用户/非核心用户
用户标签体系
用户属性分析:
1. 社会属性:年龄、职业、性别、学历、区域
2. 商业属性:付费钱数、付费次数、付费频率
3. 行为属性:点击行为、浏览行为、搜索行为
4. 内容属性:产品数量、产品喜好
5. 设备属性:品牌、机型

* where/when:什么时候用户流失了?哪一步流失了?
用户指标体系:
用户生命周期AARRR:获取->激活->留存->变现->推荐
用户行为路径UJM:注册->登录->加购->购买->复购
漏斗分析
流失前N步分析

* why:因为什么(特征)流失了?
因果推断
归因分析

* how:怎么做?(怎么分析流失用户)
OSM(Object-Strategy-Measure)拆解
ML(Machine Learning)机器学习:Kmeans,XGBoost,CoxPH,SHAP,…

* how much:怎么验证结论?(衡量结果)
结果优化:用户画像
1. 用户属性分析
2. 用户产品体验:
① 表现层:产品语言设计、布局
② 框架层:操作体验、刷新、页面跳转
③ 结构层:常规功能、特色功能、用户流程
④ 范围层:核心功能,满足用户什么样的需求
⑤ 战略层:产品定位、商业模式及其后续发展
结果验证:AB test

三、举例

(1) 作为一名数据分析师,我会首先判断该用户次日留存率下降的主人公以及事件的真实性,就以核心用户次日留存率下降来说,
① 是否因为季节周期效应或者最近一段时间的营销活动等造成的局部次日留存率下降。这可以通过对比分析如同比、环比等判断最近几月与最近几年的核心用户留存率情况;
② 数据来源的统一口径是否一致。这可以通过核对数据埋点设计的计算方式与口径是否一样,比如对于DAU(日活用户)的统计,以全局唯一用户ID为最小维度进行统计和以设备唯一ID最小维度进行统计都会带来一定的偏差。
结果发现都不是这些因素引起的,需进一步分析;

(2) 我流失的这部分核心用户群的用户画像是什么样的,核心流失用户是怎么定义的。
① 核心用户画像可以通过已经存在的标签体系,如通过RFM(Recently-Frequence-Money)模型确立的优质/良好/普通用户进行初步划分,也可以通过用户属性的5个维度进行精细划分,比如社会属性的年龄、性别、地区等,商业属性的账户余额、预估年薪等,行为属性的信用评分、是否活跃用户等,内容属性的产品数量、是否持有信用卡等。这里简单地将办过银行产品的用户都称之为核心用户(会员用户)。
② 用户流失的定义有两种,一种是拐点法,一种是分位数法:
所谓拐点法,即判断N日用户回归率或者N日用户留存率随N递增构建的曲线逐渐趋于平缓的点就是拐点,拐点对应的N天就是用户的流逝周期。其中,

   所谓分位数法,即统计一段时间内的所有用户活跃时间间隔长度,从该段时间内的第一天算起,累加每天的用户活跃时间间隔占比,直到第N天的累加占比达到90%,就认为N天是用户的流逝周期。

 比如使用拐点法定义会员流失用户周期,通过计算N日的流失用户回归率,当N=20及以后回归率趋于平缓,那么会员用户的流失周期就是20天。而这里则是会员用户关闭了该产品的服务,就定义为用户流失了。之后可以根据时间周期,如每天、20天、一个月,统计用户的流失率。

(3) 通过以上分析确定了会员用户的用户画像,以及流失的周期,接下来需要分析会员用户到底是在哪一步流失了。这里通过OSM+AARRR的方式分析拆解流失用户。
OSM(Object-Strategy-Measure)即目标O-策略S-指标M。
① 既然用户已经流失了,那么明确目标O:提升会员用户的召回率;
② 用户在第一次了解产品到最后停止产品服务就是一整个用户生命周期,所以制定的策略就是AARRR:获取-激活-留存-付费-推荐;
③ 最后需要对每一步进行指标的拆解,也就是指标M:新用户数量–日/周/月活跃用户数–留存用户数–付费用户数–分享用户数
对应的二级指标具体到个体用户则可以是:注册账户时长–活跃时间间隔时长、点击率–活跃时长、次日活跃时长–付费次数、付费金额–分享次数

(4) 按以上方法找到了能提升会员用户的召回率的一些相关指标,这一步就是对这些指标进行分析,会员用户的流失到底和什么因素(特征)有关。这里采用机器学习(Machine Learning)的方法来进行特征的归因。
① XGBoost:构建基于树的集成学习算法模型,分析影响会员用户流失的输入特征有哪些,并输出对应的特征重要度;
② 为了验证建立模型的正确率,采用交叉验证的方式检验该模型的鲁棒性,并通过精确率、召回率和F1值这些评价指标给模型打分。

(5) 通过机器学习,即使我们能正确分析出是由哪些特征因素导致的会员用户流失情况偏高,但如果用户已经流失了,那么再去召回的概率也很小了。所以还得预测当前的会员用户在未来是否会流失,什么时候流失。
① Cox风险比例模型:生存分析中的半参数模型,其相比于传统的机器学习模型来说,不仅有预测流失用户的能力,也可以预测用户的具体流失时间。其评价标准是一致性指数(Concordance Index),其越接近于1,表示模型训练效果越好;
② SHAP分析:发展于博弈论中正负贡献的Shapley值,在机器学习模型预测正确的情况下,相比于整体的特征重要度,能更精确地反应每个用户的不同特征影响程度,比如用户A流失的很大程度上是因为因素x,用户B流失的很大程度上是因为因素y,从而实现不同用户的精确召回,为运营部门做数据支撑与业务建议。

(6) 最终,我们找到了具有某些特征的会员用户群体,其流失率较大,比如年龄在40~60岁之间,在银行只办过一种产品的会员用户,其流失率较高。
① 定制召回策略:针对这类用户,可以进一步分析具体会员流失用户到底办的是什么样的产品,如果大部分办的都是产品A,针对该产品提出一些有助于用户召回的合理改进建议,比如,发现大部分会员用户办理的都是某一款定期存款产品,那么在保证利润不变或ROI能提高的情况下,是否能为会员用户免费升级该产品,提升更多的存储金额额度以及利息。否则,需进一步分析用户的行为路径和用户的产品体验。
② 衡量策略指标:ABtest。针对提出的召回策略,进行AB实验,步骤如下:
1. 对照组为当前产品不变的会员用户,实验组为升级当前产品的会员用户;
2. 原假设为升级当前产品的会员用户平均流失率与不升级没有差别,备择假设为升级当前产品的会员用户平均流失率与不升级有差别;
3. 视原产品的每日用户流失率为总体,那么均值和方差都是已知的,采用Z检验统计量(总体与样本),定义置信度α=0.05,以及期望能提升的每日流失率E=0.1%,计算所需的最小天数n;
4. 为实验组和对照组分配相同的流量,统计每组n天内会员用户流失率的均值和方差;
5. 计算Z检验/T检验统计量(样本与样本),查表获取p值,若p<α=0.05,则小概率事件成立,拒绝原假设,接收备择假设,即认为升级当前产品,会员用户的流失率有显著性降低;反之则接收原假设。

四、实操

根据所述方法论,包括但不限于用户画像、用户属性、用户指标、用户标签等,编写SQL进行多表联查,提取一段时间内如最近一年的会员用户的相关特征。  
本次数据集来源于Kaggle官网公开数据集:Kaggle, Shubham K. Churn Modelling-Deep Learning Artificial Neural Network Used\[EB/OL],总共10000 rows × 14 columns,每个字段的解释为如下:  RowNumber (行号):数据行的序号。  CustomerId (客户ID):客户的唯一标识ID。  Surname (姓氏):客户的姓氏。  CreditScore (信用评分):客户的信用评分,用于衡量客户的信用状况。  Geography (地理位置):客户所在地理位置或国家/地区。  Gender (性别):客户的性别,通常为男性或女性。  Age (年龄):客户的年龄。  Tenure (任期):客户在银行(或其他机构)已经持有账户的时间长度。  Balance (余额):客户在账户中的余额金额。  NumOfProducts (产品数量):客户持有的产品数量,可以是银行产品或其他相关产品。  HasCrCard (是否有信用卡):客户是否持有信用卡,通常用二元变量表示是否持有信用卡。  IsActiveMember (是否活跃会员):客户是否是活跃会员的标识,通常用二元变量表示是否活跃会员。  EstimatedSalary (预估工资):客户的预估年收入或工资。  Exited (是否已退出):一个标识变量,表示客户是否已经退出了银行的服务(例如关闭账户或取消产品)。其中0是非流失用户,1是流失用户  
代码如下:

# # 导入需要的包
import numpy as np
import pandas as pd
import scipy
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.preprocessing import LabelEncoder
import warnings
warnings.filterwarnings("ignore")
from sklearn.model_selection import train_test_split
from lifelines import CoxPHFitter
import xgboost as xgb
from sklearn.utils.class_weight import compute_class_weight
from sklearn.metrics import classification_report
from sklearn.inspection import permutation_importance
import shap
import scipy.stats
import gc
from sklearn.cluster import KMeans
from sklearn.preprocessing import StandardScaler```
```python
# # 读取数据
df_data=pd.read_csv("Churn_Modelling.csv")
df_data

image


# # 查看标签分布
ax = sns.countplot(x='Exited', data=df_data)
# 添加标题
plt.title('Exited Value Counts')
# 在每个条形的顶部添加具体数值
for p in ax.patches:ax.annotate(f'{p.get_height()}', (p.get_x() + p.get_width() / 2., p.get_height()),ha='center', va='center', fontsize=11, color='black', xytext=(0, 5),textcoords='offset points')
plt.show()

image
从图中我们可以看到,会员流失用户和非流失用户是一个数据不平衡的情况,后续应该做相应的处理。


# # 类别变量转换为数值型变量
df_data.loc[:, 'Geography'] = LabelEncoder().fit_transform(df_data.loc[:, 'Geography'])
df_data.loc[:, 'Gender'] = LabelEncoder().fit_transform(df_data.loc[:, 'Gender'])
df_data = df_data.drop(columns=['RowNumber', 'CustomerId', 'Surname'])# # 绘制关于Exited标签的数据分布直方图
statuses = set(df_data['Exited'])
plt.figure(figsize=(15,14))
for i, col in enumerate(df_data.columns.drop('Exited')):plt.subplot(4, 3, i+1)for status in statuses:sns.distplot(df_data.loc[df_data.Exited==status, col], label=f'{status} is {len(df_data[df_data.Exited == status])} counts', kde=True)plt.legend()
plt.show()

image
image
从数据分布图来看,初步认为年龄Age特征和持有产品数量NumOfProducts特征对是否是流失用户的判断有较大的影响。
因为数据都是处理好的,没有缺失值情况且客户ID唯一,可以直接用于训练集构建以及预测,实际上应该对提取的数据集做进一步的特征优化以及衍生特征构建。


'''
交叉验证
'''
for i in range(1,4): print('====================================')print(f'第{i}轮交叉验证')train_data, valid_data = train_test_split(df_data, test_size=0.2, stratify=df_data.loc[:, 'Exited'])def balanced_weight(y_series):# 假设有n个类别,训练数据的标签为 y_trainn_classes = len(set(y_series))# 计算类别权重class_weights = compute_class_weight(class_weight="balanced", classes=np.unique(y_series), y=y_series)print('类别权重:', class_weights)# 将类别权重转换为样本权重(sample weight)sample_weights = np.zeros(len(y_series))for class_index, weight in enumerate(class_weights):sample_weights[y_series == class_index] = weightreturn sample_weightsdef model_fit(X_df,y_series,sample_weights):if len(set(y_series))==2:objective = 'binary:logistic'else:objective = 'multi:softmax'params = {'objective':objective #目标函数}# 定义XGBoost模型xgb_model = xgb.XGBClassifier(**params).fit(X_df, y_series,sample_weight=sample_weights)return xgb_model'''模型训练'''label = 'Exited'X_train = train_data.drop(columns=label)y_train = train_data.loc[:, label]sample_weights = balanced_weight(y_train)xgb_model = model_fit(X_train,y_train,sample_weights)print(f'第{i}轮模型训练完成,xgb参数:',xgb_model.get_params())'''排序重要度'''def feature_importance(xgb_model, X_train, y_train):# 计算排列重要性result = permutation_importance(xgb_model, X_train, y_train, n_repeats=5, random_state=42, scoring='neg_mean_squared_error')# 输出特征重要性结果importance = result.importances_meanfeature_names = X_train.columnsdict_values = dict(zip(feature_names, importance))# 使用 sorted 函数和匿名函数对字典的值进行排序sorted_dict_values = sorted(dict_values.items(), key=lambda x: x[1], reverse=True)[:20]keys = [item[0] for item in sorted_dict_values]values = [item[1] for item in sorted_dict_values]plt.figure(figsize=(14,4))sns.barplot(x=values, y=keys)plt.xlabel('Value')plt.ylabel('Key')plt.title('Top 20 Permutation Importance')for i, v in enumerate(values):plt.text(v + 0.001, i, str(np.round(v,4)), color='black', fontweight='bold')plt.tight_layout()plt.show()feature_importance(xgb_model, X_train, y_train)'''模型预测'''X_valid = valid_data.drop(columns=label)y_valid = valid_data.loc[:, label]y_pred_valid = xgb_model.predict(X_valid)# 生成分类报告report = classification_report(y_valid, y_pred_valid)print(f'第{i}轮模型预测报告:')print(report)

image
image
image
image
image
image
通过3轮的交叉验证(80%作为训练集,20%作为验证集)可以知道,其中Age和NumOfProducts特征的重要度最高,这和我们初步观察的数据分布图得出的结论一致。从模型预测报告来说,对于流失用户(1)的预测谈不上特别好,但至少比随机预测的概率要高(f1-score>(407/2000=20.35%))。


'''
划分训练集和验证集
'''
train_data, valid_data = train_test_split(df_data, test_size=0.2, stratify=df_data.loc[:, 'Exited'])
print(df_data.columns)'''
Cox风险比例模型
'''
formula = 'CreditScore + Geography + Gender + Age + Balance+ NumOfProducts+ HasCrCard + IsActiveMember + EstimatedSalary'
cox_model = CoxPHFitter(penalizer=0.01, l1_ratio=0)
cox_model = cox_model.fit(train_data, duration_col='Tenure', event_col='Exited',formula=formula)
cox_model.print_summary() # 一致性指数Concordance越接近1,预测效果越好'''
一致性检验
'''
# 如果变量的影响程度越高(p值越显著),变量的风险概率也越高,那么模型的一致性指数就越高
plt.figure(figsize=(7,6))
cox_model.plot(hazard_ratios=True)
plt.xlabel('Hazard Ratios (95% CI)')
plt.title('Hazard Ratios')
plt.show()

image
image
image
可以看到,CoxPH模型对于Gender、Age、Balance、IsActiveMember特征的显著性检验p值<0.005,表明这些变量对于模型的预测结果来说具有很高的影响力。
Concordance是一致性指数,越接近1表明模型训练效果越好,这里的Concordance=0.71;模型的一致性指数可以理解为:如果变量的影响程度越高(p值越显著),变量的风险概率(Hazard Ratios)也越高。
该CoxFH模型的训练效果为良。


'''
Cox流失用户预测
'''
df_predict_0 = cox_model.predict_survival_function(valid_data.loc[valid_data['Exited']==0, :])
df_predict_0df_predict_1 = cox_model.predict_survival_function(valid_data.loc[valid_data['Exited']==1, :])
df_predict_1print('正常用户一致性:', np.round(1 - (df_predict_0.iloc[-1]<0.5).sum()/len(df_predict_0.iloc[-1]),2)*100, '%')
print('流失用户一致性:', np.round((df_predict_1.iloc[-1]<0.5).sum()/len(df_predict_1.iloc[-1]), 2)*100, '%')
plt.figure(figsize=(6, 6))
observe_index = 5
i = observe_index
observe_df = df_predict_1
while i < observe_index + 5:plt.plot(observe_df.loc[:, observe_df.columns[i]], label=observe_df.columns[i])i = i + 1
plt.axhline(y=0.5, color='black', linestyle='--')
plt.text(10, 0.47, 'Threshold=0.5', color='black', fontsize=12)  # 在y=0.5处添加文本
plt.xlabel('Timeline')
plt.ylabel('Retain probability')
plt.title('The Churn Trend of Samples')
plt.xticks(range(0, 11, 1))
plt.legend(loc='best')
plt.show()

image
从生存函数图得知预测结果,当retain probability<0.5的时候就认为该会员用户有流失的风险了。这里随机取了5个会员用户预测他的流失时序情况,比如202用户没有流失,其他用户均流失了。实际上这幅生存函数图是基于已经知道的流失用户群体来绘制的,理论情况是图中所有会员用户都呈现流失情况,但202用户正好相反,所以通过一定的计算方式即一致性指标来评价CoxPH模型的预测正确率,其中预测非流失用户的一致性为74%,预测流失用户的一致性为63%。

五、调优

结合上述XGBoost模型和CoxPH模型的训练和预测结果,我们可以知道模型整体效果为良,且都有在Age、NumOfProducts、Balance等特征上表现一定的重要度和影响性,于是可以针对这些特征做进一步的数据探索和挖掘,比如将年龄划分为多个维度:0\~20岁、20\~40岁、40\~60岁、60岁及以上,分析不同用户群的流失情况及特征交互影响等。

# # 划分年龄维度
bins = [0, 20, 40, 60, 90]
labels = ['0~20','20~40','40~60','60~']
df_0 = df_data.loc[df_data.Exited==0, :]
df_0.loc[:, 'Age'] = pd.cut(df_0.loc[:, 'Age'], bins=bins, labels=labels)
print('正常用户:')
print(df_0.loc[:, 'Age'].value_counts())
df_1 = df_data.loc[df_data.Exited==1, :]
df_1.loc[:, 'Age'] = pd.cut(df_1.loc[:, 'Age'], bins=bins, labels=labels)
print('流失用户:')
print(df_1.loc[:, 'Age'].value_counts())

image
图中正常用户大部分集中在20~40岁之间,而流失用户大部分集中在40~60岁之间。


# # 正常用户基于年龄维度的持有产品数量对比
plt.figure(figsize=(14,4))
for i, age in enumerate(df_0.Age.unique()):plt.subplot(1, len(df_0.Age.unique()), i+1)sns.distplot(df_0.loc[df_0.Age==age, 'NumOfProducts'], label=f'{age} is {len(df_0[df_0.Age == age])} counts', kde=True)plt.legend()
plt.show()

image


# # 流失用户基于年龄维度的持有产品数量对比
plt.figure(figsize=(14,4))
for i, age in enumerate(df_1.Age.unique()):plt.subplot(1, len(df_1.Age.unique()), i+1)sns.distplot(df_1.loc[df_1.Age==age, 'NumOfProducts'], label=f'{age} is {len(df_1[df_1.Age == age])} counts', kde=True)plt.legend()
plt.show()

image
正常用户的持有产品数量在每个年龄段都呈“双峰”分布,而流失用户呈“小提琴”分布,这意味着流失用户群体的大部分用户都只持有1种产品。
通过更深层次地数据挖掘,最终确立流失用户群体为:
40~60岁之间没有办理额外业务卡的不活跃会员用户


# # 尝试根据['Age', 'NumOfProducts', 'Balance']通过KMeans聚类
train_data, valid_data = train_test_split(df_data, test_size=0.2, stratify=df_data.loc[:, 'Exited'])
def cluster_train(X_df):cols = X_df.select_dtypes(include=['float','int']).columns.tolist()# 创建标准化器对象scaler = StandardScaler()# 提取需要标准化的浮点型变量并转换为NumPy数组data = X_df[cols].values# 对数据进行标准化处理scaled_data = scaler.fit_transform(data)# 将标准化后的数据更新回DataFrame中的相应列X_df[cols] = scaled_data# 使用 k-means++ 聚类算法kmeans = KMeans(n_clusters=2, init='k-means++', max_iter=300, n_init=10, random_state=0)X_result = kmeans.fit_predict(X_df)return X_result, scaler, kmeansdef cluster_transform(X_df, scaler, kmeans):cols = X_df.select_dtypes(include=['float','int']).columns.tolist()data = X_df[cols].valuesscaled_data = scaler.transform(data)X_df[cols] = scaled_dataX_result = kmeans.predict(X_df)return X_resulttrain_f = train_data.loc[:, ['Age', 'NumOfProducts', 'Balance']]
valid_f = valid_data.loc[:, ['Age', 'NumOfProducts', 'Balance']]
train_K, scaler, kmeans = cluster_train(train_f)
valid_K = cluster_transform(valid_f, scaler, kmeans)
train_data.loc[:, 'cluster_K'] = train_K
valid_data.loc[:, 'cluster_K'] = valid_K
display(train_data.loc[:, 'cluster_K'].value_counts())
display(valid_data.loc[:, 'cluster_K'].value_counts())

image
尝试根据Age、NumOfProducts、Balance特征构建KMeans聚类算法,将聚类结果作为衍生特征进入模型学习。


# # 除了聚类算法产生衍生特征之外,也构建了balance_age、salary_age、isOldOneProduct等衍生特征
'''
XGBoost模型调优
'''
for i in range(1,4): print('====================================')print(f'第{i}轮交叉验证')df_raw = df_datadf_raw.loc[:, 'balance_age'] = df_raw.loc[:, 'Balance']/df_raw.loc[:, 'Age']df_raw.loc[:, 'salary_age'] = df_raw.loc[:, 'EstimatedSalary']/df_raw.loc[:, 'Age']df_raw.loc[:, 'isOldOneProduct'] = 0df_raw.loc[(df_raw.Age>=40)&(df_raw.Age<=60)&(df_raw.NumOfProducts==1), 'isOldOneProduct'] = 1train_data, valid_data = train_test_split(df_raw, test_size=0.2, stratify=df_raw.loc[:, 'Exited'])def cluster_train(X_df):cols = X_df.select_dtypes(include=['float','int']).columns.tolist()# 创建标准化器对象scaler = StandardScaler()# 提取需要标准化的浮点型变量并转换为NumPy数组data = X_df[cols].values# 对数据进行标准化处理scaled_data = scaler.fit_transform(data)# 将标准化后的数据更新回DataFrame中的相应列X_df[cols] = scaled_data# 使用 k-means++ 聚类算法kmeans = KMeans(n_clusters=2, init='k-means++', max_iter=300, n_init=10, random_state=0)X_result = kmeans.fit_predict(X_df)return X_result, scaler, kmeansdef cluster_transform(X_df, scaler, kmeans):cols = X_df.select_dtypes(include=['float','int']).columns.tolist()data = X_df[cols].valuesscaled_data = scaler.transform(data)X_df[cols] = scaled_dataX_result = kmeans.predict(X_df)return X_resulttrain_f = train_data.loc[:, ['Age', 'NumOfProducts']]valid_f = valid_data.loc[:, ['Age', 'NumOfProducts']]train_K, scaler, kmeans = cluster_train(train_f)valid_K = cluster_transform(valid_f, scaler, kmeans)train_data.loc[:, 'cluster_K'] = train_Kvalid_data.loc[:, 'cluster_K'] = valid_Kdef balanced_weight(y_series):# 假设有n个类别,训练数据的标签为 y_trainn_classes = len(set(y_series))# 计算类别权重class_weights = compute_class_weight(class_weight="balanced", classes=np.unique(y_series), y=y_series)print('类别权重:', class_weights)# 将类别权重转换为样本权重(sample weight)sample_weights = np.zeros(len(y_series))for class_index, weight in enumerate(class_weights):sample_weights[y_series == class_index] = weightreturn sample_weightsdef model_fit(X_df,y_series,sample_weights):if len(set(y_series))==2:objective = 'binary:logistic'else:objective = 'multi:softmax'params = {'objective':objective #目标函数}# 定义XGBoost模型xgb_model = xgb.XGBClassifier(**params).fit(X_df, y_series,sample_weight=sample_weights)return xgb_model'''模型训练'''label = 'Exited'X_train = train_data.drop(columns=label)y_train = train_data.loc[:, label]sample_weights = balanced_weight(y_train)xgb_model = model_fit(X_train,y_train,sample_weights)'''模型预测'''X_valid = valid_data.drop(columns=label)y_valid = valid_data.loc[:, label]y_pred_valid = xgb_model.predict(X_valid)# 生成分类报告report = classification_report(y_valid, y_pred_valid)print(f'第{i}轮模型预测报告:')print(report)

image
image
image
从模型调优后的三轮交叉验证结果来看,模型预测正确率有略微提升。


'''
CoxPH模型调优
'''
df_raw = df_data
df_raw.loc[:, 'balance_age'] = df_raw.loc[:, 'Balance']/df_raw.loc[:, 'Age']
df_raw.loc[:, 'salary_age'] = df_raw.loc[:, 'EstimatedSalary']/df_raw.loc[:, 'Age']
df_raw.loc[:, 'isOldOneProduct'] = 0
df_raw.loc[(df_raw.Age>=40)&(df_raw.Age<=60)&(df_raw.NumOfProducts==1), 'isOldOneProduct'] = 1train_data, valid_data = train_test_split(df_raw, test_size=0.2, stratify=df_raw.loc[:, 'Exited'])
def cluster_train(X_df):cols = X_df.select_dtypes(include=['float','int']).columns.tolist()# 创建标准化器对象scaler = StandardScaler()# 提取需要标准化的浮点型变量并转换为NumPy数组data = X_df[cols].values# 对数据进行标准化处理scaled_data = scaler.fit_transform(data)# 将标准化后的数据更新回DataFrame中的相应列X_df[cols] = scaled_data# 使用 k-means++ 聚类算法kmeans = KMeans(n_clusters=2, init='k-means++', max_iter=300, n_init=10, random_state=0)X_result = kmeans.fit_predict(X_df)return X_result, scaler, kmeansdef cluster_transform(X_df, scaler, kmeans):cols = X_df.select_dtypes(include=['float','int']).columns.tolist()data = X_df[cols].valuesscaled_data = scaler.transform(data)X_df[cols] = scaled_dataX_result = kmeans.predict(X_df)return X_resulttrain_f = train_data.loc[:, ['Age', 'NumOfProducts']]
valid_f = valid_data.loc[:, ['Age', 'NumOfProducts']]
train_K, scaler, kmeans = cluster_train(train_f)
valid_K = cluster_transform(valid_f, scaler, kmeans)
train_data.loc[:, 'cluster_K'] = train_K
valid_data.loc[:, 'cluster_K'] = valid_Kformula = 'Geography + Gender + Age + Balance + NumOfProducts + EstimatedSalary + IsActiveMember + isOldOneProduct + cluster_K'
cox_model = CoxPHFitter(penalizer=0.01, l1_ratio=1)
cox_model = cox_model.fit(train_data, duration_col='Tenure', event_col='Exited',formula=formula)
cox_model.print_summary() # 一致性指数Concordance越接近1,预测效果越好# 如果变量的影响程度越高(p值越显著),变量的风险概率也越高,那么模型的一致性指数就越高
plt.figure(figsize=(7,6))
cox_model.plot(hazard_ratios=True)
plt.xlabel('Hazard Ratios (95% CI)')
plt.title('Hazard Ratios')
plt.show()

image
image
image


'''
CoxPH模型调优
'''
df_predict_0 = cox_model.predict_survival_function(valid_data.loc[valid_data['Exited']==0, :])
df_predict_0df_predict_1 = cox_model.predict_survival_function(valid_data.loc[valid_data['Exited']==1, :])
df_predict_1print('正常用户一致性:', np.round(1 - (df_predict_0.iloc[-1]<0.5).sum()/len(df_predict_0.iloc[-1]),2)*100, '%')
print('流失用户一致性:', np.round((df_predict_1.iloc[-1]<0.5).sum()/len(df_predict_1.iloc[-1]), 2)*100, '%')
plt.figure(figsize=(6, 6))
observe_index = 5
i = observe_index
observe_df = df_predict_1
while i < observe_index + 5:plt.plot(observe_df.loc[:, observe_df.columns[i]], label=observe_df.columns[i])i = i + 1
plt.axhline(y=0.5, color='black', linestyle='--')
plt.text(10, 0.47, 'Threshold=0.5', color='black', fontsize=12)  # 在y=0.5处添加文本
plt.xlabel('Timeline')
plt.ylabel('Retain probability')
plt.title('The Churn Trend of Samples')
plt.xticks(range(0, 11, 1))
plt.legend(loc='best')
plt.show()

image
从结果看,CoxPH模型的预测正确率也有一定的提升。
实际上,对于测试集,CoxPH模型应基于XGBoost的预测结果去拟合,因为本身的目的就是去预测那些未知会员用户是否是流失用户的。


#%% SHAP分析(基于树模型)
def focus_importance_sort(data ,model ,explainer_type = 'tree' ,top_num = 3):import shapimport numpy as npimport pandas as pd'''putput top n most important features(positive shap value) of each sample param :: data                !datasetparam :: model               !modelparam :: explainer_type      !explainer type                      default:TreeExplainerparam :: top_num             !top n most important features       default:3'''pass# 局部可解释性
test_data = focus_importance_sort(X_valid.reset_index(drop=True),xgb_model) #测试数据;模型
test_data.loc[test_data.y_pred==1, :]

image
image
最后,基于可解释性SHAP分析,对会员用户的流失情况进行归因,提供给运营部门精细建议,实现对用户的精确召回。
比如用户3,符合年龄大、年薪高且只办了一种产品的用户画像,这样的用户群体往往会更容易成为流失用户

六、展望

由于开源数据集的局限性,很多特征没办法去获取,就如【三、举例(6)】中而言,得到了这样的用户群体,可以进一步分析产品的类别、用户的产品体验等因素。  
其实最主要的不是模型要达到一个非常好的效果,因为测试集的不同,模型的每次预测也会产生偏差;而是通过模型识别出人为难以判断的关键业务逻辑,解决最真实的业务问题。

文章来源:https://www.cnblogs.com/zher/p/18106286

http://www.lryc.cn/news/578952.html

相关文章:

  • [云上玩转Qwen3系列之四]PAI-LangStudio x AI搜索开放平台 x ElasticSearch: 构建AI Search RAG全栈应用
  • CLIP heat map generation
  • vue中的toRef
  • SpringBoot控制反转
  • 无人机AI制导模块运行方式概述
  • Docker Desktop导致存储空间不足时的解决方案
  • 阿里巴巴Java开发手册(1.3.0)
  • Python数据解析与图片下载工具:从JSON到本地文件的自动化流程
  • 买卖股票的最佳时机--js 算法
  • Nginx、Spring Cloud Gateway 与 Higress 的应用场景及核心区别
  • 从0到1:我的飞算JavaAI实战之旅,效率飙升10倍不是梦!
  • 【Rancher Server + Kubernets】- Nginx-ingress日志持久化至宿主机
  • uniapp项目中node_modules\sass\sass.dart.js的体积过大怎么处理
  • LeetCode[617]合并二叉树
  • 音频调试经验总结
  • 单链表和双向链表
  • Knife4j+Axios+Redis:前后端分离架构下的 API 管理与会话方案
  • 将文件使用base64存入数据库并在微信小程序中实现文件下载
  • 修复手机液晶面板显性横向线性不良定位及相关液晶线路激光修复原理
  • 【安全工具】SQLMap 使用详解:从基础到高级技巧
  • 【深度学习机器学习】Epoch 在深度学习实战中的合理设置指南
  • cmake find_package
  • Minio安装配置,桶权限设置,nginx代理 https minio
  • JAVA学习-练习试用Java实现“人脸识别:使用OpenCV+Java实现人脸检测与识别”
  • 【论文阅读】DeepEyes: Incentivizing “Thinking with Images” via Reinforcement Learning
  • STM32之光敏电阻传感器模块
  • uniapp 滚动tab
  • WPF控件大全:核心属性详解
  • Android-EDLA 解决 GtsMediaRouterTestCases 存在 fail
  • 移动公司Linux运维工程师招聘笔试题