運(yùn)營商客戶流失分析與預(yù)測

電信運(yùn)營商客戶數(shù)據(jù)集

背景描述

----關(guān)于用戶留存有這樣一個(gè)觀點(diǎn),如果將用戶流失率降低5%,公司利潤將提升25%-85%。如今高居不下的獲客成本讓電信運(yùn)營商遭遇“天花板”,甚至陷入獲客難的窘境。隨著市場飽和度上升,電信運(yùn)營商亟待解決增加用戶黏性,延長用戶生命周期的問題。因此,電信用戶流失分析與預(yù)測至關(guān)重要。

數(shù)據(jù)說明

----每一行代表一個(gè)客戶,每一列包含列元數(shù)據(jù)中描述的客戶屬性。原始數(shù)據(jù)包含7043行(客戶)和21列(特性)。

數(shù)據(jù)來源

https://www.kaggle.com/blastchar/telco-customer-churn

問題描述

----預(yù)測客戶是否流失?

本文索引

-->1.觀察理解數(shù)據(jù),提出問題;
-->2.數(shù)據(jù)清洗;
-->3.探索性數(shù)據(jù)分析;
-->4.數(shù)據(jù)預(yù)處理;
-->5.挖掘建模;
-->6.模型評估 ;
-->7.結(jié)論和建議

一、觀察理解數(shù)據(jù),提出問題

1.已知,數(shù)據(jù)每一行代表一個(gè)客戶,每一列包含列元數(shù)據(jù)中描述的客戶屬性。原始數(shù)據(jù)包含7043行(客戶)和21列(特性)

2.數(shù)據(jù)包含1個(gè)是否流失的y標(biāo)簽列"Churn",20個(gè)特征列x;其中"SeniorCitizen"、"tenure"、"MonthlyCharges"3個(gè)字段為數(shù)值特征,其他均為文本特征。

3.目標(biāo)為研究20個(gè)特征列與是否流失標(biāo)簽列y之間的關(guān)系模型,預(yù)測現(xiàn)有客戶的可能流失情況,針對預(yù)測結(jié)果制定相應(yīng)挽留措施,降低流失率。

二、數(shù)據(jù)清洗

----數(shù)據(jù)清洗的“完全合一”規(guī)則:

1.完整性:單條數(shù)據(jù)是否存在空值,統(tǒng)計(jì)的字段是否完善。

2.全面性:觀察某一列的全部數(shù)值,通過常識來判斷該列是否有問題,比如:數(shù)據(jù)定義、單位標(biāo)識、數(shù)據(jù)本身。

3.合法性:數(shù)據(jù)的類型、內(nèi)容、大小的合法性。比如數(shù)據(jù)中是否存在非ASCII字符,性別存在了未知,年齡超過了150等。

4.唯一性:數(shù)據(jù)是否存在重復(fù)記錄,因?yàn)閿?shù)據(jù)通常來自不同渠道的匯總,重復(fù)的情況是常見的。行數(shù)據(jù)、列數(shù)據(jù)都需要是唯一的。

#導(dǎo)包及數(shù)據(jù)集
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline

df = pd.read_csv('C:/Users/82122/Desktop/data/WA_Fn-UseC_-Telco-Customer-Churn.csv',encoding='utf8')

#空值檢查
df.isnull().sum()  #結(jié)果不存在空值
pd.set_option('display.max_columns',None)  #設(shè)置pandas顯示所有列
df.head(5)

df.dtypes  #'TotalCharges'總消費(fèi)額字段顯示為文本字符串類型,一般經(jīng)驗(yàn)看要轉(zhuǎn)化為浮點(diǎn)型
#df['TotalCharges'].astype(float)  
#"ValueError: could not convert string to float: "顯示不能轉(zhuǎn)換為float

各字段大概意義

--customerID 用戶ID;--gender 性別;--SeniorCitizen 是否老年人;--Partner 是否有伴侶;--Dependents 是否家屬;

--tenure 入網(wǎng)時(shí)長;--PhoneService 電話服務(wù);--MultipleLines 多業(yè)務(wù);--InternetService 網(wǎng)絡(luò)服務(wù);--OnlineSecurity 在線安全;

--OnlineBackup 在線備份;--DeviceProtection 設(shè)備保護(hù);--TechSupport 技術(shù)支持;

