用戶流失預測模型-電信行業項目實戰

本文以電信行業數據為基礎,對其進行用戶流失預警的建模,整理如下,歡迎拍磚~

一、流失知識點整理

1. 流失定義

不同產品存在不同的使用周期,因此在定義流失用戶上,需要去進行用戶調研,比如可以對時隔1周、1個月、3個月、半年未下單用戶進行用戶調研,去了解用戶不再產生瀏覽和購買的行為原因,定義流失。

DAU/MAU

這里的DAU是一個月內日活均值,MAU是月活躍用戶數去重。
DAU/MAU比值高,說明用戶訪問產品頻率高且穩定,用戶粘性高留存率高;
相反DAU/MAU比值低,說明用戶訪問產品頻率低不穩定,用戶粘性差留存率低;

以社交產品為例,DAU/MAU定義用戶流失
一般來說0.03<DAU/MAU<1,DAU/MAU=0.03說明活躍用戶只來一次,產品粘性太低;=1說明用戶每天都來。微信的DAU/MAU處于0.8左右,一般產品0.3就比較好了。

2. 研究流失的目的

首要目的還是避免用戶繼續流失,其次才是挽回流失。
這里可以看用戶的生命周期判斷流失原因:
(1)獲取期:新用戶通過推廣、宣傳來到產品中,嘗鮮型;
(2)提升期:用戶有購買行為
(3)成熟期:用戶存在復購和交叉購買行為
(4)衰退期:購買行為和頻次開始衰退,是最需要預警的時期
(5)離開期:達到流失標準
根據以上五個生命周期為用戶打上標簽,判斷用戶是在哪個時期流失,相應的流失原因不同,采取不同措施對產品進行改進。

3. 流失指標
  • 可以通過流失率趨勢圖定位流失率的大小


    image.png

    如圖可知,當流失率達到40%左右開始流失變緩且達到28天處于平穩趨勢,因此30天沒活躍就算成流失用戶。

  • 細分流失用戶的畫像
    包括與活躍用戶的行為差異、屬性差異、渠道差異等,流失之前的訪問頻次、訪問頁面一些列行為。
4. 確定首要挽回用戶

對于老板來說,成本有限,挽回流失用戶當然是挑最值得挽回那一波。因此這里用到了RMF模型,總的來說獲取期用戶的優先級一定是低于成熟期的。這里有我寫過的RFM文章:RFM模型分析實戰

5. 流失用戶召回

使用push推送、短信、微信服務號等方式進行召回
這里也同樣應用到了流失模型。
比如:根據購買頻次和金額來細分。
1次也沒購買過的用戶可派發大額度優惠券、大促活動或超低價商品吸引回訪,成為首單新客。
購買1—2次且客單價較低的用戶,可精準推送優惠專場或在這個客單水平的好貨。
購買3次及以上的用戶,可推送用戶偏好的品牌或品類,額外增加會員專屬優惠券等形式。

總而言之,根據用戶流失模型區分不同行為和屬性的用戶,以及他們流失的節點、原因,運營才可以做到有的放矢,強化用戶召回的效果。

說在后面:用戶的召回很難,更好的做法是避免用戶流失。比如通過區分用戶生命5個周期找到用戶在各周期的流失原因進行產品或運營改進;比如通過分析之前已流失的用戶特征屬性,行為建立預警模型,針對現有用戶有流失跡象提前預警提前進行挽回打消用戶流失的念頭,這才是關鍵!

二、電信行業流失預警模型

分析目的

哪些用戶可能會流失?
流失概率高的用戶有哪些共同特征?

數據探索EDA
2.1 導入數據
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
%matplotlib inline
import seaborn as sns
import os
os.chdir('/Users/xy/Desktop/專業知識/電信行業流失預警/')
df = pd.read_csv('WA_Fn-UseC_-Telco-Customer-Churn.csv')
2.2 數據概覽
pd.set_option('display.max_columns', None)
df.head(5)
#各個字段取值個數
for i in range(21):
    print(df.iloc[:,i].value_counts())
df.shape

