電信運(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() #存在空字符串
#使用強(qiáng)制轉(zhuǎn)化為數(shù)字贵涵,不可轉(zhuǎn)換的變?yōu)镹aN
df['TotalCharges'] = df['TotalCharges'].convert_objects(convert_numeric=True)
#空值的在網(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ù)分析
#y標(biāo)簽分布
df['Churn'].value_counts().plot(kind='bar')
print('流失率:',df['Churn'].value_counts()[1]/len(df['Churn'])) #整體流失率達(dá)到26.5%端盆,正負(fù)樣本并不均衡
#性別
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
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('*********************************************************')
'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)越高流失越少
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ū)間流失量高
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í)流失較多
五走搁、數(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())
#訓(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é)果
根據(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ù)流失池摧。