--StreamingTV 電視業(yè)務(wù);--StreamingMovies 影視服務(wù);

--Contract 合同;--PaperlessBilling 無紙賬單;--PaymentMethod 付款方法;

--MonthlyCharges 每月收費(fèi);--TotalCharges 總花費(fèi);--Churn 是否流失;

df['TotalCharges'].value_counts()   #存在空字符串
圖片.png
#使用強(qiáng)制轉(zhuǎn)化為數(shù)字,不可轉(zhuǎn)換的變?yōu)镹aN
df['TotalCharges'] = df['TotalCharges'].convert_objects(convert_numeric=True)
圖片.png

圖片.png
#空值的在網(wǎng)時(shí)長‘tenure’均是0,預(yù)估這部分用戶是在當(dāng)月入網(wǎng)的用戶,根據(jù)一般經(jīng)驗(yàn)看,此部分用戶肯定是需要繳費(fèi)的。
#此部分用戶包含重要特征,所以不選擇刪除此部分?jǐn)?shù)據(jù),把這部分用戶入網(wǎng)時(shí)長改為1,消費(fèi)總額按當(dāng)月的繳費(fèi)
df['tenure'].replace(0,1,inplace=True)
df['TotalCharges']=df['TotalCharges'].fillna(df['MonthlyCharges'])

三、探索性數(shù)據(jù)分析

圖片.png
#y標(biāo)簽分布
df['Churn'].value_counts().plot(kind='bar') 
print('流失率:',df['Churn'].value_counts()[1]/len(df['Churn']))  #整體流失率達(dá)到26.5%,正負(fù)樣本并不均衡
圖片.png
#性別
plt.rcParams['font.sans-serif']=['SimHei'] #用來正常顯示中文標(biāo)簽
df['gender'].value_counts().plot(kind='bar')
plt.title('gender 柱狀圖')
x_len=range(len(df['gender'].unique()))
y_data=list(df['gender'].value_counts())
for x,y in zip(x_len,y_data):
    plt.text(x,y+1,'%.0f'%y, ha='center', va= 'bottom',fontsize=10)
print('性別比例:',df['gender'].value_counts()[0]/df['gender'].value_counts()[1])  #性別比例接近1:1
print(df[df['Churn']=="No"]['gender'].value_counts())
print(df[df['Churn']=="Yes"]['gender'].value_counts())

性別比例: 1.0192087155963303
Male 2625
Female 2549
Name: gender, dtype: int64
Female 939
Male 930
Name: gender, dtype: int64


圖片.png
def feature_explor(feature):
    #導(dǎo)包
    %matplotlib inline
    import seaborn as sns
    import matplotlib.pyplot as plt
    #流失率計(jì)算
    a1 = df.groupby([feature])['Churn'].value_counts().to_frame()
    a1.rename(columns={"Churn":'計(jì)數(shù)'},inplace=True)
    a1.reset_index(inplace=True)
    a2=df[feature].value_counts().to_frame()
    a2.reset_index(inplace=True)
    a2.columns=[feature,'計(jì)數(shù)1']
    a3=pd.merge(a1,a2,how='left',on=feature)
    a3['流失率']=a3['計(jì)數(shù)']/a3['計(jì)數(shù)1']
    print(a3[a3["Churn"]=='Yes'])
    #特征流失率可視化
#     g1=sns.barplot(x=feature,y='流失率',hue='Churn',data=a3)
#     plt.title('{0} 維度流失率'.format(feature))  #添加標(biāo)題
#     #得到標(biāo)簽數(shù)據(jù)
#     #w=g1.get_width()
#     x_1=len(a2)
#     x_len=range(len(a3))
#     y_data=a3['流失率'].tolist()
#     for x,y in zip(x_len,y_data):
#         plt.text(x/x_1-0.25,y+0.05,'%.2f'%y, ha='center', va= 'bottom',fontsize=10)
#得到特征列表,并去除id列、數(shù)值列特征以及標(biāo)簽'Churn';'SeniorCitizen'不是真正的數(shù)值特征,算是分類特征,保留在列表內(nèi)
c_l=df.columns.tolist()
l1=['customerID','tenure','MonthlyCharges','TotalCharges','Churn']
for i in l1:
    c_l.remove(i)