customerID:用戶ID
gender:性別 male男性 female女性
SeniorCitizen:是否老年人 0否1是
Partner:是否有配偶 YES是NO否
Dependents:是否經濟獨立 YES是NO否
tenure:客戶職位 73個
PhoneService:是否開通電話業務 YES是NO否
MultipleLines:是否開通多線業務 YES是NO否 No phoneservice
InternetService:是否開通互聯網業務 No, DSL數字網絡,fiber optic光纖網絡
OnlineSecurity:是否開通網絡安全服務YES是NO否 No phoneservice
OnlineBackup:是否開通在線備份業務YES是NO否 No phoneservice
DeviceProtection:是否開通了設備保護業務YES是NO否 No phoneservice
TechSupport:是否開通了技術支持服務YES是NO否 No phoneservice
StreamingTV:是否開通網絡電視YES是NO否 No phoneservice
StreamingMovies:是否開通網絡電影
Contract:簽訂合同方式 按月 一年 二年
PaperlessBilling:是否開通電子賬單YES是NO否
PaymentMethod:付款方式(bank transfer,credit card,electronic check,mailed check)
MonthlyCharges:月費用
TotalCharges:總費用
Churn:是否流失 YES是NO否

(7043行, 21列)

其中:
gender:男女比例均衡
SeniorCitizen:非老年人居多
Partner:有無配偶比例均衡
Dependents:經濟獨立2k,非獨立5k
PhoneService:開通電話業務居多
Contract:合同中按月的較多,按1年和2年的占比相似
Churn:數據集中有5174名用戶沒流失,有1869名客戶流失,數據集不均衡

2.3 數據信息
df.info()
df.isnull().sum()
image.png

未發現缺失值

df['TotalCharges'].apply(pd.to_numeric, errors='coerce')#object轉為float
df['TotalCharges'].isnull().sum()#再次查看缺失值
df.dropna(inplace=True)#刪除缺失行

將TotalCharges的object轉為float格式,網上給出的方法大多數都是dt_df = dt_df.convert_objects(convert_numeric=True),但是因為我現在的版本號低于1.0也不想升級,因此終于找到上面的方法改變類型啦!()
發現有11個缺失值,數量不多刪除

df['Churn'].replace('Yes',1,inplace=True)
df['Churn'].replace('No',0,inplace=True)

將df['Churn']中值變為數值化,yes變為1,no變為1

2.4 數據可視化
2.41 流失用戶占比
plt.rcParams['font.sans-serif']=['SimHei']  #正常顯示中文
plt.rcParams['axes.unicode_minus'] = False  #正常顯示負號

labels = ['未流失用戶','流失用戶']
Churn = df['Churn'].value_counts()
plt.pie(Churn,labels=labels,autopct='%.1f%%')
image.png

流失用戶占比26.6%,未流失占比73.4%

2.42 性別、老年人、配偶、經濟是否獨立對流失用戶的影響
plt.figure(1),plt.title('區分性別對流失的影響')
sns.countplot(x='gender',hue='Churn',data=df,palette='BuPu_r')
plt.figure(2),plt.title('區分老年人對流失的影響')
sns.countplot(x='SeniorCitizen',hue='Churn',data=df,palette='BuPu_r')
plt.figure(3),plt.title('區分配偶對流失的影響')
sns.countplot(x='Partner',hue='Churn',data=df,palette='BuPu_r')
plt.figure(4),plt.title('區分經濟獨立對流失的影響')
sns.countplot(x='Dependents',hue='Churn',data=df,palette='BuPu_r')
image.png

image.png

image.png

image.png

可以得出結論:男性與女性之間的流失沒有差異;老年用戶流失占比比非老年用戶高;沒有配偶的流失占比高于有配偶的流失占比;經濟未獨立的流失率遠高于經濟獨立;

2.43 特征之間的關系

想知道特征之間的關系,需要將這些特征以數值形式展現。這里就用到LabelEncoder或pd.factorize,LabelEncoder能將文本或非連續性數字進行編號,缺點是該編碼方法有順序關系。pd.factorize與LabelEncoder的區別是:pd.factorize支持None默認不排序。pd.factorize()返回兩個值前面值為編碼后的值,后面為原來值。

  • 使用LabelEncoder進行編碼化:
from sklearn.preprocessing import LabelEncoder
labelencoder = LabelEncoder()
lst = []
columns=['gender', 'SeniorCitizen', 'Partner', 'Dependents',
       'tenure', 'PhoneService', 'MultipleLines', 'InternetService',
       'OnlineSecurity', 'OnlineBackup', 'DeviceProtection', 'TechSupport',
       'StreamingTV', 'StreamingMovies', 'Contract', 'PaperlessBilling',
       'PaymentMethod', 'MonthlyCharges', 'TotalCharges']
for i in range(1,20):
    corrdf = labelencoder.fit_transform(df.iloc[:,i])
    lst.append(corrdf)
df2 = pd.DataFrame(map(list,zip(*lst)),columns=columns)
df2.corr()

用corr相關矩陣難以看出這些列之間的關系,可以用熱力圖。

plt.figure(figsize=(16,8))
sns.heatmap(df2.corr(),cmap='YlGnBu',annot=True)

image.png

通常情況下通過以下取值范圍判斷變量的相關強度:
相關系數
0.8-1.0 極強相關
0.6-0.8 強相關
0.4-0.6 中等程度相關
0.2-0.4 弱相關
0.0-0.2 極弱相關或無相關

從熱力圖矩陣中能夠明顯看出配偶與經濟獨立有一定關系,職業與繳納總費用以及合同期限有強相關關系

  • 使用pd.factorize進行編碼化:
charge=df.iloc[:,1:20]#取要編碼的列
for i in range(0,19):
    charge.iloc[:,i] = pd.factorize(charge.iloc[:,i])[0]
plt.figure(figsize=(16,8))
sns.heatmap(charge.corr(),cmap='YlGnBu',annot=True)
image.png

可以看出電話業務與多線業務之間存在強相關性,互聯網服務、網絡安全服務、在線備份業務、設備保護業務、技術支持服務、網絡電視和網絡電影之間存在較強的相關性,并且都呈正相關關系。

我稍微研究了下兩者編碼后的區別,都是按照從上到下出現順序對離散非數值變量進行0,1,...的編碼;對于離散數值變量如職業,labelencoder按照原數據大小從0向后依次編碼,而pd.factorize仍舊按照出現順序不分大小進行編碼。這樣也就解釋了為什么在pd.factorize中有明顯關系的幾個列在labelencoder無明顯關系了,因為被labelencoder中的有大小順序掩蓋了。(僅個人見解)

2.44 特征與churn之間的關系

首先介紹連續數據離散化相關知識點:
一般在回歸、分類、聚類算法中,特征之間計算歐式距離來計算相似度十分重要。當特征中既包括離散變量也包括連續變量,或存在數量級不一致的情況下,就要進行歸一化。
(1)連續變量處理方法

  • 最大最小值歸一化[0,1] x = (x-x.min)/(x.max-x.min)

  • 標準化z_score x= (x-x.mean)/σ
    (2)離散變量處理方法-onehot編碼,將離散取值擴展到歐式空間上。

  • pd.get_dummies:適合于含有字符串類型的數據

  • OneHotEncoder():適用于含有數值的數據

  • Churn與各特征之間相關性

df_churn = pd.get_dummies(df.iloc[:,1:21])
plt.figure(figsize=(16,8)),plt.title('Churn與各特征之間相關性')
df_churn.corr()['Chaurn'].sort_values(ascending=False).plot(kind='bar')
image.png

可見變量gender 和 PhoneService處于中間,相關性非常小,可以舍棄。

  • 網絡安全服務、在線備份業務、設備保護業務、技術支持服務、網絡電視和網絡電影對流失影響
columns=['OnlineSecurity', 'OnlineBackup', 'DeviceProtection', 'TechSupport',
       'StreamingTV', 'StreamingMovies']
fig,axes = plt.subplots(nrows=2,ncols=3,figsize=(16,10))
for i,item in enumerate(columns):#enumerate變量數據對象如列表,返回每個數據及其下標
    plt.subplot(2,3,(i+1))
    ax = sns.countplot(x=item,hue='Churn',data=df,order=['Yes','No','No internet service'],palette='YlGnBu_r')
    #頻數柱形圖,x為每個特征,hue為區分是否流失,y為頻數,order可以規定x內顯示順序
    plt.xlabel(str(item))
    plt.title('Churn與'+str(item)+'的關系')
    i = i+1
plt.show()

image.png

