運(yùn)營(yíng)商客戶(hù)流失分析與預(yù)測(cè)

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

背景描述

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

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

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

數(shù)據(jù)來(lái)源

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

問(wèn)題描述

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

本文索引

-->1.觀察理解數(shù)據(jù)搁廓,提出問(wèn)題引颈;
-->2.數(shù)據(jù)清洗;
-->3.探索性數(shù)據(jù)分析境蜕;
-->4.數(shù)據(jù)預(yù)處理蝙场;
-->5.挖掘建模;
-->6.模型評(píng)估 粱年;
-->7.結(jié)論和建議

一售滤、觀察理解數(shù)據(jù),提出問(wèn)題

1.已知,數(shù)據(jù)每一行代表一個(gè)客戶(hù)完箩,每一列包含列元數(shù)據(jù)中描述的客戶(hù)屬性赐俗。原始數(shù)據(jù)包含7043行(客戶(hù))和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ù)測(cè)現(xiàn)有客戶(hù)的可能流失情況,針對(duì)預(yù)測(cè)結(jié)果制定相應(yīng)挽留措施呐舔,降低流失率币励。

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

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

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

2.全面性:觀察某一列的全部數(shù)值,通過(guò)常識(shí)來(lái)判斷該列是否有問(wèn)題澎现,比如:數(shù)據(jù)定義仅胞、單位標(biāo)識(shí)、數(shù)據(jù)本身剑辫。

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

4.唯一性:數(shù)據(jù)是否存在重復(fù)記錄编整,因?yàn)閿?shù)據(jù)通常來(lái)自不同渠道的匯總,重復(fù)的情況是常見(jiàn)的乳丰。行數(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)額字段顯示為文本字符串類(lèi)型产园,一般經(jīng)驗(yàn)看要轉(zhuǎn)化為浮點(diǎn)型
#df['TotalCharges'].astype(float)  
#"ValueError: could not convert string to float: "顯示不能轉(zhuǎn)換為float

各字段大概意義

--customerID 用戶(hù)ID汞斧;--gender 性別;--SeniorCitizen 是否老年人什燕;--Partner 是否有伴侶粘勒;--Dependents 是否家屬;

--tenure 入網(wǎng)時(shí)長(zhǎng)屎即;--PhoneService 電話(huà)服務(wù)仲义;--MultipleLines 多業(yè)務(wù);--InternetService 網(wǎng)絡(luò)服務(wù);--OnlineSecurity 在線安全埃撵;

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

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

--Contract 合同谣拣;--PaperlessBilling 無(wú)紙賬單募寨;--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í)長(zhǎng)‘tenure’均是0列肢,預(yù)估這部分用戶(hù)是在當(dāng)月入網(wǎng)的用戶(hù),根據(jù)一般經(jīng)驗(yàn)看宾茂,此部分用戶(hù)肯定是需要繳費(fèi)的瓷马。
#此部分用戶(hù)包含重要特征,所以不選擇刪除此部分?jǐn)?shù)據(jù)跨晴,把這部分用戶(hù)入網(wǎng)時(shí)長(zhǎng)改為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'] #用來(lái)正常顯示中文標(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ù)值特征蒋伦,算是分類(lèi)特征,保留在列表內(nèi)
c_l=df.columns.tolist()
l1=['customerID','tenure','MonthlyCharges','TotalCharges','Churn']
for i in l1:
    c_l.remove(i)
c_l
#對(duì)得到的特征列表访敌,特征流失率可視化
for i in c_l:
    print('*********************************************************')    
    feature_explor(i)
    print('*********************************************************')
圖片.png

圖片.png

圖片.png

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

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

'Partner':沒(méi)有伴侶的流失率高于有伴侶的

'Dependents':沒(méi)有家屬的高于有家屬的

'PhoneService'凉敲、'MultipleLines Churn':對(duì)流失率影響差異不明顯

'InternetService'衣盾、'OnlineSecurity'寺旺、'OnlineBackup'、'DeviceProtection'势决、'TechSupport'阻塑、

