
產(chǎn)品如同蓄水池,用戶好比池中之水。池子中每時(shí)每刻都有新用戶源源不斷地加入,也有一部分用戶選擇離開。如果用戶流失超過新用戶的補(bǔ)給,且速度越來越快、規(guī)模越來越大時(shí),產(chǎn)品如若不警惕,蓄水池遲早會干涸。
不合理的周期造成預(yù)測準(zhǔn)確率低且不平衡,我們需要不斷嘗試周期劃分,在保證整體準(zhǔn)確率的情況下尋求流失與留存準(zhǔn)確率最佳的平衡點(diǎn),才能更為準(zhǔn)確地同時(shí)預(yù)測流失及留存情況。

流失比較經(jīng)典的定義是“一段時(shí)間內(nèi)未進(jìn)行關(guān)鍵行為的用戶”,關(guān)鍵點(diǎn)在于如何界定時(shí)間周期(流失周期)和關(guān)鍵行為(流失行為)。
用戶回訪率 = 回訪用戶數(shù) ÷ 流失用戶數(shù) × 100%
通過流失預(yù)警模型,我們可以獲得產(chǎn)品一系列功能模塊或指標(biāo)對流失留存的影響因子,并計(jì)算出每個(gè)用戶的流失概率。通過影響因子,我們可以對流失原因有所了解,在此基礎(chǔ)上進(jìn)行深入研究和確認(rèn),結(jié)合用戶反饋的頻率、專家意見等確定改版的優(yōu)先級。
區(qū)分出可能流失的用戶是為了提高挽留策略的針對性,提高效率與減少成本,實(shí)現(xiàn)精細(xì)化運(yùn)營——這也是流失模型的核心價(jià)值所在。

從用戶使用的輕重程度出發(fā)(如上圖),在通過模型計(jì)算出用戶未來的流失概率后,將使用App的頻率和時(shí)長作為用戶輕重度的劃分標(biāo)準(zhǔn),結(jié)合用戶流失留存預(yù)期,將用戶劃分為高價(jià)值、重點(diǎn)發(fā)展、重點(diǎn)轉(zhuǎn)化、有待挽留等幾種類型,分析每個(gè)類型用戶不同的行為特點(diǎn)和使用痛點(diǎn),采取針對性的運(yùn)營策略。
當(dāng)然,流失模型也可結(jié)合付費(fèi)維度進(jìn)行研究。先篩選出極有可能將會流失的用戶,再根據(jù)購買頻次和付費(fèi)金額來進(jìn)行細(xì)分:從未付費(fèi)的用戶可通過優(yōu)惠券、促銷活動(dòng)或超低價(jià)商品吸引回訪、促成首單購買;少量付費(fèi)且客單價(jià)低的用戶可以精準(zhǔn)推送符合個(gè)性化偏好的商品,或者推薦符合該用戶消費(fèi)層次的超值商品;多次付費(fèi)的老用戶,可以增加會員專屬優(yōu)惠,通過回饋激勵(lì)增強(qiáng)用戶粘性,延長使用周期。
以上只是流失模型的兩個(gè)層面的應(yīng)用,在不同項(xiàng)目中還可以結(jié)合多種方式對用戶進(jìn)行精細(xì)化運(yùn)營。模型準(zhǔn)確性高的話,可以用更少的成本、對用戶更少的干擾來留住更有價(jià)值的用戶。
下面舉例:電信公司希望針對客戶的信息預(yù)測其流失可能性
從機(jī)器學(xué)習(xí)的分類來講, 這是一個(gè)監(jiān)督問題中的分類問題。 具體來說, 是一個(gè)二分類問題。
數(shù)據(jù)預(yù)處理
讀取數(shù)據(jù):
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="個(gè)人客戶的ID"
churn="是否流失:1=流失";
Age="年齡"
incomeCode="用戶居住區(qū)域平均收入的代碼"
duration="在網(wǎng)時(shí)長"
peakMinAv="統(tǒng)計(jì)期間內(nèi)最高單月通話時(shí)長"
peakMinDiff="統(tǒng)計(jì)期間結(jié)束月份與開始月份相比通話時(shí)長增加數(shù)量"
posTrend="該用戶通話時(shí)長是否呈現(xiàn)出上升態(tài)勢:是=1"
negTrend="該用戶通話時(shí)長是否呈現(xiàn)出下降態(tài)勢:是=1"
nrProm="電話公司營銷的數(shù)量"
prom="最近一個(gè)月是否被營銷過:是=1"
curPlan="統(tǒng)計(jì)時(shí)間開始時(shí)套餐類型:1=最高通過200分鐘;2=300分鐘;3=350分鐘;4=500分鐘"
avPlan="統(tǒng)計(jì)期間內(nèi)平均套餐類型"
planChange="統(tǒng)計(jì)結(jié)束時(shí)和開始時(shí)套餐的變化:正值代表套餐檔次提升,負(fù)值代表下降,0代表不變"
posPlanChange="統(tǒng)計(jì)期間是否提高套餐:1=是"
negPlanChange="統(tǒng)計(jì)期間是否降低套餐:1=是"
call_10086="撥打10086的次數(shù)"
查看前5行
ds.head()

整個(gè)數(shù)據(jù)集有3463條數(shù)據(jù), 20個(gè)維度,第二項(xiàng)是分類是否流失。
查看數(shù)據(jù)類型:
ds.info()