c_l
#對得到的特征列表,特征流失率可視化
for i in c_l:
    print('*********************************************************')    
    feature_explor(i)
    print('*********************************************************')
圖片.png

圖片.png

圖片.png

'gender':男女流失率基本相同

'SeniorCitizen':老人流失率高于非老人群體

'Partner':沒有伴侶的流失率高于有伴侶的

'Dependents':沒有家屬的高于有家屬的

'PhoneService'、'MultipleLines Churn':對流失率影響差異不明顯

'InternetService'、'OnlineSecurity'、'OnlineBackup'、'DeviceProtection'、'TechSupport'、

'StreamingTV'、'StreamingMovies'、'Contract'、'PaperlessBilling '、'PaymentMethod':對流失率有明細(xì)影響

import seaborn as sns
plt.figure(figsize=(10,4))
d1=df[df['Churn']=='Yes']['tenure'].dropna()
d2=df[df['Churn']=='No']['tenure'].dropna()
sns.kdeplot(d1,color= 'navy', label= 'Churn: Yes',shade='True')
sns.kdeplot(d2,color= 'orange', label= 'Churn: No', shade='True')
plt.xlabel('tenure')
plt.title("KDE for tenure")
#設(shè)置字體大小
plt.rcParams.update({'font.size': 20})
plt.legend(fontsize=10)   
# plt.xlim([-10,10])   #入網(wǎng)3個(gè)月左右流失量達(dá)到峰值,入網(wǎng)時(shí)長越高流失越少
圖片.png
import seaborn as sns
plt.figure(figsize=(10,4))
d1=df[df['Churn']=='Yes']['MonthlyCharges'].dropna()
d2=df[df['Churn']=='No']['MonthlyCharges'].dropna()
sns.kdeplot(d1,color= 'navy', label= 'Churn: Yes',shade='True')
sns.kdeplot(d2,color= 'orange', label= 'Churn: No', shade='True')
plt.xlabel('MonthlyCharges')
plt.title("KDE for MonthlyCharges")
#設(shè)置字體大小
plt.rcParams.update({'font.size': 20})
plt.legend(fontsize=10)   #沒有付費(fèi)80-120區(qū)間流失量高
圖片.png
import seaborn as sns
plt.figure(figsize=(10,4))
d1=df[df['Churn']=='Yes']['TotalCharges'].dropna()
d2=df[df['Churn']=='No']['TotalCharges'].dropna()
sns.kdeplot(d1,color= 'navy', label= 'Churn: Yes',shade='True')
sns.kdeplot(d2,color= 'orange', label= 'Churn: No', shade='True')
plt.xlabel('TotalCharges')
plt.title("KDE for TotalCharges")
#設(shè)置字體大小
plt.rcParams.update({'font.size': 20})
plt.legend(fontsize=10)
# plt.xlim([-2000,400])    #總花費(fèi)300-400時(shí)流失較多
圖片.png

五、數(shù)據(jù)預(yù)處理