'StreamingTV'、'StreamingMovies'果复、'Contract'陈莽、'PaperlessBilling '、'PaymentMethod':對(duì)流失率有明細(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í)長(zhǎng)越高流失越少
圖片.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)   #沒(méi)有付費(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種方法忌栅,過(guò)濾:selectKbest,包裹:RFE曲稼,嵌入:selectfrommodel
from sklearn.feature_selection import SelectKBest,RFE,SelectFromModel
X = df3.iloc[:,0:-1]
y = df3['Churn']
#過(guò)濾
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)練集索绪、測(cè)試集拆分
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  #決策樹(shù)
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  #模型評(píng)估贫悄,準(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ù)以上分析窄坦,得到高流失率用戶(hù)的特征:
1唤反、老年用戶(hù),未婚用戶(hù)嫡丙,無(wú)親屬用戶(hù)更容易流失拴袭;
2、在網(wǎng)時(shí)長(zhǎng)小于半年曙博,有電話(huà)服務(wù)拥刻,光纖用戶(hù)/光纖用戶(hù)附加流媒體電視、電影服務(wù)父泳,無(wú)互聯(lián)網(wǎng)服務(wù)般哼;
3、簽訂的合同期較短惠窄,采用電子支票支付蒸眠,是電子賬單,月租費(fèi)約80-120元的客戶(hù)容易流失杆融;
其它屬性對(duì)用戶(hù)流失影響較小楞卡,以上特征保持獨(dú)立。

針對(duì)上述結(jié)論脾歇,從業(yè)務(wù)角度給出相應(yīng)建議:
根據(jù)預(yù)測(cè)模型蒋腮,定期構(gòu)建一個(gè)高流失率的用戶(hù)列表,針對(duì)不同用戶(hù)制定相應(yīng)營(yíng)銷(xiāo)舉措藕各,挽留用戶(hù)流失池摧。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市激况,隨后出現(xiàn)的幾起案子作彤,更是在濱河造成了極大的恐慌膘魄,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,430評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件竭讳,死亡現(xiàn)場(chǎng)離奇詭異创葡,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)绢慢,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,406評(píng)論 3 398
  • 文/潘曉璐 我一進(jìn)店門(mén)蹈丸,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人呐芥,你說(shuō)我怎么就攤上這事逻杖。” “怎么了思瘟?”我有些...
    開(kāi)封第一講書(shū)人閱讀 167,834評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵荸百,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我滨攻,道長(zhǎng)够话,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,543評(píng)論 1 296
  • 正文 為了忘掉前任光绕,我火速辦了婚禮女嘲,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘诞帐。我一直安慰自己欣尼,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,547評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布停蕉。 她就那樣靜靜地躺著愕鼓,像睡著了一般。 火紅的嫁衣襯著肌膚如雪慧起。 梳的紋絲不亂的頭發(fā)上菇晃,一...
    開(kāi)封第一講書(shū)人閱讀 52,196評(píng)論 1 308
  • 那天,我揣著相機(jī)與錄音蚓挤,去河邊找鬼磺送。 笑死,一個(gè)胖子當(dāng)著我的面吹牛灿意,可吹牛的內(nèi)容都是我干的估灿。 我是一名探鬼主播,決...
    沈念sama閱讀 40,776評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼脾歧,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼甲捏!你這毒婦竟也來(lái)了演熟?” 一聲冷哼從身側(cè)響起鞭执,我...
    開(kāi)封第一講書(shū)人閱讀 39,671評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤司顿,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后兄纺,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體大溜,經(jīng)...
    沈念sama閱讀 46,221評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,303評(píng)論 3 340
  • 正文 我和宋清朗相戀三年估脆,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了钦奋。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,444評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡疙赠,死狀恐怖付材,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情圃阳,我是刑警寧澤厌衔,帶...
    沈念sama閱讀 36,134評(píng)論 5 350
  • 正文 年R本政府宣布,位于F島的核電站捍岳,受9級(jí)特大地震影響富寿,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜锣夹,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,810評(píng)論 3 333
  • 文/蒙蒙 一页徐、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧银萍,春花似錦变勇、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,285評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至滤蝠,卻和暖如春豌熄,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背物咳。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,399評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工锣险, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人览闰。 一個(gè)月前我還...
    沈念sama閱讀 48,837評(píng)論 3 376
  • 正文 我出身青樓芯肤,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親压鉴。 傳聞我的和親對(duì)象是個(gè)殘疾皇子崖咨,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,455評(píng)論 2 359