用Logistic和随机森林建立流失预警模型(含多个模型)


产品如同蓄水池,用户好比池中之水。池子中每时每刻都有新用户源源不断地加入,也有一部分用户选择离开。如果用户流失超过新用户的补给,且速度越来越快、规模越来越大时,产品如若不警惕,蓄水池迟早会干涸。

不合理的周期造成预测准确率低且不平衡,我们需要不断尝试周期划分,在保证整体准确率的情况下寻求流失与留存准确率最佳的平衡点,才能更为准确地同时预测流失及留存情况。


流失比较经典的定义是“一段时间内未进行关键行为的用户”,关键点在于如何界定时间周期(流失周期)和关键行为(流失行为)。

用户回访率 = 回访用户数 ÷ 流失用户数 × 100%

通过流失预警模型,我们可以获得产品一系列功能模块或指标对流失留存的影响因子,并计算出每个用户的流失概率。通过影响因子,我们可以对流失原因有所了解,在此基础上进行深入研究和确认,结合用户反馈的频率、专家意见等确定改版的优先级。

区分出可能流失的用户是为了提高挽留策略的针对性,提高效率与减少成本,实现精细化运营——这也是流失模型的核心价值所在。

从用户使用的轻重程度出发(如上图),在通过模型计算出用户未来的流失概率后,将使用App的频率和时长作为用户轻重度的划分标准,结合用户流失留存预期,将用户划分为高价值、重点发展、重点转化、有待挽留等几种类型,分析每个类型用户不同的行为特点和使用痛点,采取针对性的运营策略。

当然,流失模型也可结合付费维度进行研究。先筛选出极有可能将会流失的用户,再根据购买频次和付费金额来进行细分:从未付费的用户可通过优惠券、促销活动或超低价商品吸引回访、促成首单购买;少量付费且客单价低的用户可以精准推送符合个性化偏好的商品,或者推荐符合该用户消费层次的超值商品;多次付费的老用户,可以增加会员专属优惠,通过回馈激励增强用户粘性,延长使用周期。

以上只是流失模型的两个层面的应用,在不同项目中还可以结合多种方式对用户进行精细化运营。模型准确性高的话,可以用更少的成本、对用户更少的干扰来留住更有价值的用户。

下面举例:电信公司希望针对客户的信息预测其流失可能性
从机器学习的分类来讲, 这是一个监督问题中的分类问题。 具体来说, 是一个二分类问题。

数据预处理

读取数据:

from __future__ import division
import pandas as pd
import numpy as np
ds = pd.read_csv('F:\churn.csv')
col_names = ds.columns.tolist()
print("Column names:")
print(col_names)
ds.shape

变量说明:

subscriberID="个人客户的ID"
churn="是否流失:1=流失";
Age="年龄"
incomeCode="用户居住区域平均收入的代码"
duration="在网时长"
peakMinAv="统计期间内最高单月通话时长"
peakMinDiff="统计期间结束月份与开始月份相比通话时长增加数量"
posTrend="该用户通话时长是否呈现出上升态势:是=1"
negTrend="该用户通话时长是否呈现出下降态势:是=1"
nrProm="电话公司营销的数量"
prom="最近一个月是否被营销过:是=1"
curPlan="统计时间开始时套餐类型:1=最高通过200分钟;2=300分钟;3=350分钟;4=500分钟"
avPlan="统计期间内平均套餐类型"
planChange="统计结束时和开始时套餐的变化:正值代表套餐档次提升,负值代表下降,0代表不变"
posPlanChange="统计期间是否提高套餐:1=是"
negPlanChange="统计期间是否降低套餐:1=是"
call_10086="拨打10086的次数"

查看前5行

ds.head()

整个数据集有3463条数据, 20个维度,第二项是分类是否流失。

查看数据类型:

ds.info()

全为浮点型数据,不需要数值转换

首先查看因变量中各类别的比例差异,通过饼图:

import matplotlib.pyplot as plt
# 数据集中是否违约的客户比例
# 为确保绘制的饼图为圆形,需执行如下代码
plt.axes(aspect = 'equal')
# 中文乱码和坐标轴负号的处理
plt.rcParams['font.sans-serif'] = ['Microsoft YaHei']
plt.rcParams['axes.unicode_minus'] = False
#重命名因变量
ds.rename(columns={'churn':'y'},inplace=True)
# 统计客户是否违约的频数
counts = ds.y.value_counts()
# 绘制饼图
plt.pie(x = counts, # 绘图数据
        labels=pd.Series(counts.index).map({0:'不流失',1:'流失'}), # 添加文字标签
        autopct='%.1f%%' # 设置百分比的格式,这里保留一位小数
       )
# 显示图形
plt.show()

总体来说,两个类别的比例不算失衡。

拆分数据

# 将数据集拆分为训练集和测试集
# 导入第三方包
from sklearn import model_selection
from sklearn import ensemble
from sklearn import metrics

# 排除数据集中的ID变量和因变量,剩余的数据用作自变量X
X = ds.drop(['y'], axis = 1)
y = ds.y
# 数据拆分
X_train,X_test,y_train,y_test = model_selection.train_test_split(X,y,test_size = 0.3, random_state = 1234)

法一:利用ROC(只涉及logistic和随机森林)

Logistic模型

建模

from sklearn import linear_model
#利用训练集建模
sklearn_logistic=linear_model.LogisticRegression()
sklearn_logistic.fit(X_train,y_train)
#返回模型的各个参数
print(sklearn_logistic.intercept_,sklearn_logistic.coef_)

预测构建混淆矩阵

# 模型预测
sklearn_predict = sklearn_logistic.predict(X_test)
# 预测结果统计
pd.Series(sklearn_predict).head()
pd.Series(sklearn_predict).value_counts()

判断为不流失的为1039个

# 导入第三方模块
from sklearn import metrics
# 混淆矩阵
cm = metrics.confusion_matrix(y_test, sklearn_predict, labels = [0,1])
cm

绘制ROC曲线

Accuracy = metrics.scorer.accuracy_score(y_test, sklearn_predict)
Sensitivity = metrics.scorer.recall_score(y_test, sklearn_predict)
Specificity = metrics.scorer.recall_score(y_test, sklearn_predict, pos_label=0)
print('模型准确率为%.2f%%:' %(Accuracy*100))
print('正例覆盖率为%.2f%%' %(Sensitivity*100))
print('负例覆盖率为%.2f%%' %(Specificity*100))

整体的预测准确率一般

# y得分为模型预测正例的概率
y_score = sklearn_logistic.predict_proba(X_test)[:,1]
# 计算不同阈值下,fpr和tpr的组合值,其中fpr表示1-Specificity,tpr表示Sensitivity
fpr,tpr,threshold = metrics.roc_curve(y_test, y_score)
# 计算AUC的值
roc_auc = metrics.auc(fpr,tpr)

# 绘制面积图
plt.stackplot(fpr, tpr, color='steelblue', alpha = 0.5, edgecolor = 'black')
# 添加边际线
plt.plot(fpr, tpr, color='black', lw = 1)
# 添加对角线
plt.plot([0,1],[0,1], color = 'red', linestyle = '--')
# 添加文本信息
plt.text(0.5,0.3,'ROC curve (area = %0.2f)' % roc_auc)
# 添加x轴与y轴标签
plt.xlabel('1-Specificity')
plt.ylabel('Sensitivity')
# 显示图形
plt.show()

低于0.8,认定回归模型是不合理的

随机森林模型

from sklearn import ensemble
# 构建随机森林
RF_class = ensemble.RandomForestClassifier(n_estimators=200, random_state=1234)
# 随机森林的拟合
RF_class.fit(X_train, y_train)
# 模型在测试集上的预测
RFclass_pred = RF_class.predict(X_test)
# 模型的准确率
print('模型在测试集的预测准确率:\n',metrics.accuracy_score(y_test, RFclass_pred))
# 计算绘图数据
y_score = RF_class.predict_proba(X_test)[:,1]
fpr,tpr,threshold = metrics.roc_curve(y_test, y_score)
roc_auc = metrics.auc(fpr,tpr)
# 绘图
plt.stackplot(fpr, tpr, color='steelblue', alpha = 0.5, edgecolor = 'black')
plt.plot(fpr, tpr, color='black', lw = 1)
plt.plot([0,1],[0,1], color = 'red', linestyle = '--')
plt.text(0.5,0.3,'ROC curve (area = %0.2f)' % roc_auc)
plt.xlabel('1-Specificity')
plt.ylabel('Sensitivity')
plt.show()