全為浮點(diǎn)型數(shù)據(jù),不需要數(shù)值轉(zhuǎn)換
首先查看因變量中各類別的比例差異,通過餅圖:
import matplotlib.pyplot as plt
# 數(shù)據(jù)集中是否違約的客戶比例
# 為確保繪制的餅圖為圓形,需執(zhí)行如下代碼
plt.axes(aspect = 'equal')
# 中文亂碼和坐標(biāo)軸負(fù)號的處理
plt.rcParams['font.sans-serif'] = ['Microsoft YaHei']
plt.rcParams['axes.unicode_minus'] = False
#重命名因變量
ds.rename(columns={'churn':'y'},inplace=True)
# 統(tǒng)計(jì)客戶是否違約的頻數(shù)
counts = ds.y.value_counts()
# 繪制餅圖
plt.pie(x = counts, # 繪圖數(shù)據(jù)
labels=pd.Series(counts.index).map({0:'不流失',1:'流失'}), # 添加文字標(biāo)簽
autopct='%.1f%%' # 設(shè)置百分比的格式,這里保留一位小數(shù)
)
# 顯示圖形
plt.show()

總體來說,兩個(gè)類別的比例不算失衡。
拆分?jǐn)?shù)據(jù)
# 將數(shù)據(jù)集拆分為訓(xùn)練集和測試集
# 導(dǎo)入第三方包
from sklearn import model_selection
from sklearn import ensemble
from sklearn import metrics
# 排除數(shù)據(jù)集中的ID變量和因變量,剩余的數(shù)據(jù)用作自變量X
X = ds.drop(['y'], axis = 1)
y = ds.y
# 數(shù)據(jù)拆分
X_train,X_test,y_train,y_test = model_selection.train_test_split(X,y,test_size = 0.3, random_state = 1234)
法一:利用ROC(只涉及l(fā)ogistic和隨機(jī)森林)
Logistic模型
建模
from sklearn import linear_model
#利用訓(xùn)練集建模
sklearn_logistic=linear_model.LogisticRegression()
sklearn_logistic.fit(X_train,y_train)
#返回模型的各個(gè)參數(shù)
print(sklearn_logistic.intercept_,sklearn_logistic.coef_)

預(yù)測構(gòu)建混淆矩陣
# 模型預(yù)測
sklearn_predict = sklearn_logistic.predict(X_test)
# 預(yù)測結(jié)果統(tǒng)計(jì)
pd.Series(sklearn_predict).head()

pd.Series(sklearn_predict).value_counts()

判斷為不流失的為1039個(gè)
# 導(dǎo)入第三方模塊
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('模型準(zhǔn)確率為%.2f%%:' %(Accuracy*100))
print('正例覆蓋率為%.2f%%' %(Sensitivity*100))
print('負(fù)例覆蓋率為%.2f%%' %(Specificity*100))

整體的預(yù)測準(zhǔn)確率一般
# y得分為模型預(yù)測正例的概率
y_score = sklearn_logistic.predict_proba(X_test)[:,1]
# 計(jì)算不同閾值下,fpr和tpr的組合值,其中fpr表示1-Specificity,tpr表示Sensitivity
fpr,tpr,threshold = metrics.roc_curve(y_test, y_score)
# 計(jì)算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軸標(biāo)簽
plt.xlabel('1-Specificity')
plt.ylabel('Sensitivity')
# 顯示圖形
plt.show()

低于0.8,認(rèn)定回歸模型是不合理的
隨機(jī)森林模型
from sklearn import ensemble
# 構(gòu)建隨機(jī)森林
RF_class = ensemble.RandomForestClassifier(n_estimators=200, random_state=1234)
# 隨機(jī)森林的擬合
RF_class.fit(X_train, y_train)
# 模型在測試集上的預(yù)測
RFclass_pred = RF_class.predict(X_test)
# 模型的準(zhǔn)確率
print('模型在測試集的預(yù)測準(zhǔn)確率:\n',metrics.accuracy_score(y_test, RFclass_pred))

# 計(jì)算繪圖數(shù)據(jù)
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()

遠(yuǎn)遠(yuǎn)高于0.8,認(rèn)為模型合理
再挑選出重要因素
# 變量的重要性程度值
importance = RF_class.feature_importances_
# 構(gòu)建含序列用于繪圖
Impt_Series = pd.Series(importance, index = X_train.columns)
# 對序列排序繪圖
Impt_Series.sort_values(ascending = True).plot('barh')
plt.show()

取出重要性比較高的變量再利用交叉驗(yàn)證選擇參數(shù)建模
# 取出重要性比較高的自變量建模
predictors = list(Impt_Series[Impt_Series>0.015].index)
predictors

重新建模
# 隨機(jī)森林的擬合
RF_class.fit(X_train[predictors], y_train)
# 模型在測試集上的預(yù)測
RFclass_pred = RF_class.predict(X_test[predictors])
# 模型的準(zhǔn)確率
print('模型在測試集的預(yù)測準(zhǔn)確率:\n',metrics.accuracy_score(y_test, RFclass_pred))

# 計(jì)算繪圖數(shù)據(jù)
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()

遠(yuǎn)遠(yuǎn)高于0.8且準(zhǔn)確率上升,認(rèn)為模型合理
法二:利用誤差均值(多個(gè)模型循環(huán)比較)
# 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判別效果最好
但可以利用集成學(xué)習(xí),例如隨機(jī)森林
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())
則隨機(jī)森林的模型更加合理