電信用戶數據:https://www.datafountain.cn/dataSets/35/details#
將裝有該字典的Excel表導入到python中
import pandas as pd dict_name=pd.read_excel('F:\\python\\電信用戶數據\\電信用戶數據的字典.xlsx') dict_name.columns=['name','解釋']
1.導入模塊和數據
# 模塊 import pandas as pd import numpy as np import matplotlib.pyplot as plt import seaborn as sns import scipy.stats as st plt.rc("font",family="SimHei",size="12") #解決中文無法顯示的問題 # 導入數據 dianxin=pd.read_csv('F:\\python\\電信用戶數據\\WA_Fn-UseC_-Telco-Customer-Churn.csv')
2.基本信息查看
#查看數據信息 #前面5條+后面5條 dianxin.head(5).append(dianxin.tail(5)) #info() dianxin.info() #shape dianxin.shape #(7043, 21) #describe(),由於其他變量都是object變量,因此只有三個特征 dianxin.describe().to_csv('F:\\python\\電信用戶數據\\特征描述同統計變量.csv')
3.數據清洗
查看缺失值
#查看缺失值 dianxin.isnull().sum() #上面的結果都是0,再看看其他類型的缺失值如'-' object_col=dianxin.select_dtypes(include=[np.object]).columns for i in object_col: print('{}: 特征有 {} 個不同的值'.format(i,dianxin[i].nunique())) print(dianxin[i].value_counts()) #可見TotalCharges有11個缺失值 dianxin[dianxin['TotalCharges']==' '] #使用nan替換 dianxin['TotalCharges'].replace(' ',np.nan,inplace=True)
dianxin['TotalCharges']=dianxin['TotalCharges'].astype(np.float64)
y值的分布
#y值 dianxin['Churn'].value_counts() sns.countplot(dianxin['Churn'])
類別特征分析
#刪除沒有意義的用戶ID object_col=object_col.drop('customerID') #每個類別特征每個類別可視化 for i in object_col: sns.countplot(dianxin[i]) plt.show() #上面的一張張圖片粘貼太麻煩了,我們把這個放在同一張圖里面 def count_plot(x,**kwargs): sns.countplot(x=x) x=plt.xticks(rotation=90) f=pd.melt(dianxin,value_vars=object_col) g=sns.FacetGrid(f,col='variable',col_wrap=2,sharex=False,sharey=False,size=5) g=g.map(count_plot,'value')
類別特征和y值的條形圖
#類別特征和y值的條形圖,由於y值也是類別型,因此使用列聯表,然后再畫圖 object_col=object_col.drop('Churn') for i in object_col: pd.crosstab(dianxin[i],dianxin['Churn']).plot.bar() plt.show()
類別特征畫餅圖
for i in object_col: dianxin[i].value_counts().plot.pie(figsize=(6,6),autopct='%.2f%%',title=i) plt.show()
數字特征
#數字特征 num_col=list(dianxin.select_dtypes(include=[np.number]).columns) #數字特征的可視化 for i in num_col: dianxin[i].plot.hist(title=i) plt.show()
構建特征
#將類別特征獨熱編碼,經過上面的畫圖我們可以知道SeniorCitizen也是類別特征 #除了費用其他的都是類別特征,看一下年費用和年費用的相關性如何 dianxin['MonthlyCharges'].corr(dianxin['TotalCharges']) # 0.6510648032262024 相關性很高,可以考慮去掉其中一個 #構造數字特征 dianxin.Churn[dianxin['Churn']=='No']=0 dianxin.Churn[dianxin['Churn']=='Yes']=1
dianxin['Churn']=dianxin['Churn'].astype(np.int)
dianxin['Churn'].value_counts() dianxin['MonthlyCharges'].describe() #畫箱型圖 a0 = dianxin.MonthlyCharges[dianxin.Churn == 1].dropna() a1 = dianxin.MonthlyCharges[dianxin.Churn == 0].dropna() plt.boxplot((a0,a1),labels=('流失','沒流失')) dianxin.MonthlyCharges[dianxin.Churn == 1].describe() dianxin.MonthlyCharges[dianxin.Churn == 0].describe() dianxin['MonthlyCharges_bin']=pd.cut(dianxin['MonthlyCharges'],7,labels=False) dianxin['MonthlyCharges_bin'].plot.hist() pd.crosstab(dianxin['MonthlyCharges_bin'],dianxin['Churn']).plot(kind="bar") #費用獨熱編碼 feiyong=pd.get_dummies(dianxin['MonthlyCharges_bin']) #類別特征獨熱編碼 object_col=['gender', 'SeniorCitizen', 'Partner', 'Dependents', 'tenure', 'PhoneService', 'MultipleLines', 'InternetService', 'OnlineSecurity', 'OnlineBackup', 'DeviceProtection', 'TechSupport', 'StreamingTV', 'StreamingMovies', 'Contract', 'PaperlessBilling', 'PaymentMethod'] dainxin1=pd.get_dummies(dianxin,columns=object_col) train_x=dainxin1.join(feiyong) train_x=train_x.drop([ 'customerID', 'MonthlyCharges', 'TotalCharges', 'Churn',],axis=1) train_y=dianxin['Churn']
模型構造
#構建模型 from sklearn.linear_model import LogisticRegression from sklearn.metrics import confusion_matrix, roc_curve,roc_auc_score,classification_report from sklearn.model_selection import train_test_split x_train,x_test,y_train,y_test=train_test_split(train_x,train_y,test_size=0.3,random_state=0) #邏輯回歸 clf = LogisticRegression() clf.fit(x_train,y_train) #用測試集進行檢驗 clf.predict(x_test) #混淆矩陣 confusion_matrix(y_test,clf.predict(x_test)) #array([[1392, 168],[ 257, 296]], dtype=int64) #roc roc_auc_score(y_test,clf.predict_proba(x_test)[:,1]) #0.8373574210599526 #畫圖 fpr,tpr,thresholds = roc_curve(y_test,clf.predict_proba(x_test)[:,1]) plt.plot(fpr,tpr) #分類報告 print(classification_report(y_test,clf.predict(x_test)))
# coding: utf-8 # # 電信客戶流失預測 # ## 1、導入數據 import numpy as np import pandas as pd import os #https://catboost.ai/docs/concepts/python-reference_catboost_predict.html # 導入相關的包 import matplotlib.pyplot as plt import seaborn as sns from pylab import rcParams import matplotlib.cm as cm import sklearn from sklearn import preprocessing from sklearn.preprocessing import LabelEncoder # 編碼轉換 from sklearn.preprocessing import StandardScaler from sklearn.model_selection import StratifiedShuffleSplit from sklearn.ensemble import RandomForestClassifier # 隨機森林 from sklearn.svm import SVC, LinearSVC # 支持向量機 from sklearn.linear_model import LogisticRegression # 邏輯回歸 from sklearn.neighbors import KNeighborsClassifier # KNN算法 from sklearn.naive_bayes import GaussianNB # 朴素貝葉斯 from sklearn.tree import DecisionTreeClassifier # 決策樹分類器 from xgboost import XGBClassifier from catboost import CatBoostClassifier from sklearn.ensemble import AdaBoostClassifier from sklearn.ensemble import GradientBoostingClassifier from sklearn.metrics import classification_report, precision_score, recall_score, f1_score from sklearn.metrics import confusion_matrix from sklearn.model_selection import GridSearchCV from sklearn.metrics import make_scorer from sklearn.ensemble import VotingClassifier from sklearn.decomposition import PCA from sklearn.cluster import KMeans from sklearn.metrics import silhouette_score import warnings warnings.filterwarnings('ignore') get_ipython().magic('matplotlib inline') # 讀取數據文件 telcom=pd.read_csv('F:\\python\\電信用戶數據\\WA_Fn-UseC_-Telco-Customer-Churn.csv') # ## 2、查看數據集信息 telcom.head(10) # 查看數據集大小 telcom.shape # 獲取數據類型列的描述統計信息 telcom.describe() # ## 3、數據清洗 # 查找缺失值 pd.isnull(telcom).sum() telcom["Churn"].value_counts() # 數據集中有5174名用戶沒流失,有1869名客戶流失,數據集不均衡。 telcom.info() # TotalCharges表示總費用,這里為對象類型,需要轉換為float類型 #convert_objects已經棄用 ,注意astype如果遇到空值,轉換成數值型就會報錯 telcom['TotalCharges'].replace(' ',np.nan,inplace=True) telcom['TotalCharges']=telcom['TotalCharges'].astype(np.float64) # convert_numeric=True表示強制轉換數字(包括字符串),不可轉換的值變為NaN telcom["TotalCharges"].dtypes # 再次查找是否存在缺失值 pd.isnull(telcom["TotalCharges"]).sum() # 這里存在11個缺失值,由於數量不多我們可以直接刪除這些行 # 刪除缺失值所在的行 telcom.dropna(inplace=True) telcom.shape # 數據歸一化處理 # 對Churn 列中的值 Yes和 No分別用 1和 0替換,方便后續處理 telcom['Churn'].replace(to_replace = 'Yes', value = 1,inplace = True) telcom['Churn'].replace(to_replace = 'No', value = 0,inplace = True) telcom['Churn'].head() telcom['Churn'].replace(to_replace='Yes', value=1, inplace=True) telcom['Churn'].replace(to_replace='No', value=0, inplace=True) telcom['Churn'].head() # ## 4、數據可視化呈現 # 查看流失客戶占比 """ 畫餅圖參數: labels (每一塊)餅圖外側顯示的說明文字 explode (每一塊)離開中心距離 startangle 起始繪制角度,默認圖是從x軸正方向逆時針畫起,如設定=90則從y軸正方向畫起 shadow 是否陰影 labeldistance label 繪制位置,相對於半徑的比例, 如<1則繪制在餅圖內側 autopct 控制餅圖內百分比設置,可以使用format字符串或者format function '%1.1f'指小數點前后位數(沒有用空格補齊) pctdistance 類似於labeldistance,指定autopct的位置刻度 radius 控制餅圖半徑 """ churnvalue=telcom["Churn"].value_counts() labels=telcom["Churn"].value_counts().index rcParams["figure.figsize"]=6,6 plt.pie(churnvalue,labels=labels,colors=["whitesmoke","yellow"], explode=(0.1,0),autopct='%1.1f%%', shadow=True) plt.title("Proportions of Customer Churn") plt.show() # 性別、老年人、配偶、親屬對流客戶流失率的影響 f, axes = plt.subplots(nrows=2, ncols=2, figsize=(10,10)) plt.subplot(2,2,1) gender=sns.countplot(x="gender",hue="Churn",data=telcom,palette="Pastel2") # palette參數表示設置顏色,這里設置為主題色Pastel2 plt.xlabel("gender") plt.title("Churn by Gender") plt.subplot(2,2,2) seniorcitizen=sns.countplot(x="SeniorCitizen",hue="Churn",data=telcom,palette="Pastel2") plt.xlabel("senior citizen") plt.title("Churn by Senior Citizen") plt.subplot(2,2,3) partner=sns.countplot(x="Partner",hue="Churn",data=telcom,palette="Pastel2") plt.xlabel("partner") plt.title("Churn by Partner") plt.subplot(2,2,4) dependents=sns.countplot(x="Dependents",hue="Churn",data=telcom,palette="Pastel2") plt.xlabel("dependents") plt.title("Churn by Dependents") # 提取特征 charges=telcom.iloc[:,1:20] # 對特征進行編碼 """ 離散特征的編碼分為兩種情況: 1、離散特征的取值之間沒有大小的意義,比如color:[red,blue],那么就使用one-hot編碼 2、離散特征的取值有大小的意義,比如size:[X,XL,XXL],那么就使用數值的映射{X:1,XL:2,XXL:3} """ corrDf = charges.apply(lambda x: pd.factorize(x)[0]) corrDf .head() # 構造相關性矩陣 corr = corrDf.corr() corr # 使用熱地圖顯示相關系數 ''' heatmap 使用熱地圖展示系數矩陣情況 linewidths 熱力圖矩陣之間的間隔大小 annot 設定是否顯示每個色塊的系數值 ''' plt.figure(figsize=(20,16)) ax = sns.heatmap(corr, xticklabels=corr.columns, yticklabels=corr.columns, linewidths=0.2, cmap="YlGnBu",annot=True) plt.title("Correlation between variables") # 結論:從上圖可以看出,互聯網服務、網絡安全服務、在線備份業務、設備保護業務、技術支持服務、網絡電視和網絡電影之間存在較強的相關性,多線業務和電話服務之間也有很強的相關性,並且都呈強正相關關系。 # 使用one-hot編碼 tel_dummies = pd.get_dummies(telcom.iloc[:,1:21]) tel_dummies.head() telcom.info() # 電信用戶是否流失與各變量之間的相關性 plt.figure(figsize=(15,8)) tel_dummies.corr()['Churn'].sort_values(ascending = False).plot(kind='bar') plt.title("Correlations between Churn and variables") # 由圖上可以看出,變量gender 和 PhoneService 處於圖形中間,其值接近於 0 ,這兩個變量對電信客戶流失預測影響非常小,可以直接舍棄。 # 網絡安全服務、在線備份業務、設備保護業務、技術支持服務、網絡電視、網絡電影和無互聯網服務對客戶流失率的影響 covariables=["OnlineSecurity", "OnlineBackup", "DeviceProtection", "TechSupport", "StreamingTV", "StreamingMovies"] fig,axes=plt.subplots(nrows=2,ncols=3,figsize=(16,10)) for i, item in enumerate(covariables): plt.subplot(2,3,(i+1)) ax=sns.countplot(x=item,hue="Churn",data=telcom,palette="Pastel2",order=["Yes","No","No internet service"]) plt.xlabel(str(item)) plt.title("Churn by "+ str(item)) i=i+1 plt.show() # 由上圖可以看出,在網絡安全服務、在線備份業務、設備保護業務、技術支持服務、網絡電視和網絡電影六個變量中,沒有互聯網服務的客戶流失率值是相同的,都是相對較低。 # # 這可能是因為以上六個因素只有在客戶使用互聯網服務時才會影響客戶的決策,這六個因素不會對不使用互聯網服務的客戶決定是否流失產生推論效應。 # 簽訂合同方式對客戶流失率的影響 sns.barplot(x="Contract",y="Churn", data=telcom, palette="Pastel1", order= ['Month-to-month', 'One year', 'Two year']) plt.title("Churn by Contract type") # 由圖上可以看出,簽訂合同方式對客戶流失率影響為:按月簽訂 > 按一年簽訂 > 按兩年簽訂,這可能表明,設定長期合同對留住現有客戶更有效。 # 付款方式對客戶流失率的影響 plt.figure(figsize=(10,5)) sns.barplot(x="PaymentMethod",y="Churn", data=telcom, palette="Pastel1", order= ['Bank transfer (automatic)', 'Credit card (automatic)', 'Electronic check','Mailed check']) plt.title("Churn by PaymentMethod type") # 由圖上可以看出,在四種支付方式中,使用Electronic check的用戶流流失率最高,其他三種支付方式基本持平,因此可以推斷電子賬單在設計上影響用戶體驗。 # ## 5、數據預處理 # 由前面結果可知,CustomerID表示每個客戶的隨機字符,對后續建模不影響,我這里選擇刪除CustomerID列;gender 和 PhoneService 與流失率的相關性低,可直接忽略。 telcomvar=telcom.iloc[:,2:20] telcomvar.drop("PhoneService",axis=1, inplace=True) # 提取ID telcom_id = telcom['customerID'] telcomvar.head() # 對客戶的職位、月費用和總費用進行去均值和方差縮放,對數據進行標准化 """ 標准化數據,保證每個維度的特征數據方差為1,均值為0,使得預測結果不會被某些維度過大的特征值而主導。 """ scaler = StandardScaler(copy=False) # fit_transform()的作用就是先擬合數據,然后轉化它將其轉化為標准形式 scaler.fit_transform(telcomvar[['tenure','MonthlyCharges','TotalCharges']]) # tranform()的作用是通過找中心和縮放等實現標准化 telcomvar[['tenure','MonthlyCharges','TotalCharges']]=scaler.transform(telcomvar[['tenure','MonthlyCharges','TotalCharges']]) # 使用箱線圖查看數據是否存在異常值 plt.figure(figsize = (8,4)) numbox = sns.boxplot(data=telcomvar[['tenure','MonthlyCharges','TotalCharges']], palette="Set2") plt.title("Check outliers of standardized tenure, MonthlyCharges and TotalCharges") # 由以上結果可以看出,在三個變量中不存在明顯的異常值 # 查看對象類型字段中存在的值 def uni(columnlabel): print(columnlabel,"--" ,telcomvar[columnlabel].unique()) # unique函數去除其中重復的元素,返回唯一值 telcomobject=telcomvar.select_dtypes(['object']) for i in range(0,len(telcomobject.columns)): uni(telcomobject.columns[i]) # 綜合之前的結果來看,在六個變量中存在No internet service,即無互聯網服務對客戶流失率影響很小,這些客戶不使用任何互聯網產品,因此可以將No internet service 和 No 是一樣的效果,可以使用 No 替代 No internet service telcomvar.replace(to_replace='No internet service', value='No', inplace=True) telcomvar.replace(to_replace='No phone service', value='No', inplace=True) for i in range(0,len(telcomobject.columns)): uni(telcomobject.columns[i]) # 使用Scikit-learn標簽編碼,將分類數據轉換為整數編碼 def labelencode(columnlabel): telcomvar[columnlabel] = LabelEncoder().fit_transform(telcomvar[columnlabel]) for i in range(0,len(telcomobject.columns)): labelencode(telcomobject.columns[i]) for i in range(0,len(telcomobject.columns)): uni(telcomobject.columns[i]) # ## 6、構建模型 # ### (1)建立訓練數據集和測試數據集 """ 我們需要將數據集拆分為訓練集和測試集以進行驗證。 由於我們所擁有的數據集是不平衡的,所以最好使用分層交叉驗證來確保訓練集和測試集都包含每個類樣本的保留人數。 交叉驗證函數StratifiedShuffleSplit,功能是從樣本數據中隨機按比例選取訓練數據(train)和測試數據(test) 參數 n_splits是將訓練數據分成train/test對的組數,可根據需要進行設置,默認為10 參數test_size和train_size是用來設置train/test對中train和test所占的比例 參數 random_state控制是將樣本隨機打亂 """ X=telcomvar y=telcom["Churn"].values sss=StratifiedShuffleSplit(n_splits=5, test_size=0.2, random_state=0) print(sss) print("訓練數據和測試數據被分成的組數:",sss.get_n_splits(X,y)) # 建立訓練數據和測試數據 for train_index, test_index in sss.split(X, y): print("train:", train_index, "test:", test_index) X_train,X_test=X.iloc[train_index], X.iloc[test_index] y_train,y_test=y[train_index], y[test_index] # 輸出數據集大小 print('原始數據特征:', X.shape, '訓練數據特征:',X_train.shape, '測試數據特征:',X_test.shape) print('原始數據標簽:', y.shape, ' 訓練數據標簽:',y_train.shape, ' 測試數據標簽:',y_test.shape) # ### (2)選擇機器學習算法 # 使用分類算法,這里選用10種分類算法 Classifiers=[["Random Forest",RandomForestClassifier()], ["Support Vector Machine",SVC()], ["LogisticRegression",LogisticRegression()], ["KNN",KNeighborsClassifier(n_neighbors=5)], ["Naive Bayes",GaussianNB()], ["Decision Tree",DecisionTreeClassifier()], ["AdaBoostClassifier", AdaBoostClassifier()], ["GradientBoostingClassifier", GradientBoostingClassifier()], ["XGB", XGBClassifier()], ["CatBoost", CatBoostClassifier(logging_level='Silent')] ] # ### (3)訓練模型 Classify_result=[] names=[] prediction=[] for name,classifier in Classifiers: classifier=classifier classifier.fit(X_train,y_train) y_pred=classifier.predict(X_test) recall=recall_score(y_test,y_pred) precision=precision_score(y_test,y_pred)
f1score=f1_score(y_test,y_pred,average='binary') class_eva=pd.DataFrame([recall,precision,f1score]) Classify_result.append(class_eva) name=pd.Series(name) names.append(name) y_pred=pd.Series(y_pred) prediction.append(y_pred) # ### (4)評估模型 # 評估模型 """ 召回率(recall)的含義是:原本為對的當中,預測為對的比例(值越大越好,1為理想狀態) 精確率、精度(precision)的含義是:預測為對的當中,原本為對的比例(值越大越好,1為理想狀態) F1分數(F1-Score)指標綜合了Precision與Recall的產出的結果 F1-Score的取值范圍從0到1的,1代表模型的輸出最好,0代表模型的輸出結果最差。 """ names=pd.DataFrame(names) names=names[0].tolist() result=pd.concat(Classify_result,axis=1) result.columns=names result.index=["recall","precision","f1score"] result # 綜上所述,在10種分類算法中朴素貝葉斯(Naive Bayes)的F1分數最大為63.31%,所以使用朴素貝葉斯模型效果最好。 # ## 7、實施方案 # 預測數據集特征(由於沒有提供預測數據集,這里選取后10行作為需要預測的數據集) pred_X = telcomvar.tail(10) # 提取customerID pre_id = telcom_id.tail(10) # 使用朴素貝葉斯方法,對預測數據集中的生存情況進行預測 model = GaussianNB() model.fit(X_train,y_train) pred_y = model.predict(pred_X) # 預測結果 predDf = pd.DataFrame({'customerID':pre_id, 'Churn':pred_y}) predDf