Python 銀行信用卡客戶流失預測(kaggle)

1.背景

愈來愈多的客戶再也不使用信用卡服務,銀行的經理對此感到不安。若是有人能爲他們預測哪些客戶即將流失,他們將不勝感激,由於這樣他們能夠主動向客戶提供更好的服務,並挽回這些即將流失的客戶。python

2.數據集

該數據集由10,000個客戶組成,其中包含了他們的年齡,工資,婚姻情況,信用卡限額,信用卡類別等。express

不過,這裏面只有16%的客戶是流失的,所以拿來預測客戶是否會流失有點難度。編程

在Python實用寶典後臺回覆 預測客戶流失 下載這份數據和源代碼。dom

譯自kaggle並對原文進行了修改和補充,感謝原做者:
https://www.kaggle.com/thomas...編輯器

3.代碼與分析

開始以前,你要確保Python和pip已經成功安裝在電腦上,若是沒有,請訪問這篇文章:超詳細Python安裝指南 進行安裝。若是你用Python的目的是數據分析,能夠直接安裝Anaconda:Python數據分析與挖掘好幫手—Anaconda,它內置了Python和pip.性能

此外,推薦你們用VSCode編輯器,由於它能夠在編輯器下方的終端運行命令安裝依賴模塊:Python 編程的最好搭檔—VSCode 詳細指南測試

本文具有流程性,建議使用 VSCode 的 Jupiter Notebook 擴展,新建一個名爲 test.ipynb 的文件,跟着教程一步步走下去。編碼

圖片

Windows環境下打開 Cmd (開始-運行-CMD),蘋果系統環境下請打開 Terminal (command+空格輸入Terminal),準備開始輸入命令安裝依賴。spa

所需依賴:3d

`pip install numpy`
`pip install pandas`
`pip install plotly`
`pip install scikit-learn`
`pip install scikit-plot`
`# 最後模型預測須要用到,安裝須要conda`
`# 若是隻是想探索性分析數據,能夠不導入 imblearn`
`conda install -c conda-forge imbalanced-learn`

3.1 導入須要的模塊

本文比較長,涉及到的模塊比較多,若是隻是想探索性分析數據,能夠不導入 imblearn。

`import numpy as np # linear algebra`
`import pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)`
`import matplotlib.pyplot as plt`
`import seaborn as sns`
`import plotly.express as ex`
`import plotly.graph_objs as go`
`import plotly.figure_factory as ff`
`from plotly.subplots import make_subplots`
`import plotly.offline as pyo`
`pyo.init_notebook_mode()`
`sns.set_style('darkgrid')`
`from sklearn.decomposition import PCA`
`from sklearn.model_selection import train_test_split,cross_val_score`
`from sklearn.ensemble import RandomForestClassifier,AdaBoostClassifier`
`from sklearn.svm import SVC`
`from sklearn.pipeline import Pipeline`
`from sklearn.preprocessing import StandardScaler`
`from sklearn.metrics import f1_score as f1`
`from sklearn.metrics import confusion_matrix`
`import scikitplot as skplt`
`plt.rc('figure',figsize=(18,9))`
`%pip install imbalanced-learn`
`from imblearn.over_sampling import SMOTE`

遇到任何 No module named "XXX" 均可以嘗試pip install一下。

若是pip install沒解決,能夠谷歌/百度一下,看看別人是怎麼解決的。

3.2 加載數據

`c_data = pd.read_csv('./BankChurners.csv')`
`c_data = c_data[c_data.columns[:-2]]`
`c_data.head(3)`

這裏去掉了最後兩列的樸素貝葉斯分類結果。

顯示前三行數據, 能夠看到全部的字段:

3.3 探索性數據分析

下面看看這20+列數據中,哪一些是對咱們有用的。

首先,我想知道數據集中的客戶年齡分佈:

`fig = make_subplots(rows=2, cols=1)`
`tr1=go.Box(x=c_data['Customer_Age'],name='Age Box Plot',boxmean=True)`
`tr2=go.Histogram(x=c_data['Customer_Age'],name='Age Histogram')`
`fig.add_trace(tr1,row=1,col=1)`
`fig.add_trace(tr2,row=2,col=1)`
`fig.update_layout(height=700, width=1200, title_text="Distribution of Customer Ages")`
`fig.show()`

能夠看到,客戶的年齡分佈大體遵循正態分佈,所以使用能夠在正態假設下進一步使用年齡特徵。

一樣滴,我想知道性別分佈如何:

ex.pie(c_data,names='Gender',title='Propotion Of Customer Genders')

可見,在咱們的數據集中,女性的樣本比男性更多,可是差別的百分比不是那麼顯著,因此咱們能夠說性別是均勻分佈的。

每一個客戶的家庭人數的分佈怎麼樣?