del df['customerID']  #刪除ID列
#1、特征縮放
# 數(shù)值化
#使用pd.get_dummies(df),把數(shù)據(jù)OneHotEncoder編碼;把標(biāo)簽y轉(zhuǎn)化為正負(fù)1,0
df1 = pd.get_dummies(df.iloc[:,0:-1])
df2=df
df2['Churn']=df2['Churn'].replace('Yes',1)
df2['Churn']=df2['Churn'].replace('No',0)
df3=pd.concat([df1,df2['Churn']],axis=1)
#特征縮放-數(shù)值特征標(biāo)準(zhǔn)化
from sklearn.preprocessing import StandardScaler
encoder = StandardScaler()
df3[['tenure']] = pd.DataFrame(encoder.fit_transform(df3[['tenure']]))
df3[['MonthlyCharges']] = pd.DataFrame(encoder.fit_transform(df3[['MonthlyCharges']]))
df3[['TotalCharges']] = pd.DataFrame(encoder.fit_transform(df3[['TotalCharges']]))
#特征選擇-3種方法,過濾:selectKbest,包裹:RFE,嵌入:selectfrommodel
from sklearn.feature_selection import SelectKBest,RFE,SelectFromModel
X = df3.iloc[:,0:-1]
y = df3['Churn']
#過濾
skb=SelectKBest(k=35)
skb.fit(X,y)
x_skb=pd.DataFrame(skb.transform(X),columns=X.columns[skb.get_support()])
#包裹
from sklearn.linear_model import LogisticRegression
from sklearn.svm import SVR
# rfe=RFE(LogisticRegression(),35)
rfe=RFE(SVR(kernel='linear'),35)
rfe.fit(X,y)
x_rfe=pd.DataFrame(rfe.transform(X),columns=X.columns[rfe.get_support()])
#嵌入
from sklearn.tree import DecisionTreeRegressor
sfm=SelectFromModel(DecisionTreeRegressor(),threshold=0.0011)
sfm.fit(X,y)
x_sfm=pd.DataFrame(sfm.transform(X),columns=X.columns[sfm.get_support()])
x_sfm.shape
#比較3種方法的結(jié)果
print(skb.get_support())
print(rfe.get_support())
print(sfm.get_support())
圖片.png
#訓(xùn)練集、測試集拆分
from sklearn.model_selection import train_test_split
x_tt,x_validation,y_tt,y_validation=train_test_split(X,y,test_size=0.2,random_state=42)
x_train,x_test,y_train,y_test=train_test_split(x_tt,y_tt,test_size=0.25,random_state=42)
#構(gòu)造模型
from sklearn.neighbors import NearestNeighbors,KNeighborsClassifier  #KNN,K近鄰
from sklearn.naive_bayes import GaussianNB,BernoulliNB  #樸素貝葉斯(高斯貝葉斯,伯努利貝葉斯)
from sklearn.linear_model import LogisticRegression  #邏輯回歸
from sklearn.tree import DecisionTreeClassifier  #決策樹
from sklearn.svm import SVC,SVR  #支持向量機(jī)
from sklearn.ensemble import RandomForestClassifier,AdaBoostClassifier  #隨機(jī)森林(bagging,boost-Adaboost)
from sklearn.metrics import accuracy_score,recall_score,f1_score  #模型評估,準(zhǔn)確率,召回率,f1

models=[]
models.append(('KNN',KNeighborsClassifier(n_neighbors=45)))
models.append(('GaussianNB',GaussianNB()))
models.append(('BernoulliNB',BernoulliNB()))
models.append(('LogisticRegression',LogisticRegression(C=10,penalty='l2',class_weight='balanced',max_iter=1000)))
models.append(('DecisionTreeClassifier',DecisionTreeClassifier(max_depth=4,class_weight='balanced')))
models.append(('SVC',SVC(C=1000,class_weight='balanced')))
models.append(('RandomForestClassifier',RandomForestClassifier(n_estimators=100,max_depth=6,class_weight='balanced')))
models.append(('AdaBoostClassifier',AdaBoostClassifier(LogisticRegression(C=10,penalty='l2',class_weight='balanced',max_iter=1000)
                                                       ,n_estimators=80,learning_rate=0.4)))



for clf_name,clf in models:
    clf.fit(x_train,y_train)
    xy_list=[(x_train,y_train),(x_validation,y_validation),(x_test,y_test)]
    print('****************************************************************')
    for i in range(len(xy_list)):
        x_part=xy_list[i][0]
        y_part=xy_list[i][1]
        y_pred=clf.predict(x_part)
        print(i)
        print(clf_name,'-ACC',accuracy_score(y_part,y_pred))
        print(clf_name,'-REC',recall_score(y_part,y_pred))
        print(clf_name,'-F1',f1_score(y_part,y_pred))

模型結(jié)果

圖片.png

圖片.png

圖片.png

圖片.png

根據(jù)以上分析,得到高流失率用戶的特征:
1、老年用戶,未婚用戶,無親屬用戶更容易流失;
2、在網(wǎng)時(shí)長小于半年,有電話服務(wù),光纖用戶/光纖用戶附加流媒體電視、電影服務(wù),無互聯(lián)網(wǎng)服務(wù);
3、簽訂的合同期較短,采用電子支票支付,是電子賬單,月租費(fèi)約80-120元的客戶容易流失;
其它屬性對用戶流失影響較小,以上特征保持獨(dú)立。

針對上述結(jié)論,從業(yè)務(wù)角度給出相應(yīng)建議:
根據(jù)預(yù)測模型,定期構(gòu)建一個(gè)高流失率的用戶列表,針對不同用戶制定相應(yīng)營銷舉措,挽留用戶流失。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容