电信用户流失预测案例(1)
【Kaggle】Telco Customer Churn 電信用戶流失預測案例
前言:案例學習說明與案例建模流程
??在學習了經典機器學習算法和Scikit-Learn的調參策略之后,接下來,我們將把此前所學到的內容應用到實踐當中去。從本節開始,我們將圍繞Kaggle中的電信用戶流失數據集(Telco Customer Churn)進行用戶流失預測。在此過程中,我們將綜合應用此前所介紹的各種方法與技巧,并在實踐中提煉總結更多實用技巧。
??對于實戰案例的講解,我們將分為三個階段進行,當然這也是我們在參與算法競賽、或者在實際算法建模時的一般流程:
- Stage 1.業務背景解讀與數據探索
??在拿到數據(接受任務)的第一時間,需要對數據(也就是對應業務)的基本背景進行解讀。由于任何數據都誕生于某業務場景下,同時也是根據某些規則來進行的采集或者計算得出,因此如果可以,我們應當盡量去了解數據誕生的基本環境和對應的業務邏輯,盡可能準確的解讀每個字段的含義,而只有在無法獲取真實業務背景時,才會考慮退而求其次通過數據情況去倒推業務情況。
??當然,在進行了數據業務背景解讀后,接下來就需要對拿到的數據進行基本的數據探索。一般來說,數據探索包括數據分布檢驗、數據正確性校驗、數據質量檢驗、訓練集/測試集規律一致性檢驗等。當然,這里可能涉及到的操作較多,也并非所有的操作都必須在一次建模過程中全部完成。但作為教學案例,我們將在后續的內容中詳細介紹每個環節的相關操作及目的。 - Stage 2.數據預處理與特征工程
??在了解了建模業務背景和基本數據情況后,接下來我們就需要進行實際建模前的“數據準備”工作了,也就是數據預處理(數據清洗)與特征工程。其中,數據清洗主要聚焦于數據集數據質量提升,包括缺失值、異常值、重復值處理,以及數據字段類型調整等;而特征工程部分則更傾向于調整特征基本結構,來使數據集本身規律更容易被模型識別,如特征衍生、特殊類型字段處理(包括時序字段、文本字段等)等。
??當然,很多時候我們并不刻意區分數據清洗與特征工程之間的區別,很多時候數據清洗的工作也可以看成是特征工程的一部分。同時,也有很多時候我們也不會一定要求在不同階段執行不同操作,例如如果在數據探索時發現缺失值比例較小,則可以直接對其進行均值/眾數填補,而不用等到特征工程階段統一處理,再例如很多特征工程的方法需要結合實際建模效果來判別,所以有的時候特征衍生也會和建模過程交替進行。 - Stage 3.算法建模與模型調優
??在經過一系列準備工作后,就將進入到最終建模環節了,建模過程既包括算法訓練也包括參數調優。當然,很多時候建模工作不會一蹴而就,需要反復嘗試各種模型、各種調參方法、以及模型融合方法。此外,很多時候我們也需要根據最終模型輸出結果來進行數據預處理和特征工程相關方法調整。
??上述流程可以用如下流程圖進行表示:
??本節我們將先從數據集業務背景開始介紹,并簡單討論如何借助Kaggle平臺獲取更多幫助。同時我們也將圍繞獲取到的數據進行數據探索。
Part 1.數據背景介紹與數據探索
一、業務背景與數據背景
1.數據集基本情況與數據集獲取方法
??本次案例的數據源自Kaggle平臺上分享的建模數據集:Telco Customer Churn,該數據集描述了某電信公司的用戶基本情況,包括每位用戶已注冊的相關服務、用戶賬戶信息、用戶人口統計信息等,當然,也包括了最為核心的、也是后續建模需要預測的標簽字段——用戶流失情況(Churn)。
??需要注意的是,該數據并非競賽數據集,而是Kaggle分享的一個高投票數據集(1788 votes),是Kaggle平臺上非常經典的圍繞偏態數據集建模的數據集。該數據源自IBM商業社區(IBM Business Analytics Community)上分享的數據集,用于社區成員內部學習使用。
??根據IBM商業社區分享團隊描述,該數據集為某電信公司在加利福尼亞為7000余位用戶(個人/家庭)提供電話和互聯網服務的相關記錄。由于該數據集并不是競賽數據集,因此數據集的下載方式相對容易,官網也只提供了網頁下載一種選項(無法通過命令行直接下載)。我們可以在該數據集的Kaggle主頁看到數據集的相關信息以及下載地址。此處我們簡單介紹關于Kaggle數據集頁面的基本功能,既Kaggle平臺的基本使用方法,在后續的課程學習中,若是Kaggle案例,我們也將頻繁借助Kaggle主頁來獲取幫助。當然,熟練使用Kaggle主頁獲取數據和挖掘信息(而不是借助第三方渠道),也是算法工程師必備技能之一。
- 數據集主頁
??首先是數據集主頁:
??此處我們能大致看到數據集基本信息,如總共只有一個數據文件(在其他很多情況下,一個建模目標可能會包含多個數據集),總共21列,7043條數據,以及其他各列的一些基本統計信息等。
??當然,在數據集主頁上,我們可以直接點擊下載按鈕進行數據集下載。下載完成后將其放到當前操作主目錄下進行讀取:
在其他一些情況下,我們可能需要借助命令行來進行數據集下載和建模結果提交,相關內容我們將在后續介紹競賽數據集時詳細討論。
- Discussion頁面
??此外,我們還能夠在Discussion頁面中看到圍繞該建模問題的相關討論。值得一提的是,在很多場合下(尤其是在參加算法競賽時),Discussion頁面中的討論帖都是重要的信息獲取渠道,其中不乏一些官方給出的補充問答內容、一些競賽大神給出的自己的賽題理解、以及一些參賽者獨到的討論和見解。而從中快速捕獲信息,則能幫助你迅速建立信息優勢。
??例如在本數據集相關的討論中,就有關于用戶流失業務背景的相關討論。其內較為具體的說明了到底什么是用戶流失、為什么要關注用戶流失,以及圍繞用戶流失在進行建模時,最好關注那些模型評估指標。當然,這些內容我們也將隨后詳細探討。
- Code頁面
??此外,我們還能夠在code頁面看到其他用戶分享的代碼,其中也不乏一些精彩的思路和方法,也有很多可以借鑒和學習的內容。比如在該數據集的Code頁面,就有很多同學會比較關注的處理樣本不均衡的SHAP方法,以及該方法配合集成學習的使用方法:
當然,Kaggle平臺上也有很多帶有廣告性質的內容,需要進行甄別。
2.用戶流失業務背景與建模目標
- 電信業務基本背景介紹
??在基本了解Kaggle平臺使用方法以及獲取到建模數據集之后,接下來我們需要圍繞電信用戶流失這一基本業務背景來進行介紹,同時解釋本案例的最終建模目標。
??我們知道,電信作為公共網絡、數據傳輸、電話語音通信等基礎服務提供方,一直以來都是國家支柱產業之一。而伴隨著移動互聯網的普及、數字經濟蓬勃發展,網絡這一基礎設施也愈發重要。有個非常形象的比喻,在過去,斷電會導致工廠停產、造成重大的經濟損失,而現在,中斷網絡數字傳輸,則足以讓某些企業一夜損失上億。
??簡而言之,在一定程度上,網絡的質量和速度甚至會直接決定著數字經濟發展的質量和速度。而伴隨著5G時代的到來,5G的工業化應用,如車聯網、物聯網、工業互聯網等,也將在未來發展成為萬億規模產業,并且增強寬帶、海量連接、低延時、高可靠的網絡基礎設施,將為構建物聯網、人工智能等技術體系提供保障。
??而在此背景下,電信市場的競爭也愈發激烈。一般來說電信領域的運營商在3-4家時能保持一個健康的市場競爭狀態,而在國內,5G的運營商牌照也頒發了四家,除了三大運營商外(電信、聯通、移動),還有中國廣電。而在數字時代,傳統的大眾營銷已經失去優勢,如何基于用戶信息和行為,來進行更加精準的營銷,從而滿足用戶更加多樣化、層次化和個性化的需求,成為所有電信運營商必須面對的課題。而于此同時,電信的公共客戶(個人或家庭用戶)用戶又同時具有易變性、發展性和替代性等特點,且用戶需求彈性較小,外加普通用戶購買電信產品周期較長,導致在實際的交易關系中,電信公司對公共客戶獲客較難、主動拓展新用戶成本較高,因此維系既有用戶、防止用戶流失就成了重要的運營策略。
??當然,對于電信運營商來說,用戶流失有很多偶然因素,不過通過對用戶屬性和行為的數字化描述,我們或許也能夠在這些數據中,挖掘導致用戶流失的“蛛絲馬跡”,并且更重要的一點,如果能夠實時接入這些數據,或許還能夠進一步借助模型來對未來用戶流失的風險進行預測,從而及時制定挽留策略,來防止用戶真實流失情況發生。
- 機器學習建模目標
??也就是說,在此背景下,實際的算法建模目標有兩個,其一是對流失用戶進行預測,其二則是找出影響用戶流失的重要因子,來輔助運營人員來進行營銷策略調整或制定用戶挽留措施。
??綜合上述兩個目標我們不難發現,我們要求模型不僅要擁有一定的預測能力,并且能夠輸出相應的特征重要性排名,并且最好能夠具備一定的可解釋性,也就是能夠較為明顯的闡述特征變化是如何影響標簽取值變化的。據此要求,我們首先可以考慮邏輯回歸模型。邏輯回歸的線性方程能夠提供非常好的結果可解釋性,同時我們也可以通過邏輯回歸中的正則化項也可以用于評估特征重要性。
??當然,此外我們也可以考慮構建決策樹即集成模型來解決該問題。決策樹同樣具有較好的可解釋性,并且也可以根據樹模型中的信息熵(或者基尼系數)的下降情況來進行特征重要性評估。
二、數據解讀與預處理
??在基本了解業務背景和建模目標之后,我們開始圍繞數據集進行解讀和探索。
1.字段解釋
??首先是圍繞數據集字段含義進行解釋。該數據集并沒有提供相應的數據字典作為不同字段的解釋,但由于數據集并沒有匿名字段,所以基本可以根據字段的名稱給出相應的解釋:
tcc.info() # <class 'pandas.core.frame.DataFrame'> # RangeIndex: 7043 entries, 0 to 7042 # Data columns (total 21 columns): # # Column Non-Null Count Dtype # --- ------ -------------- ----- # 0 customerID 7043 non-null object # 1 gender 7043 non-null object # 2 SeniorCitizen 7043 non-null int64 # 3 Partner 7043 non-null object # 4 Dependents 7043 non-null object # 5 tenure 7043 non-null int64 # 6 PhoneService 7043 non-null object # 7 MultipleLines 7043 non-null object # 8 InternetService 7043 non-null object # 9 OnlineSecurity 7043 non-null object # 10 OnlineBackup 7043 non-null object # 11 DeviceProtection 7043 non-null object # 12 TechSupport 7043 non-null object # 13 StreamingTV 7043 non-null object # 14 StreamingMovies 7043 non-null object # 15 Contract 7043 non-null object # 16 PaperlessBilling 7043 non-null object # 17 PaymentMethod 7043 non-null object # 18 MonthlyCharges 7043 non-null float64 # 19 TotalCharges 7043 non-null object # 20 Churn 7043 non-null object # dtypes: float64(1), int64(2), object(18) # memory usage: 1.1+ MB根據Kaggle的數據集介紹,以及IBM商業分析社區中提供的解釋,數據集中各字段解釋如下:
| customerID | 用戶ID |
| gender | 性別 |
| SeniorCitizen | 是否是老年人(1代表是) |
| Partner | 是否有配偶(Yes or No) |
| Dependents | 是否經濟獨立(Yes or No) |
| tenure | 用戶入網時間 |
| PhoneService | 是否開通電話業務(Yes or No) |
| MultipleLines | 是否開通多條電話業務(Yes 、 No or No phoneservice) |
| InternetService | 是否開通互聯網服務(No、DSL數字網絡或filber potic光線網絡) |
| OnlineSecurity | 是否開通網絡安全服務(Yes、No or No internetservice) |
| OnlineBackup | 是否開通在線備份服務(Yes、No or No internetservice) |
| DeviceProtection | 是否開通設備保護服務(Yes、No or No internetservice) |
| TechSupport | 是否開通技術支持業務(Yes、No or No internetservice) |
| StreamingTV | 是否開通網絡電視(Yes、No or No internetservice) |
| StreamingMovies | 是否開通網絡電影(Yes、No or No internetservice) |
| Contract | 合同簽訂方式(按月、按年或者兩年) |
| PaperlessBilling | 是否開通電子賬單(Yes or No) |
| PaymentMethod | 付款方式(bank transfer、credit card、electronic check、mailed check) |
| MonthlyCharges | 月度費用 |
| TotalCharges | 總費用 |
| Churn | 是否流失(Yes or No) |
同時,根據官方給出的數據集說明,上述字段基本可以分為三類,分別是用戶已注冊的服務信息、用戶賬戶信息和用戶人口統計信息,三類字段劃分情況如下:
2.數據質量探索
??在了解數據集字段含義后,首先我們需要對數據集的數據質量進行探索,這也是數據探索的最基礎的角度。
- 數據集正確性校驗
??首先是數據集正確性校驗。一般來說數據集正確性校驗分為兩種,其一是檢驗數據集字段是否和數據字典中的字段一致,其二則是檢驗數據集中ID列有無重復。由于該數據集并為提供數據字典,因此此處主要校驗數據集ID有無重復:
tcc['customerID'].nunique() == tcc.shape[0] #True另外的方法
tcc['customerID'].unique() #array(['7590-VHVEG', '5575-GNVDE', '3668-QPYBK', ..., '4801-JZAZL', # '8361-LTMKD', '3186-AJIEK'], dtype=object) tcc['customerID'].nunique() #7043 len(set(tcc["customerID"])) #7043 tcc.shape #(7043, 21)當然,ID列沒有重復,則數據集中也不存在完全重復的兩行數據:
tcc.duplicated().sum() #數據中有沒有重復的兩行 #0- 數據缺失值檢驗
??接下來進一步檢查數據集缺失情況,我們可以通過isnull來快速查看數據集缺失情況:
tcc.isnull().sum() # customerID 0 # gender 0 # SeniorCitizen 0 # Partner 0 # Dependents 0 # tenure 0 # PhoneService 0 # MultipleLines 0 # InternetService 0 # OnlineSecurity 0 # OnlineBackup 0 # DeviceProtection 0 # TechSupport 0 # StreamingTV 0 # StreamingMovies 0 # Contract 0 # PaperlessBilling 0 # PaymentMethod 0 # MonthlyCharges 0 # TotalCharges 0 # Churn 0 # dtype: int64 '''也可以tcc.info()'''此外,我們也可以通過定義如下函數來輸出更加完整的每一列缺失值的數值和占比:
def missing (df):"""計算每一列的缺失值及占比"""missing_number = df.isnull().sum().sort_values(ascending=False) # 每一列的缺失值求和后降序排序 missing_percent = (df.isnull().sum()/df.isnull().count()).sort_values(ascending=False) # 每一列缺失值占比missing_values = pd.concat([missing_number, missing_percent], axis=1, keys=['Missing_Number', 'Missing_Percent']) # 合并為一個DataFramereturn missing_valuesmissing(tcc)此外,在info返回的信息中的non-null也能看出數據集不存在缺失值。
從上述結果能看出,數據集不存在缺失值。
沒有缺失只代表數據集中沒有None或者Nan,并不排除可能存在用別的值表示缺失值的情況,稍后我們將對其進行進一步分析。
3.字段類型探索
??接下來,我們將進一步圍繞數據集的字段類型來進行調整。
- 時序字段處理
??根據數據集info我們發現,大多數字段都屬于離散型字段,并且object類型居多。對于建模分析來說,我們是無法直接使用object類型對象的,因此需要對其進行類型轉化,通常來說,我們會將字段劃分為連續型字段和離散型字段,并且根據離散字段的具體含義來進一步區分是名義型變量還是有序變量。不過在劃分連續/離散字段之前,我們發現數據集中存在一個入網時間字段,看起來像是時序字段。需要注意的是,從嚴格意義上來說,用時間標注的時序字段即不數據連續型字段或離散型字段(盡管可以將其看成是離散字段,但這樣做會損失一些信息),因此我們需要重點關注入網時間字段是否是時間標注的字段:
tcc['tenure'] # 0 1 # 1 34 # 2 2 # 3 45 # 4 2 # .. # 7038 24 # 7039 72 # 7040 11 # 7041 4 # 7042 66 # Name: tenure, Length: 7043, dtype: int64簡單查看我們發現,該字段并不是典型的用年月日標注的時間字段,如2020-08-01,而是一串連續的數值。當然,我們可以進一步查看該字段的取值范圍:
tcc['tenure'].nunique() #73該字段總共有73個不同的取值,結合此前所說,數據集是第三季度的用戶數據,因此我們推斷該字段應該是經過字典排序后的離散型字段。所謂字典排序,其本質是一種離散變量的轉化方式,有時我們也可以將時序數據進行字典排序,該過程我們可以通過如下示例進行說明:
也就是說,在第三季度中,這些用戶的行為發生在某73天內,因此入網時間字段有73個取值。不過由于該字段是經過字典排序后的結果,因此已經損失了原始信息,即每位用戶實際的入網時間。而在實際的分析過程中,我們可以轉化后的入網時間字段看成是離散變量,當然也可以將其視作連續變量來進行分析,具體選擇需要依據模型來決定。此處我們先將其視作離散變量,后續根據情況來進行調整。
關于字典排序的代碼實現方法會在后續進行介紹。
- 連續/離散型變量標注
??接下來,我們來標注每一列的數據類型,我們可以通過不同列表來存儲不同類型字段的名稱:
# 離散字段 category_cols = ['customerID', 'gender', 'SeniorCitizen', 'Partner', 'Dependents', 'tenure', 'PhoneService', 'MultipleLines', 'InternetService', 'OnlineSecurity', 'OnlineBackup', 'DeviceProtection', 'TechSupport', 'StreamingTV', 'StreamingMovies', 'Contract', 'PaperlessBilling','PaymentMethod']# 連續字段 numeric_cols = ['MonthlyCharges', 'TotalCharges']# 標簽 target = 'Churn'# 驗證是否劃分能完全 assert len(category_cols) + len(numeric_cols) + 1 == tcc.shape[1]??當然,大多數時候離散型字段都在讀取時都是object類型,因此我們也可以通過如下方式直接提取object字段:
tcc.select_dtypes('object').columns #Index(['customerID', 'gender', 'Partner', 'Dependents', 'PhoneService', # 'MultipleLines', 'InternetService', 'OnlineSecurity', 'OnlineBackup', # 'DeviceProtection', 'TechSupport', 'StreamingTV', 'StreamingMovies', # 'Contract', 'PaperlessBilling', 'PaymentMethod', 'TotalCharges', # 'Churn'], # dtype='object')然后,我們需要對不同類型字段進行轉化。并且在此過程中,我們需要檢驗是否存在采用別的值來表示缺失值的情況。就像此前所說我們通過isnull只能檢驗出None(Python原生對象)和np.Nan(numpy/pandas在讀取數據文件時文件內部缺失對象的讀取后表示形式)對象。但此外我們還需要注意數據集中是否包含采用某符號表示缺失值的情況,例如某些時候可能使用空格(其本質也是一種字符)來代替空格:
df = pd.DataFrame({'A':['Y', None, 'N', 'N'], 'B':[np.NaN, ' ', 'Y', 'N']}) df
此時在進行檢驗時,空格的數據并不會被識別為缺失值(空格本身也是一種值)。
但根據實際情況來看,空格可能確實是代表著數據采集時數據是缺失的,因此我們仍然需要將其識別然后標記為缺失值,此時可以通過比較數據集各列的取值水平是否和既定的一致來進行檢查。例如,對于上述df數據集來說,特征A和B默認情況只有Y和N兩種取值,而B列由于通過空格表示了缺失值,因此用nunique查看數據集的話,B列將出現3種取值:
注意,缺失值None或NaN并不是某一種取值
此時我們可以進一步查看B列每個不同取值出現的次數:
df['B'].explode().value_counts().to_dict() #{'Y': 1, 'N': 1, ' ': 1}在判斷空格為缺失值后,我們即可對其進行后續處理。我們可以先檢驗離散變量是否存在這種情況:
tcc[category_cols].nunique() #customerID 7043 #gender 2 #SeniorCitizen 2 #Partner 2 #Dependents 2 #tenure 73 #PhoneService 2 #MultipleLines 3 #InternetService 3 #OnlineSecurity 3 #OnlineBackup 3 #DeviceProtection 3 #TechSupport 3 #StreamingTV 3 #StreamingMovies 3 #Contract 3 #PaperlessBilling 2 #PaymentMethod 4 #dtype: int64我們也可以通過如下方式查看每個離散變量的不同取值:
for feature in tcc[category_cols]:print(f'{feature}: {tcc[feature].unique()}') #customerID: ['7590-VHVEG' '5575-GNVDE' '3668-QPYBK' ... '4801-JZAZL' '8361-LTMKD' # '3186-AJIEK'] #gender: ['Female' 'Male'] #SeniorCitizen: [0 1] #Partner: ['Yes' 'No'] #Dependents: ['No' 'Yes'] #tenure: [ 1 34 2 45 8 22 10 28 62 13 16 58 49 25 69 52 71 21 12 30 47 72 17 27 # 5 46 11 70 63 43 15 60 18 66 9 3 31 50 64 56 7 42 35 48 29 65 38 68 # 32 55 37 36 41 6 4 33 67 23 57 61 14 20 53 40 59 24 44 19 54 51 26 0 # 39] #PhoneService: ['No' 'Yes'] #MultipleLines: ['No phone service' 'No' 'Yes'] #InternetService: ['DSL' 'Fiber optic' 'No'] #OnlineSecurity: ['No' 'Yes' 'No internet service'] #OnlineBackup: ['Yes' 'No' 'No internet service'] #DeviceProtection: ['No' 'Yes' 'No internet service'] #TechSupport: ['No' 'Yes' 'No internet service'] #StreamingTV: ['No' 'Yes' 'No internet service'] #StreamingMovies: ['No' 'Yes' 'No internet service'] #Contract: ['Month-to-month' 'One year' 'Two year'] #PaperlessBilling: ['Yes' 'No'] #PaymentMethod: ['Electronic check' 'Mailed check' 'Bank transfer (automatic)' # 'Credit card (automatic)']'''或者''' for feature in category_cols:print("{0}:{1}".format(feature,tcc[feature].unique()))通過對比離散變量的取值水平,我們發現并不存在通過其他值表示缺失值的情況。
需要注意的是,如果是連續變量,則無法使用上述方法進行檢驗(取值水平較多),但由于往往我們需要將其轉化為數值型變量再進行分析,因此對于連續變量是否存在其他值表示缺失值的情況,我們也可以觀察轉化情況來判別,例如如果是用空格代表缺失值,則無法直接使用astype來轉化成數值類型。
# 無法全部轉化為數值型字段,運行將報錯 # tcc[numeric_cols].astype(float)- 缺失值檢驗與填補
發現在連續特征中存在空格。則此時我們需要進一步檢查空格字符出現在哪一列的哪個位置,我們可以通過如下函數來進行檢驗:
def find_index(data_col, val):"""查詢某值在某列中第一次出現位置的索引,沒有則返回-1:param data_col: 查詢的列:param val: 具體取值"""val_list = [val]if data_col.isin(val_list).sum() == 0:index = -1else:index = data_col.isin(val_list).idxmax()return index tcc["MonthlyCharges"].isin([" "]) #0 False #1 False #2 False #3 False #4 False # ... #7038 False #7039 False #7041 False #7042 False #Name: MonthlyCharges, Length: 7043, dtype: bool查看空格第一次出現在哪一列的哪個位置:
for col in numeric_cols:print(find_index(tcc[col], ' ')) #-1 #488即空格第一次出現在’TotalCharges’列的索引值為488的位置:
tcc['TotalCharges'].iloc[488] #' '接下來使用np.nan對空格進行替換,并將’MonthlyCharges’轉化為浮點數類型:
tcc['TotalCharges']= tcc['TotalCharges'].apply(lambda x: x if x!= ' ' else np.nan).astype(float) tcc['MonthlyCharges'] = tcc['MonthlyCharges'].astype(float)再次查看連續變量的缺失值占比情況:
missing(tcc[numeric_cols])
關于該缺失值應該如何填補,首先考慮的是,由于缺失值占比較小,因此可以直接使用均值進行填充:
此外,我們也可以簡單觀察缺失’TotalCharges’信息的每條數據實際情況,或許能發現一些蛛絲馬跡:
tcc[tcc['TotalCharges'].isnull()]
我們發現,這11條數據的入網時間都是0,也就是說,這11位用戶極有可能是在統計周期結束前的最后時間入網的用戶,因此沒有過去的總消費記錄,但是卻有當月的消費記錄。也就是說,該數據集的過去總消費記錄不包括當月消費記錄,也就是不存在過去總消費記錄等于0的記錄。我們可以簡單驗證:
既然如此,我們就可以將這11條記錄的缺失值記錄為0,以表示在最后一個月統計消費金額前,這些用戶的過去總消費金額為0:
tcc['TotalCharges'] = tcc['TotalCharges'].fillna(0) tcc['TotalCharges'].isnull().sum() #0 tcc['TotalCharges'].describe() #count 7043.000000 #mean 2279.734304 #std 2266.794470 #min 0.000000 #25% 398.550000 #50% 1394.550000 #75% 3786.600000 #max 8684.800000 #Name: TotalCharges, dtype: float64此外還有另一種便捷的方式,即直接使用pd.to_numeric對連續變量進行轉化,并在errors參數位上輸入’coerce’參數,表示能直接轉化為數值類型時直接轉化,無法轉化的用缺失值填補,過程如下:
df1 = pd.read_csv('WA_Fn-UseC_-Telco-Customer-Churn.csv') df1.TotalCharges = pd.to_numeric(df1.TotalCharges, errors='coerce') # 查看缺失值情況 df1.TotalCharges.isnull().sum() #11 # 查看原空格處是否被標記為缺失值 df1.TotalCharges.iloc[488] #nan # 查看字段整體類型 df1.TotalCharges.dtype #dtype('float64')注意,此處暫時未對離散特征進行變量類型轉化,是因為本小節后半段需要圍繞標簽取值在不同特征維度上分布進行分析,此時需要查看各特征的原始取值情況(例如性別是Male和Female,而不是0/1),因此我們會在本節結束后對離散變量再進行字典編碼。
- 異常值檢驗
??當然,對于連續型變量,我們可以進一步對其進行異常值檢測。首先我們可以采用describe方法整體查看連續變量基本統計結果:
tcc[numeric_cols].describe()
異常值檢測有很多方法,我們可以通過三倍標準差法來進行檢驗,即以均值-3倍標注差為下界,均值+3倍標準差為上界,來檢測是否有超過邊界的點:
能夠發現,數據集并不存在異常值點。
??此外,我們還可以通過箱線圖來進行異常值點的識別(數據偏態嚴重用箱線圖好,正態分布兩種方法都好),和3倍標準差法利用均值和方差進行計算不同,箱線圖主要借助中位數和四分位數來進行計算,以上四分位數+1.5倍四分位距為上界、下四分位數-1.5倍四分位距為下界,超出界限則認為是異常值。我們可以借助plt.boxplot繪圖函數迅速繪制箱線圖來觀察異常值點情況:
import seaborn as sns import matplotlib.pyplot as plt # MonthlyCharges上四分位數 Q3 = tcc[numeric_cols].describe()['MonthlyCharges']['75%'] Q3 #89.85 # MonthlyCharges下四分位數 Q1 = tcc[numeric_cols].describe()['MonthlyCharges']['25%'] Q1 #35.5 #MonthlyCharges的四分位距 IQR = Q3 - Q1 IQR #54.349999999999994 # 異常值上界 Q3 + 1.5 * IQR #171.375 # 異常值下界 Q1 - 1.5 * IQR #-46.02499999999999 tcc['MonthlyCharges'].min(), tcc['MonthlyCharges'].max() #(18.25, 118.75)Q3 = tcc[numeric_cols].describe()['TotalCharges']['75%'] Q1 = tcc[numeric_cols].describe()['TotalCharges']['25%'] IQR = Q3 - Q1 (Q1 - 1.5 * IQR, Q3 + 1.5 * IQR) #(-4683.525, 8868.675) tcc['TotalCharges'].min(), tcc['TotalCharges'].max() #(0.0, 8684.8)??不過需要知道的是,由于數據集中沒有超出邊界的異常值點,因此在實際繪制箱線圖時,箱線圖的邊界會以數據集的極值為準:
plt.figure(figsize=(16, 6), dpi=200) plt.subplot(121) plt.boxplot(tcc['MonthlyCharges']) plt.xlabel('MonthlyCharges') plt.subplot(122) plt.boxplot(tcc['TotalCharges']) plt.xlabel('TotalCharges')
??能夠發現,根據箱線圖的判別結果,數據并沒有異常值出現。當然,此外我們還能通過連續變量的分布情況來觀察是否存在異常值:
當然,通過上述圖像我們也能基本看出月消費金額和總消費金額的基本分布情況,對于大多數用戶來說月消費金額和總消費金額都較小,而月消費金額所出現的波動,極有可能是某些套餐的組合定價。
需要知道的是,對于異常值的檢測和處理也是需要根據實際數據分布和業務情況來判定,一般來說,數據分布越傾向于正態分布,則通過三倍標準差或者箱線圖檢測的異常值會更加準確一些,此外,在很多時候,異常值或許是某類特殊用戶的標識,有的時候我們需要圍繞異常值進行單獨分析,而不是簡單的對其進行修改。
對于異常值的修改,最通用的方法就是天花板蓋帽法
三、變量相關性探索分析與探索性分析
1.標簽取值分布
??在基本完成數據探索與處理之后,接下來我們可以通過探索標簽在不同特征上的分布,來初步探索哪些特征對標簽取值影響較大。當然,首先我們可以先查看標簽字段的取值分布情況:
y = tcc['Churn'] print(f'Percentage of Churn: {round(y.value_counts(normalize=True)[1]*100,2)} % --> ({y.value_counts()[1]} customer)\nPercentage of customer did not churn: {round(y.value_counts(normalize=True)[0]*100,2)} % --> ({y.value_counts()[0]} customer)') #Percentage of Churn: 26.54 % --> (1869 customer) #Percentage of customer did not churn: 73.46 % --> (5174 customer)'''或者''' (tcc['Churn'][tcc['Churn']=='No'].count())/tcc['Churn'].count() #0.7346301292063041 (tcc['Churn'][tcc['Churn']=='Yes'].count())/tcc['Churn'].count() #0.2653698707936959也就是在總共7000余條數據中,流失用戶占比約為26%,整體來看標簽取值并不均勻,但如果放到用戶流失這一實際業務背景中來探討,流失用戶比例占比26%已經是非常高的情況了。當然我們也可以通過直方圖進行直觀的觀察:
sns.displot(y) #sns.countplot(y) #sns.histplot(y)2.變量相關性分析
??接下來,我們嘗試對變量和標簽進行相關性分析。從嚴格的統計學意義講,不同類型變量的相關性需要采用不同的分析方法,例如連續變量之間相關性可以使用皮爾遜相關系數進行計算,而連續變量和離散變量之間相關性則可以卡方檢驗進行分析,而離散變量之間則可以從信息增益角度入手進行分析。但是,如果我們只是想初步探查變量之間是否存在相關關系,則可以忽略變量連續/離散特性,統一使用相關系數進行計算,這也是pandas中的.corr方法所采用的策略。
- 計算相關系數矩陣
??當然,首先我們可以先計算相關系數矩陣,直接通過具體數值大小來表示相關性強弱。不過需要注意的是,盡管我們可以忽略變量的連續/離散特性,但為了更好的分析分類變量如何影響標簽的取值,我們需要將標簽轉化為整型(也就是視作連續變量),而將所有的分類變量進行啞變量處理:
# 剔除ID列 df3 = tcc.iloc[:,1:].copy()# 將標簽Yes/No轉化為1/0 df3['Churn'].replace(to_replace='Yes', value=1, inplace=True) df3['Churn'].replace(to_replace='No', value=0, inplace=True)# 將其他所有分類變量轉化為啞變量,連續變量保留不變 df_dummies = pd.get_dummies(df3) df_dummies.head()
此處需要注意pd.get_dummies會將非數值類型對象類型進行自動啞變量轉化,而對數值類型對象,無論是整型還是浮點型,都會保留原始列不變:
然后即可采用.corr方法計算相關系數矩陣:
當然,在所有的相關性中,我們較為關注特征和標簽之間的相關關系,因此可以直接挑選標簽列的相關系數計算結果,并進行降序排序:
df_dummies.corr()['Churn'].sort_values(ascending = False) # Churn 1.000000 # Contract_Month-to-month 0.405103 # OnlineSecurity_No 0.342637 # TechSupport_No 0.337281 # InternetService_Fiber optic 0.308020 # . . . # StreamingTV_No internet service -0.227890 # TechSupport_No internet service -0.227890 # OnlineBackup_No internet service -0.227890 # Contract_Two year -0.302253 # tenure -0.352229需要知道的是,根據相關系數計算的基本原理,相關系數為正數,則二者為正相關,數值變化會更傾向于保持同步。例如Churn與Contract_Month-to-month相關系數為0.4,則說明二者存在一定的正相關性,即Contract_Month-to-month取值為1(更大)越有可能使得Churn取值為1。也就是在Contract字段的Month-to-month取值結果和最終流失的結果相關性較大,也就是相比其他條件,Contract取值為Month-to-month的用戶流失概率較大,而tenure和Churn負相關,則說明tenure取值越大、用戶流失概率越小。其他結果解讀依此類推。
- 熱力圖展示相關性
??當然,我們也可以通過一些可視化的方式來展示特征和標簽之間的相關性,例如可以考慮使用熱力圖進行相關性的可視化展示:
plt.figure(figsize=(15,8), dpi=200) sns.heatmap (df_dummies.corr())- 柱狀圖展示相關性
??當然,很多時候如果特征較多,熱力圖的展示結果并不直觀,此時我們可以考慮進一步使用柱狀圖來進行表示:
sns.set() plt.figure(figsize=(15,8), dpi=200)df_dummies.corr()['Churn'].sort_values(ascending = False).plot(kind='bar')3.探索性數據分析
??當然,直接計算整體相關系數矩陣以及對整體相關性進行可視化展示是一種非常高效便捷的方式,在實際的算法競賽中,我們也往往會采用上述方法快速的完成數據相關性檢驗和探索工作。不過,如果是對于業務分析人員,可能我們需要為其展示更為直觀和具體的一些結果,才能有效幫助業務人員對相關性進行判別。此時我們可以考慮圍繞不同類型的屬性進行柱狀圖的展示與分析。當然,此處需要對比不同字段不同取值下流失用戶的占比情況,因此可以考慮使用柱狀圖的另一種變形:堆疊柱狀圖來進行可視化展示:
fig,axes=plt.subplots(nrows=1,ncols=2,figsize=(12,6), dpi=100)# 柱狀圖 plt.subplot(121) sns.countplot(x="gender",hue="Churn",data=tcc,palette="Blues", dodge=True) plt.xlabel("Gender") plt.title("Churn by Gender")# 柱狀堆疊圖 plt.subplot(122) sns.countplot(x="gender",hue="Churn",data=tcc,palette="Blues", dodge=False) plt.xlabel("Gender") plt.title("Churn by Gender")#x: x軸上的條形圖,以x標簽劃分統計個數 #y: y軸上的條形圖,以y標簽劃分統計個數 #hue: 在x或y標簽劃分的同時,再以hue標簽劃分統計個數注,此處堆疊圖簡單理解其實就是純粹的重合,并不是上下堆疊,而是深色柱狀圖覆蓋在淺色柱狀圖的上面。
接下來,我們將根據此前劃分的字段類型來進行逐類分析:
- 用戶人口統計信息
首先是用戶的人口統計信息:
col_1 = ["gender", "SeniorCitizen", "Partner", "Dependents"]fig,axes=plt.subplots(nrows=2,ncols=2,figsize=(16,12), dpi=200)for i, item in enumerate(col_1):plt.subplot(2,2,(i+1))ax=sns.countplot(x=item,hue="Churn",data=tcc,palette="Blues", dodge=False)plt.xlabel(item)plt.title("Churn by "+ item)
能夠發現,老年用戶、未結婚用戶以及經濟未獨立用戶流失比例相對較高,而性別因素對是否流失影響不大。在實際制定運營策略時,這三類用戶需要重點關注。
- 已注冊的服務信息
??然后繼續分析服務屬性字段與用戶流失之間的關系:
col_2 = ["OnlineSecurity", "OnlineBackup", "DeviceProtection", "TechSupport", "StreamingTV", "StreamingMovies"]fig,axes=plt.subplots(nrows=2,ncols=3,figsize=(16,12))for i, item in enumerate(col_2):plt.subplot(2,3,(i+1))ax=sns.countplot(x=item,hue="Churn",data=tcc,palette="Blues",order=["Yes","No","No internet service"], dodge=False)plt.xlabel(item)plt.title("Churn by "+ item)
能夠發現,未開通網絡服務的用戶、以及開通了網絡服務并且同時開通很多增值服務的用戶往往流失概率較小,而開通網絡服務、未開通其他增值服務的用戶流失概率較大。因此可以考慮更多的提供免費體驗增值服務的機會,增加增值服務宣傳,促進用戶購買,從而提升用戶粘性。
- 用戶合同屬性
??接下來進一步分析用戶合同屬性與流失率之間的關系:
col_3 = ["Contract", "PaperlessBilling", "PaymentMethod"]fig,axes=plt.subplots(nrows=2,ncols=2,figsize=(16,12))for i, item in enumerate(col_3):plt.subplot(2,2,(i+1))ax=sns.countplot(x=item,hue="Churn",data=tcc,palette="Blues", dodge=False)plt.xlabel(item)plt.title("Churn by "+ item)
能夠發現,一次性簽署服務周期越短的用戶越容易流失,并且相比其他支付方式,在線支付的用戶更容易流失。因此可能需要在實際運營過程中更多的引導用戶簽訂長期合同,無論是通過折扣還是滿贈,借此提升用戶生命周期。此外,需要更加關注在線支付用戶的實際產品體驗,也可以考慮提升在線支付本身的用戶體驗或者提供更多的價格優惠,以此提升在線支付用戶滿意度。
??當然,如果能獲取更多的實際業務背景知識,則能夠進行更加深入的數據分析與用戶挽留策略的制定。不過需要知道的是,無論是作為實際建模預測項目,還是結合實際業務進行數據分析,在完成數據清洗后對變量進行相關性分析,都是了解數據情況的重要手段,也是所有建模過程中必備的環節。在后續的內容中,我們也將在此基礎上進一步來進行特征工程以及模型訓練的相關工作,最終借助模型,來進行實時的用戶流失預測,并且根據最終的模型結果來更精確的判別變量重要性,以及根據模型方程來判斷變量影響流失概率的量化結果。
總結
以上是生活随笔為你收集整理的电信用户流失预测案例(1)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 文章/网站分享工具——百度分享
- 下一篇: ionic4学习笔记11-某东项目热门商