上述可見,網絡安全服務、在線備份業務、設備保護業務、技術支持服務、網絡電視和網絡電影這六個變量中沒有開通網絡服務的用戶流失率相同的且較低,可能因為這六個變量只有在開通網絡服務才會影響用戶決策。因此這六個變量不會對未使用網絡服務用戶的流失產生推論效應。
在這六個變量中前四個變量“網絡安全服務、在線備份業務、設備保護業務、技術支持服務”未開通該服務的用戶流失占比遠高于開通服務的流失占比,而“網絡電視和網絡電影”流失幾乎不受是否開通該業務影響。猜測是因為前四個變量開通能提高用戶體驗質量減少流失,而“網絡電視和網絡電影”不夠成熟導致用戶體驗變差反而造成流失。

2.45 合同與流失的關系
plt.title('合同類型與客戶流失的關系')
sns.barplot(x='Contract',y='Churn',data=df,order=['Month-to-month','One year','Two year'])
plt.ylabel('Churn:流失占比')
image.png

簽訂合同方式對流失率的影響是:按月>1年>2年

2.46 付款方式與流失的關系
#付款方式與流失的關系
plt.title('合同類型與客戶流失的關系')
sns.barplot(x='PaymentMethod',y='Churn',data=df,order=['Electronic check','Mailed check','Bank transfer (automatic)','Credit card (automatic)'])
plt.ylabel('Churn:流失占比')

image.png

從上圖可知,電子賬單的流失率最高,可見電子賬單的流程或設計影響用戶體驗,需要改進。

3. 特征工程
3.1 特征篩選

根據上面的結論得知,變量gender 和 PhoneService對流失影響可忽略,刪除這兩列。customerID是隨機字符對后續建模不影響,也刪除。

df_new= df.iloc[:,2:20]
df_new.drop('PhoneService',inplace=True,axis=1)
df_id = df['customerID']#提取客戶ID

對用戶職位、總費用、月費用進行連續變量歸一化,使方差為1均值為0,這樣預測數值不會被這些過大的特征值主導