`fig = make_subplots(rows=2, cols=1)`
`tr1=go.Box(x=c_data['Dependent_count'],name='Dependent count Box Plot',boxmean=True)`
`tr2=go.Histogram(x=c_data['Dependent_count'],name='Dependent count Histogram')`
`fig.add_trace(tr1,row=1,col=1)`
`fig.add_trace(tr2,row=2,col=1)`
`fig.update_layout(height=700, width=1200, title_text="Distribution of Dependent counts (close family size)")`
`fig.show()`

它也大體符合正態分佈,偏右一點,或許後續分析能用得上。

客戶的受教育水平如何?

ex.pie(c_data,names='Education_Level',title='Propotion Of Education Levels')

假設大多數教育程度不明(Unknown)的顧客都沒有接受過任何教育。咱們能夠指出,超過70%的顧客都受過正規教育,其中約35%的人受教育程度達到碩士以上水平,45%的人達到本科以上水準。

他們的婚姻狀態如何?

ex.pie(c_data,names='Marital_Status',title='Propotion Of Different Marriage Statuses')

看來,這家銀行幾乎一半的客戶都是已婚人士,有趣的是,另外一半客戶幾乎都是單身人士,另外只有7%的客戶離婚了。

看看收入分佈和卡片類型的分佈:

ex.pie(c_data,names='Income_Category',title='Propotion Of Different Income Levels')

ex.pie(c_data,names='Card_Category',title='Propotion Of Different Card Categories')

可見大部分人的年收入處於60K美圓如下。

在持有的卡片的類型上,藍卡佔了絕大多數。

每個月帳單數量有沒有特徵?

`fig = make_subplots(rows=2, cols=1)`
`tr1=go.Box(x=c_data['Months_on_book'],name='Months on book Box Plot',boxmean=True)`
`tr2=go.Histogram(x=c_data['Months_on_book'],name='Months on book Histogram')`
`fig.add_trace(tr1,row=1,col=1)`
`fig.add_trace(tr2,row=2,col=1)`
`fig.update_layout(height=700, width=1200, title_text="Distribution of months the customer is part of the bank")`
`fig.show()`

能夠看到中間的峯值特別高,顯然這個指標不是正態分佈的。

每位客戶持有的銀行業務數量有沒有特徵呢?

`fig = make_subplots(rows=2, cols=1)`
`tr1=go.Box(x=c_data['Total_Relationship_Count'],name='Total no. of products Box Plot',boxmean=True)`
`tr2=go.Histogram(x=c_data['Total_Relationship_Count'],name='Total no. of products Histogram')`
`fig.add_trace(tr1,row=1,col=1)`
`fig.add_trace(tr2,row=2,col=1)`
`fig.update_layout(height=700, width=1200, title_text="Distribution of Total no. of products held by the customer")`
`fig.show()`

基本上都是均勻分佈的,顯然這個指標對於咱們而言也沒太大意義。

用戶不活躍月份數量是否是好用的特徵?

`fig = make_subplots(rows=2, cols=1)`
`tr1=go.Box(x=c_data['Months_Inactive_12_mon'],name='number of months inactive Box Plot',boxmean=True)`
`tr2=go.Histogram(x=c_data['Months_Inactive_12_mon'],name='number of months inactive Histogram')`
`fig.add_trace(tr1,row=1,col=1)`
`fig.add_trace(tr2,row=2,col=1)`
`fig.update_layout(height=700, width=1200, title_text="Distribution of the number of months inactive in the last 12 months")`
`fig.show()`

這個彷佛有點用處,會不會越不活躍的用戶越容易流失呢?咱們先日後看。

信用卡額度的分佈如何?

`fig = make_subplots(rows=2, cols=1)`
`tr1=go.Box(x=c_data['Credit_Limit'],name='Credit_Limit Box Plot',boxmean=True)`
`tr2=go.Histogram(x=c_data['Credit_Limit'],name='Credit_Limit Histogram')`
`fig.add_trace(tr1,row=1,col=1)`
`fig.add_trace(tr2,row=2,col=1)`
`fig.update_layout(height=700, width=1200, title_text="Distribution of the Credit Limit")`
`fig.show()`

大部分人的額度都在0到10k之間,這比較正常,暫時看不出和流失有什麼關係。

客戶總交易額的分佈怎麼樣?

`fig = make_subplots(rows=2, cols=1)`
`tr1=go.Box(x=c_data['Total_Trans_Amt'],name='Total_Trans_Amt Box Plot',boxmean=True)`
`tr2=go.Histogram(x=c_data['Total_Trans_Amt'],name='Total_Trans_Amt Histogram')`
`fig.add_trace(tr1,row=1,col=1)`
`fig.add_trace(tr2,row=2,col=1)`
`fig.update_layout(height=700, width=1200, title_text="Distribution of the Total Transaction Amount (Last 12 months)")`
`fig.show()`