远远高于0.8,认为模型合理

再挑选出重要因素

# 变量的重要性程度值
importance = RF_class.feature_importances_
# 构建含序列用于绘图
Impt_Series = pd.Series(importance, index = X_train.columns)
# 对序列排序绘图
Impt_Series.sort_values(ascending = True).plot('barh')
plt.show()

取出重要性比较高的变量再利用交叉验证选择参数建模

# 取出重要性比较高的自变量建模
predictors = list(Impt_Series[Impt_Series>0.015].index)
predictors

重新建模

# 随机森林的拟合
RF_class.fit(X_train[predictors], y_train)
# 模型在测试集上的预测
RFclass_pred = RF_class.predict(X_test[predictors])
# 模型的准确率
print('模型在测试集的预测准确率:\n',metrics.accuracy_score(y_test, RFclass_pred))
# 计算绘图数据
y_score = RF_class.predict_proba(X_test[predictors])[:,1]
fpr,tpr,threshold = metrics.roc_curve(y_test, y_score)
roc_auc = metrics.auc(fpr,tpr)
# 绘图
plt.stackplot(fpr, tpr, color='steelblue', alpha = 0.5, edgecolor = 'black')
plt.plot(fpr, tpr, color='black', lw = 1)
plt.plot([0,1],[0,1], color = 'red', linestyle = '--')
plt.text(0.5,0.3,'ROC curve (area = %0.2f)' % roc_auc)
plt.xlabel('1-Specificity')
plt.ylabel('Sensitivity')
plt.show()

远远高于0.8且准确率上升,认为模型合理

法二:利用误差均值(多个模型循环比较)

# prepare models
from sklearn.model_selection import KFold
from sklearn.model_selection import cross_val_score
from sklearn import linear_model
from sklearn.discriminant_analysis import LinearDiscriminantAnalysis
from sklearn.neighbors import KNeighborsClassifier
from sklearn.tree import DecisionTreeClassifier
from sklearn import naive_bayes
from sklearn import svm
models = []
models.append(('LR', linear_model.LogisticRegression()))
models.append(('LDA', LinearDiscriminantAnalysis()))
models.append(('KNN', KNeighborsClassifier()))
models.append(('CART', DecisionTreeClassifier()))
models.append(('NB', naive_bayes.GaussianNB()))
models.append(('SVM', svm.SVC()))
# evaluate each model in turn
results = []
names = []
scoring = 'accuracy'
for name, model in models:
    kfold = KFold(n_splits=10, random_state=7)
    cv_results = cross_val_score(model, X_test, y_test, cv=kfold, scoring=scoring)
    results.append(cv_results)
    names.append(name)
    msg = "%s: %f (%f)" % (name, cv_results.mean(), cv_results.std())
    print(msg)
# boxplot algorithm comparison
fig = plt.figure()
fig.suptitle('Algorithm Comparison')
ax = fig.add_subplot(111)
plt.boxplot(results)
ax.set_xticklabels(names)
plt.show()


可以看出,LDA判别效果最好
但可以利用集成学习,例如随机森林

from sklearn.ensemble import RandomForestClassifier
num_trees = 100
max_features = 3
kfold = KFold(n_splits=10, random_state=7)
model = RandomForestClassifier(n_estimators=num_trees, max_features=max_features)
results = cross_val_score(model, X_test, y_test, cv=kfold)
print(results.mean())

则随机森林的模型更加合理

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,928评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,192评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,468评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,186评论 1 286
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,295评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,374评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,403评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,186评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,610评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,906评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,075评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,755评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,393评论 3 320
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,079评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,313评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,934评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,963评论 2 351

推荐阅读更多精彩内容