from sklearn.preprocessing import StandardScaler
standard = StandardScaler()
standard.fit(df_new[['tenure','MonthlyCharges','TotalCharges']]

df_new[['tenure','MonthlyCharges','TotalCharges']] = standard.transform(df_new[['tenure','MonthlyCharges','TotalCharges']])
sns.boxplot(data=df_new[['tenure','MonthlyCharges','TotalCharges']])
plt.title('職位、月費用和總費用箱型圖可視化')
image.png

如果不進行歸一化,得到的箱線圖是這樣的:


image.png

因此歸一化十分重要。
箱線圖中能夠看到,三個特征沒有異常數據。

3.2 處理對象類型數據
#查看對象類型都有哪些值
def uni(data):
    print(data,'--',df_new[data].unique())
dfobject = df_new.select_dtypes('object')
for i in range(0,len(dfobject.columns)):
    uni(dfobject.columns[i])
image.png

由上面分析可知,六個變量中沒有開通網絡業務對流失影響較小,因此可以將No internet service 和 No 是一樣的效果,可以使用 No 替代 No internet service。

df_new.replace('No internet service','No',inplace=True)
df_new.replace('No phone service','No',inplace=True)
for i in range(0,len(dfobject.columns)):
    uni(dfobject.columns[i])
image.png
3.3 將數值進行編碼

sklearn中的labelencoder進行編碼

def labelend(data):
    df_new[data] = labelencoder.fit_transform(df_new[data])#labelencoder進行編碼
for i in range(0,len(dfobject.columns)):
    labelend(dfobject.columns[i])
for i in range(0,len(dfobject.columns)):
    uni(dfobject.columns[i])
image.png
4. 構造模型
4.1 交叉驗證

由于流失占比不均衡,因此采用分層交叉驗證法

from sklearn.model_selection import StratifiedShuffleSplit
X = df_new#刪除與流失無關的列,將對象類型進行編碼,將數值較大列進行歸一化后得到的特征集
y = df['Churn'].values#標簽集
sss = StratifiedShuffleSplit(n_splits=5,test_size=0.2,random_state=0)
for train_index,test_index in sss.split(X,y):
    print('train',train_index,'test',test_index)#得到訓練集和測試集的index
    X_train,X_test = X.iloc[train_index],X.iloc[test_index]#訓練集特征,測試集特征
    y_train,y_test = y[train_index],y[test_index]#訓練集標簽,測試集標簽
    
image.png
print('原始數據特征',X.shape)
print('訓練數據特征',X_train.shape)
print('測試數據特征',X_test.shape)
image.png
print('原始數據標簽',y.shape)
print('訓練數據標簽',y_train.shape)
print('測試數據標簽',y_test.shape)
image.png
4.2 選擇機器學習算法

由于這里是監督學習分類問題,可選算法有:SVM支持向量機,決策樹,樸素貝葉斯,邏輯回歸(當然其他還有很多,比如隨機森林、KNN、xgboost、catboost等,但是暫時沒有掌握)

#算法
from sklearn.svm import SVC #支持向量機
from sklearn.linear_model import LogisticRegression #邏輯回歸
from sklearn.naive_bayes import GaussianNB#樸素貝葉斯
from sklearn.tree import DecisionTreeClassifier#決策樹分類器

from sklearn.metrics import recall_score,f1_score,precision_score
Classifiers = [['SVM',SVC()],
              ['LogisticRegression',LogisticRegression()],
              ['GaussianNB',GaussianNB()],
              ['DecisionTreeClassifier',DecisionTreeClassifier()]]
Classify_results = []
names = []
prediction = []
for name ,classifier in Classifiers:
    classifier.fit(X_train,y_train)#訓練這4個模型
    y_pred = classifier.predict(X_test)#預測這4個模型
    recall = recall_score(y_test,y_pred)#評估這四個模型的召回率
    precision = precision_score(y_test,y_pred)#評估這四個模型的精確率
    f1 = f1_score(y_test,y_pred)#評估這四個模型的f1分數
    class_eva = pd.DataFrame([recall,precision,f1])#將召回率、精確率和f1分數放在df中,方便接下來對比
    Classify_results.append(class_eva)
    name = pd.Series(name)
    names.append(name)
    y_pred = pd.DataFrame(y_pred)
    prediction.append(y_pred)

將得到的模型評估指標制作成表格

names = pd.DataFrame(names)
result = pd.concat(Classify_results,axis=1)
result.columns = names
result.index=[['recall','precision','f1']]
result

image.png

綜上所述,樸素貝葉斯模型的f1分數最高,因此使用樸素貝葉斯效果最好。

5. 實施方案

由于沒有給預測數據,這里選擇最后10行數據作為預測

df_test = df_new.tail(10)#截取數據最后10行
cutid_test = df['customerID'].tail(10)#取出最后10行的ID
model = GaussianNB()
model.fit(X_train,y_train)#訓練
pred_test_y = model.predict(df_test)#用最后10行的數據做預測
predf = pd.DataFrame({'customerID':cutid_test,'churn':pred_test_y})

image.png

對比了實際數據,返現10行中3行預測錯誤,其余正確,與f1score=63%相符。

6. 結論

通過上述分析

  • 老年用戶、單身、經濟未獨立的用戶流失率較高;
  • 是否開通電話服務對用戶流失沒有直接的影響;
  • 提供的網絡服務如:網絡安全服務、在線備份業務、設備保護業務、技術支持服務能夠降低用戶的流失率
  • 合同簽訂時間越久留存越高
  • 使用電子賬單付款的用戶更容易流失
    針對以上結論,給出的建議是:
    (1)推薦老年用戶與青少年用戶采用數字網絡,且簽訂2年期合同(可以各種輔助優惠等營銷手段來提高2年期合同的簽訂率)
    (2)若能開通相關網絡服務可增加用戶粘性,因此可增加這塊業務的推廣
    (3)考慮改善電子賬單支付的用戶體驗。

參考:
https://mp.weixin.qq.com/s/_20MN6V6aV1T3Ekd7C9neQ 李啟方大佬
https://blog.csdn.net/u013385925/article/details/80142310onehot
https://blog.csdn.net/Li_yi_chao/article/details/80852701onehot
https://blog.csdn.net/ccblogger/article/details/80010974
https://blog.csdn.net/u010986753/article/details/98069124交叉驗證
https://blog.csdn.net/wuzhongqiang/article/details/101560889分層交叉驗證詳解

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。