這個有點意思,總交易額的分佈體現出「多組」分佈,若是咱們根據這個指標將客戶聚類爲不一樣的組別,看他們之間的類似性,並做出不一樣的畫線,也許對咱們最終的流失分析有必定的意義。

接下來,最重要的流失用戶分佈

ex.pie(c_data,names='Attrition_Flag',title='Proportion of churn vs not churn customers')

咱們能夠看到,只有16%的數據樣本表明流失客戶,在接下來的步驟中,我將使用SMOTE對流失樣本進行採樣,使其與常規客戶的樣本大小匹配,以便給後面選擇的模型一個更好的機會來捕捉小細節。

3.4 數據預處理

使用SMOTE模型前,須要根據不一樣的特徵對數據進行One Hot編碼:

`c_data.Attrition_Flag = c_data.Attrition_Flag.replace({'Attrited Customer':1,'Existing Customer':0})`
`c_data.Gender = c_data.Gender.replace({'F':1,'M':0})`
`c_data = pd.concat([c_data,pd.get_dummies(c_data['Education_Level']).drop(columns=['Unknown'])],axis=1)`
`c_data = pd.concat([c_data,pd.get_dummies(c_data['Income_Category']).drop(columns=['Unknown'])],axis=1)`
`c_data = pd.concat([c_data,pd.get_dummies(c_data['Marital_Status']).drop(columns=['Unknown'])],axis=1)`
`c_data = pd.concat([c_data,pd.get_dummies(c_data['Card_Category']).drop(columns=['Platinum'])],axis=1)`
`c_data.drop(columns = ['Education_Level','Income_Category','Marital_Status','Card_Category','CLIENTNUM'],inplace=True)`

顯示熱力圖:

sns.heatmap(c_data.corr('pearson'),annot=True)

3.5 SMOTE模型採樣

SMOTE模型常常用於解決數據不平衡的問題,它經過添加生成的少數類樣本改變不平衡數據集的數據分佈,是改善不平衡數據分類模型性能的流行方法之一。

`oversample = SMOTE()`
`X, y = oversample.fit_resample(c_data[c_data.columns[1:]], c_data[c_data.columns[0]])`
`usampled_df = X.assign(Churn = y)`
`ohe_data =usampled_df[usampled_df.columns[15:-1]].copy()`
`usampled_df = usampled_df.drop(columns=usampled_df.columns[15:-1])`
`sns.heatmap(usampled_df.corr('pearson'),annot=True)`

3.6 主成分分析

咱們將使用主成分分析來下降單次編碼分類變量的維數,從而下降方差。同時使用幾個主成分而不是幾十個單次編碼特徵將幫助我構建一個更好的模型。

`N_COMPONENTS = 4`
`pca_model = PCA(n_components = N_COMPONENTS )`
`pc_matrix = pca_model.fit_transform(ohe_data)`
`evr = pca_model.explained_variance_ratio_`
`cumsum_evr = np.cumsum(evr)`
`ax = sns.lineplot(x=np.arange(0,len(cumsum_evr)),y=cumsum_evr,label='Explained Variance Ratio')`
`ax.set_title('Explained Variance Ratio Using {} Components'.format(N_COMPONENTS))`
`ax = sns.lineplot(x=np.arange(0,len(cumsum_evr)),y=evr,label='Explained Variance Of Component X')`
`ax.set_xticks([i for i in range(0,len(cumsum_evr))])`
`ax.set_xlabel('Component number #')`
`ax.set_ylabel('Explained Variance')`
`plt.show()`

`usampled_df_with_pcs = pd.concat([usampled_df,pd.DataFrame(pc_matrix,columns=['PC-{}'.format(i) for i in range(0,N_COMPONENTS)])],axis=1)`
`usampled_df_with_pcs`

特徵變得愈來愈明顯:

sns.heatmap(usampled_df_with_pcs.corr('pearson'),annot=True)

4.模型選擇及測試

選擇出如下特徵劃分訓練集並進行訓練:

`X_features = ['Total_Trans_Ct','PC-3','PC-1','PC-0','PC-2','Total_Ct_Chng_Q4_Q1','Total_Relationship_Count']`
`X = usampled_df_with_pcs[X_features]`
`y = usampled_df_with_pcs['Churn']`
`train_x,test_x,train_y,test_y = train_test_split(X,y,random_state=42)`

4.1 交叉驗證

分別看看隨機森林、AdaBoost和SVM模型三種模型的表現如何:

`rf_pipe = Pipeline(steps =[ ('scale',StandardScaler()), ("RF",RandomForestClassifier(random_state=42)) ])`
`ada_pipe = Pipeline(steps =[ ('scale',StandardScaler()), ("RF",AdaBoostClassifier(random_state=42,learning_rate=0.7)) ])`
`svm_pipe = Pipeline(steps =[ ('scale',StandardScaler()), ("RF",SVC(random_state=42,kernel='rbf')) ])`
`f1_cross_val_scores = cross_val_score(rf_pipe,train_x,train_y,cv=5,scoring='f1')`
`ada_f1_cross_val_scores=cross_val_score(ada_pipe,train_x,train_y,cv=5,scoring='f1')`
`svm_f1_cross_val_scores=cross_val_score(svm_pipe,train_x,train_y,cv=5,scoring='f1')`
`plt.subplot(3,1,1)`
`ax = sns.lineplot(x=range(0,len(f1_cross_val_scores)),y=f1_cross_val_scores)`
`ax.set_title('Random Forest Cross Val Scores')`
`ax.set_xticks([i for i in range(0,len(f1_cross_val_scores))])`
`ax.set_xlabel('Fold Number')`
`ax.set_ylabel('F1 Score')`
`plt.show()`
`plt.subplot(3,1,2)`
`ax = sns.lineplot(x=range(0,len(ada_f1_cross_val_scores)),y=ada_f1_cross_val_scores)`
`ax.set_title('Adaboost Cross Val Scores')`
`ax.set_xticks([i for i in range(0,len(ada_f1_cross_val_scores))])`
`ax.set_xlabel('Fold Number')`
`ax.set_ylabel('F1 Score')`
`plt.show()`
`plt.subplot(3,1,3)`
`ax = sns.lineplot(x=range(0,len(svm_f1_cross_val_scores)),y=svm_f1_cross_val_scores)`
`ax.set_title('SVM Cross Val Scores')`
`ax.set_xticks([i for i in range(0,len(svm_f1_cross_val_scores))])`
`ax.set_xlabel('Fold Number')`
`ax.set_ylabel('F1 Score')`
`plt.show()`

看看三種模型都有什麼不一樣的表現:

看得出來隨機森林 F1分數是最高的,達到了0.92。

4.2 模型預測

對測試集進行預測,看看三種模型的效果:

`rf_pipe.fit(train_x,train_y)`
`rf_prediction = rf_pipe.predict(test_x)`
`ada_pipe.fit(train_x,train_y)`
`ada_prediction = ada_pipe.predict(test_x)`
`svm_pipe.fit(train_x,train_y)`
`svm_prediction = svm_pipe.predict(test_x)`
`print('F1 Score of Random Forest Model On Test Set - {}'.format(f1(rf_prediction,test_y)))`
`print('F1 Score of AdaBoost Model On Test Set - {}'.format(f1(ada_prediction,test_y)))`
`print('F1 Score of SVM Model On Test Set - {}'.format(f1(svm_prediction,test_y)))`

4.3 對原始數據(採樣前)進行模型預測

接下來對原始數據進行模型預測:

`ohe_data =c_data[c_data.columns[16:]].copy()`
`pc_matrix = pca_model.fit_transform(ohe_data)`
`original_df_with_pcs = pd.concat([c_data,pd.DataFrame(pc_matrix,columns=['PC-{}'.format(i) for i in range(0,N_COMPONENTS)])],axis=1)`
`unsampled_data_prediction_RF = rf_pipe.predict(original_df_with_pcs[X_features])`
`unsampled_data_prediction_ADA = ada_pipe.predict(original_df_with_pcs[X_features])`
`unsampled_data_prediction_SVM = svm_pipe.predict(original_df_with_pcs[X_features])`

效果以下:

F1最高的隨機森林模型有0.63分,偏低,這也比較正常,畢竟在這種分佈不均的數據集中,查全率是比較難拿到高分數的。

4.4 結果

讓咱們看看最終在原數據上使用隨機森林模型的運行結果:

`ax = sns.heatmap(confusion_matrix(unsampled_data_prediction_RF,original_df_with_pcs['Attrition_Flag']),annot=True,cmap='coolwarm',fmt='d')`
`ax.set_title('Prediction On Original Data With Random Forest Model Confusion Matrix')`
`ax.set_xticklabels(['Not Churn','Churn'],fontsize=18)`
`ax.set_yticklabels(['Predicted Not Churn','Predicted Churn'],fontsize=18)`
`plt.show()`

可見,沒有流失的客戶命中了7709人,未命中791人。

流失客戶命中了1130人,未命中497人。

總體而言,是一個比較優秀的模型了。

咱們的文章到此就結束啦,若是你喜歡今天的Python 實戰教程,請持續關注Python實用寶典。

有任何問題,能夠在公衆號後臺回覆:加羣,回答相應紅字驗證信息,進入互助羣詢問。

原創不易,但願你能在下面點個贊和在看支持我繼續創做,謝謝!

點擊下方閱讀原文可得到更好的閱讀體驗

Python實用寶典 (pythondict.com)
不僅是一個寶典
歡迎關注公衆號:Python實用寶典

相關文章
相關標籤/搜索