當前位置:
首頁 > 知識 > 一文看懂機器學習流程(客戶流失率預測)

一文看懂機器學習流程(客戶流失率預測)

點擊上方「

Python開發

」,選擇「置頂公眾號」


關鍵時刻,第一時間送達!




定義問題


客戶流失率問題是電信運營商面臨得一項重要課題,也是一個較為流行的案例。根據測算,招攬新的客戶比保留住既有客戶的花費大得多(通常5-20倍的差距)。因此,如何保留住現在的客戶對運營商而言是一項非常有意義的事情。 本文希望通過一個公開數據的客戶流失率問題分析,能夠帶著大家理解如何應用機器學習預測演算法到實際應用中。


當然, 實際的場景比本文例子複雜的多,如果想具體應用到項目, 還需要針對不同的場景和數據進行具體的分析。


從機器學習的分類來講, 這是一個監督問題中的分類問題。 具體來說, 是一個二分類問題。 所有的數據中包括一些特徵, 最後就是它的分類:流失或者在網。接下來我們就開始具體的處理。


分析數據


首先我們來導入數據, 然後查看數據的基本情況。


數據導入


通過pandas來導入csv, 然後我們來查看一下數據的基本情況

from

__future__

import

division

import

pandas

as

pd

import

numpy

as

np

ds = pd.read_csv(

"./churn.csv"

)
col_names = ds.columns.tolist()

print

"Column names:"

print

col_names
print(ds.shape)

輸出:

Column

names:
[

"State"

,

"Account Length"

,

"Area Code"

,

"Phone"

,

"Int"l Plan"

,

"VMail Plan"

,

"VMail Message"

,

"Day Mins"

,

"Day Calls"

,

"Day Charge"

,

"Eve Mins"

,

"Eve Calls"

,

"Eve Charge"

,

"Night Mins"

,

"Night Calls"

,

"Night Charge"

,

"Intl Mins"

,

"Intl Calls"

,

"Intl Charge"

,

"CustServ Calls"

,

"Churn?"

]
(

3333

,

21

)

可以看到, 整個數據集有3333條數據, 20個維度, 最後一項是分類。


基本信息以及類型

我們可以列印一些數據, 對數據和取值有一個基本的理解。

peek

=

data

.head(5)

print

(peek)

輸出:

 

State

 Account Length  Area Code     Phone Int

"l Plan VMail Plan  
0     KS             128        415  382-4657         no        yes  
1     OH             107        415  371-7191         no        yes  
2     NJ             137        415  358-1921         no         no  
3     OH              84        408  375-9999        yes         no  
4     OK              75        415  330-6626        yes         no  
   Eve Charge  Night Mins  Night Calls  Night Charge  Intl Mins  Intl Calls  
0        16.78       244.7           91         11.01       10.0           3  
1        16.62       254.4          103         11.45       13.7           3  
2        10.30       162.6          104          7.32       12.2           5  
3         5.26       196.9           89          8.86        6.6           7  
4        12.61       186.9          121          8.41       10.1           3  
   Intl Charge  CustServ Calls  Churn?  
0          2.70               1  False.  
1          3.70               1  False.  
2          3.29               0  False.  
3          1.78               2  False.  
4          2.73               3  False.  


我們可以看到, 數據集有20項特徵,分別是州名, 賬戶長度, 區號, 電話號碼, 國際計劃,語音郵箱, 白天通話分鐘數, 白天電話個數, 白天收費, 晚間通話分鐘數,晚間電話個數, 晚間收費, 夜間通話分鐘數,夜間電話個數, 夜間收費, 國際分鐘數, 國際電話個數, 國際收費, 客服電話數,流失與否.




  • 可以看到這裡面有個人信息,應該可以看到有些信息與流失與否關係不大。 州名, 區號可以指明客戶的位置, 和流失有關係么, 不知道, 具體位置如果不分類, 應該完全沒有關係。 而州名, 也許某個州有了某個強勁的競爭對手? 這也是瞎猜, 暫時意義不大, 刪除。



  • 賬號長度, 電話號碼, 不需要



  • 國際計劃, 語音郵箱。 可能有關係, 先留著吧。



  • 分別統計了白天, 晚間, 夜間的通話分鐘, 電話個數, 收費情況。 這是重要信息保留



  • 客服電話, 客戶打電話投訴多那流失率可能會大。 這個是重要信息保留。



  • 流失與否。 這是分類結果。




然後我們可以看一下數據的類型, 如下:

ds.

info

()

輸出:

RangeIndex

:

3333

entries,

0

to

3332

Data

columns (total

21

columns):

State

           

3333

non-

null

object

Account

Length

   

3333

non-

null

int64

Area

Code

       

3333

non-

null

int64

Phone

           

3333

non-

null

object

Int

"l

Plan

       

3333

non-

null

object

VMail

Plan

       

3333

non-

null

object

VMail

Message

   

3333

non-

null

int64

Day

Mins

         

3333

non-

null

float64

Day

Calls

       

3333

non-

null

int64

Day

Charge

       

3333

non-

null

float64

Eve

Mins

         

3333

non-

null

float64

Eve

Calls

       

3333

non-

null

int64

Eve

Charge

       

3333

non-

null

float64

Night

Mins

       

3333

non-

null

float64

Night

Calls

     

3333

non-

null

int64

Night

Charge

     

3333

non-

null

float64

Intl

Mins

       

3333

non-

null

float64

Intl

Calls

       

3333

non-

null

int64

Intl

Charge

     

3333

non-

null

float64

CustServ

Calls

   

3333

non-

null

int64

Churn

?            

3333

non-

null

object

dtypes: float64(

8

), int64(

8

),

object

(

5

)

memory usage:

546.9

+

KB


看見, 有int, float, object。 對於不是數據型的數據, 後面除非決策樹等演算法, 否則應該會轉化成數據行。 所以我們把churn? 結果轉化, 以及"Int"l Plan","VMail Plan", 這兩個參數只有yes, no 兩種, 所以也進行轉化成01值。


描述性統計


describe() 可以返回具體的結果, 對於每一列。


數量 平均值 標準差 25% 分位 50% 分位數 75% 分位數 最大值 很多時候你可以得到NA的數量和比例。


TODO 對於非數據性的是沒有返回的的

     

Account

Length

   

Area

Code

 

VMail

Message

   

Day

Mins

   

Day

Calls

 

count

    3333

.000000

 3333

.000000

   3333

.000000

 3333

.000000

 3333

.000000

 

mean

      101

.064806

  437

.182418

      8

.099010

  179

.775098

  100

.435644

 

std

        39

.822106

   42

.371290

     13

.688365

   54

.467389

   20

.069084

 

min

         1

.000000

  408

.000000

      0

.000000

    0

.000000

    0

.000000

 
25%         74

.000000

  408

.000000

      0

.000000

  143

.700000

   87

.000000

 
50%        101

.000000

  415

.000000

      0

.000000

  179

.400000

  101

.000000

 
75%        127

.000000

  510

.000000

     20

.000000

  216

.400000

  114

.000000

 

max

       243

.000000

  510

.000000

     51

.000000

  350

.800000

  165

.000000

 

       

Day

Charge

   

Eve

Mins

   

Eve

Calls

 

Eve

Charge

 

Night

Mins

 

count

 3333

.000000

 3333

.000000

 3333

.000000

 3333

.000000

 3333

.000000

 

mean

    30

.562307

  200

.980348

  100

.114311

   17

.083540

  200

.872037

 

std

      9

.259435

   50

.713844

   19

.922625

    4

.310668

   50

.573847

 

min

      0

.000000

    0

.000000

    0

.000000

    0

.000000

   23

.200000

 
25%      24

.430000

  166

.600000

   87

.000000

   14

.160000

  167

.000000

 
50%      30

.500000

  201

.400000

  100

.000000

   17

.120000

  201

.200000

 
75%      36

.790000

  235

.300000

  114

.000000

   20

.000000

  235

.300000

 

max

     59

.640000

  363

.700000

  170

.000000

   30

.910000

  395

.000000

 

     

Night

Calls

 

Night

Charge

   

Intl

Mins

 

Intl

Calls

 

Intl

Charge

 

count

 3333

.000000

  3333

.000000

 3333

.000000

 3333

.000000

 3333

.000000

 

mean

   100

.107711

     9

.039325

   10

.237294

    4

.479448

    2

.764581

 

std

     19

.568609

     2

.275873

    2

.791840

    2

.461214

    0

.753773

 

min

     33

.000000

     1

.040000

    0

.000000

    0

.000000

    0

.000000

 
25%      87

.000000

     7

.520000

    8

.500000

    3

.000000

    2

.300000

 
50%     100

.000000

     9

.050000

   10

.300000

    4

.000000

    2

.780000

 
75%     113

.000000

    10

.590000

   12

.100000

    6

.000000

    3

.270000

 

max

    175

.000000

    17

.770000

   20

.000000

   20

.000000

    5

.400000

 

     

CustServ

Calls

 

count

    3333

.000000

 

mean

        1

.562856

 

std

         1

.315491

 

min

         0

.000000

 
25%          1

.000000

 
50%          1

.000000

 
75%          2

.000000

 

max

         9

.000000

 

圖形化理解你的數據


之前的一些信息, 只是一些很初步的理解, 但是對於機器學習演算法來講是不夠的。 下面我們從幾個維度去進一步理解你的數據。工具可以用數字表格, 也可以用圖形(matplotlib) 這裡畫圖較多。




  • 特徵自己的信息



  • 特徵和分類之間的關係



  • 特徵和特徵之間的關係 -- 這裡鑒於時間的關係, 有些關係並沒有直接應用於演算法本身, 但是在進一步的演算法提升中是很有意義的, 這裡更多的是一種展示。




特徵本身的信息


我們先來看一下流失比例, 以及關於打客戶電話的個數分布

import

matplotlib.pyplot

as

plt
%matplotlib inline
fig = plt.figure()
fig.set(alpha=

0.2

)  

# 設定圖表顏色alpha參數

plt.subplot2grid((

2

,

3

),(

0

,

0

))            

# 在一張大圖裡分列幾個小圖

ds[

"Churn?"

].value_counts().plot(kind=

"bar"

)

# plots a bar graph of those who surived vs those who did not.

plt.title(

u"stat for churn"

)

# puts a title on our graph

plt.ylabel(

u"number"

)  

plt.subplot2grid((

2

,

3

),(

0

,

2

))            
ds[

"CustServ Calls"

].value_counts().plot(kind=

"bar"

)

# plots a bar graph of those who surived vs those who did not.

plt.title(

u"stat for cusServCalls"

)

# puts a title on our graph

plt.ylabel(

u"number"

)  

plt.show()


很容易理解。


然後呢, 我們的數據的特點是對白天, 晚上, 夜間,國際都有分鐘數, 電話數, 收費三種維度。 那麼我們拿白天的來舉例。

import

matplotlib.pyplot

as

plt
%matplotlib inline
fig = plt.figure()
fig.set(alpha=

0.2

)  

# 設定圖表顏色alpha參數

plt.subplot2grid((

2

,

5

),(

0

,

0

))            

# 在一張大圖裡分列幾個小圖

ds[

"Day Mins"

].plot(kind=

"kde"

)    

# plots a kernel desnsity estimate of customer

plt.xlabel(

u"Mins"

)

# plots an axis lable

plt.ylabel(

u"density"

)
plt.title(

u"dis for day mins"

)

plt.subplot2grid((

2

,

5

),(

0

,

2

))            
ds[

"Day Calls"

].plot(kind=

"kde"

)    

# plots a kernel desnsity estimate of customer

plt.xlabel(

u"call"

)

# plots an axis lable

plt.ylabel(

u"density"

)
plt.title(

u"dis for day calls"

)

plt.subplot2grid((

2

,

5

),(

0

,

4

))          
ds[

"Day Charge"

].plot(kind=

"kde"

)  

# plots a kernel desnsity estimate of customer

plt.xlabel(

u"Charge"

)

# plots an axis lable

plt.ylabel(

u"density"

)
plt.title(

u"dis for day charge"

)

plt.show()


可以看到分布基本上都是高斯分布, 這也符合我們的預期, 而高斯分布對於我們後續的一些演算法處理是個好消息。


特徵和分類的關聯


我們來看一下一些特徵和分類之間的關聯。 比如下面int plan

import

matplotlib.pyplot

as

plt
fig = plt.figure()
fig.set(alpha=

0.2

)  

# 設定圖表顏色alpha參數

int_yes = ds[

"Churn?"

][ds[

"Int"l Plan"

] ==

"yes"

].value_counts()
int_no = ds[

"Churn?"

][ds[

"Int"l Plan"

] ==

"no"

].value_counts()
df_int=pd.DataFrame({

u"int plan"

:int_yes,

u"no int plan"

:int_no})
df_int.plot(kind=

"bar"

, stacked=

True

)
plt.title(

u"statistic between int plan and churn"

)
plt.xlabel(

u"int or not"

)
plt.ylabel(

u"number"

)

plt.show()


我們可以看到, 有國際電話的流失率較高。 猜測也許他們有更多的選擇, 或者對服務有更多的要求。 需要特別對待。 也許你需要電話多收集一下意見了。


再來看一下

#查看客戶服務電話和結果的關聯

fig = plt.figure()
fig.set(alpha=

0.2

)  

# 設定圖表顏色alpha參數

cus_0 = ds[

"CustServ Calls"

][ds[

"Churn?"

] ==

"False."

].value_counts()
cus_1 = ds[

"CustServ Calls"

][ds[

"Churn?"

] ==

"True."

].value_counts()
df=pd.DataFrame({

u"churn"

:cus_1,

u"retain"

:cus_0})
df.plot(kind=

"bar"

, stacked=

True

)
plt.title(

u"Static between customer service call and churn"

)
plt.xlabel(

u"Call service"

)
plt.ylabel(

u"Num"

)

plt.show()


基本上可以看出, 打客戶電話的多少和最終的分類是強相關的, 打電話3次以上的流失率比例急速升高。 這是一個非常關鍵的指標。


準備數據


好的, 我們已經看了很多,對數據有了一定的理解。 下面我們開始具體對數據進行操作。


去除無關列


首先, 根據對問題的分析, 我們做第一件事情, 去除三列無關列。 州名, 電話, 區號。




  • 我們和下一步一起做


轉化成數值類型


對於有些特徵, 本身不是數值類型的, 這些數據是不能被演算法直接使用的, 所以我們來處理一下

# Isolate target data
ds_result = ds[

"Churn

?"]
Y = np.

where

(ds_result ==

"True

.",

1

,

0

)

dummies_int = pd.get_dummies(ds[

"Int

"l

Plan"], prefix=

"_int

"l

Plan")
dummies_voice = pd.get_dummies(ds[

"VMail

Plan"], prefix=

"VMail

")

ds_tmp=pd.concat([ds, dummies_int, dummies_voice], axis=

1

)

# We don

"t

need these columns
to_drop = [

"State

","

Area Code

","

Phone

","

Churn?",

"Int

"l

Plan",

"VMail

Plan"]
df = ds_tmp.

drop

(to_drop,axis=

1

)

print

"after convert "

print df.head(

5

)

輸出:

after convert

01


  Account Length  VMail Message  Day Mins  Day Calls  Day Charge  Eve Mins  

0

           

128

           

25

   

265.1

       

110

     

45.07

   

197.4

 

1

           

107

           

26

   

161.6

       

123

     

27.47

   

195.5

 

2

           

137

             

0

   

243.4

       

114

     

41.38

   

121.2

 

3

             

84

             

0

   

299.4

       

71

     

50.90

     

61.9

 

4

             

75

             

0

   

166.7

       

113

     

28.34

   

148.3

 

  Eve Calls  Eve Charge  Night Mins  Night Calls  Night Charge  

Intl

Mins  

0

       

99

     

16.78

     

244.7

         

91

       

11.01

     

10.0

 

1

       

103

     

16.62

     

254.4

         

103

       

11.45

     

13.7

 

2

       

110

     

10.30

     

162.6

         

104

         

7.32

     

12.2

 

3

       

88

       

5.26

     

196.9

         

89

         

8.86

       

6.6

 

4

       

122

     

12.61

     

186.9

         

121

         

8.41

     

10.1

 

 

Intl

Calls  

Intl

Charge  CustServ Calls  _int

"l Plan_no  _int"

l Plan_yes  

0

         

3

       

2.70

             

1

             

1

               

0

 

1

         

3

       

3.70

             

1

             

1

               

0

 

2

         

5

       

3.29

             

0

             

1

               

0

 

3

         

7

       

1.78

             

2

             

0

               

1

 

4

         

3

       

2.73

             

3

             

0

               

1

 

  VMail_no  VMail_yes  

0

       

0

         

1

 

1

       

0

         

1

 

2

       

1

         

0

 

3

       

1

         

0

 

4

       

1

         

0


我們可以看到結果, 所有的數據都是數值型的, 而且除去了對我們沒有意義的列。


scale 數據範圍


我們需要做一些scale的工作。 就是有些屬性的scale 太大了。




  • 對於邏輯回歸和梯度下降來說, 個屬性的scale 差距太大, 會對收斂速度有很大的影響。



  • 我們這裡對所有的都做, 其實可以對一些突出的特徵做這種處理。

#scale

X = df.as_matrix().astype(np.float)

# This is important

from

sklearn.preprocessing

import

StandardScaler
scaler = StandardScaler()
X = scaler.fit_transform(X)

print

"Feature space holds %d observations and %d features"

% X.shape

print

"Unique target labels:"

, np.unique(y)

輸出:

Feature space holds

3333

observations

and

19

features
Unique target labels: [

0

1

]

其他的呢, 還可以考慮降維等各種方式。 但是再實際使用中, 我們往往首先做出一個模型, 得到一個參考結果, 然後逐步優化。 所以我們準備數據就到這裡。


評估演算法


我們會使用多個演算法來計算結果, 然後選擇較好的。 如下

# prepare models

models = []
models.

append

((

"LR"

, LogisticRegression()))
models.

append

((

"LDA"

, LinearDiscriminantAnalysis()))
models.

append

((

"KNN"

, KNeighborsClassifier()))
models.

append

((

"CART"

, DecisionTreeClassifier()))
models.

append

((

"NB"

, GaussianNB()))
models.

append

((

"SVM"

, SVC()))

# evaluate each model in turn

results = []
names = []
scoring =

"accuracy"

for

name, model in models:
   kfold = KFold(n_splits=

10

, random_state=

7

)
   cv_results = cross_val_score(model, X, Y, cv=kfold, scoring=scoring)
   results.

append

(cv_results)
   names.

append

(name)
   msg =

"%s: %f (%f)"

% (name, cv_results.mean(), cv_results.std())
   print(msg)

# boxplot algorithm comparison

fig = pyplot.figure()
fig.suptitle(

"Algorithm Comparison"

)
ax = fig.add_subplot(

111

)
pyplot.boxplot(results)
ax.set_xticklabels(names)
pyplot.show()

LR

: 0

.860769

(0

.021660

)

LDA

: 0

.852972

(0

.021163

)

KNN

: 0

.896184

(0

.016646

)

CART

: 0

.920491

(0

.012471

)

NB

: 0

.857179

(0

.015487

)

SVM

: 0

.921091

(0

.016828

)


可以看到什麼呢, 看到SVM 和 CART 效果相對較好。


提升結果


提升的部分, 如何使用提升演算法。 比如隨機森林。 xgboost

from sklearn.ensemble import RandomForestClassifier
num_trees = 100
max_features = 3
kfold = KFold(n_splits=10, random_state=7)
model = RandomForestClassifier(n_estimators=num_trees, max_features=max_features)
results = cross_val_score(model, X, Y, cv=kfold)
print(results.mean())

# 0.954696013379

from sklearn.ensemble import GradientBoostingClassifier
seed = 7
num_trees = 100
kfold = KFold(n_splits=10, random_state=seed)
model = GradientBoostingClassifier(n_estimators=num_trees, random_state=seed)
results = cross_val_score(model, X, Y, cv=kfold)
print(results.mean())

# 0.953197209185


可以看到, 這兩種演算法對單個演算法的提升還是很明顯的。 進一步的, 也可以繼續調整tree的數目, 但是效果應該差不多了。


展示結果


這裡展示了如何保存這個演算法, 以及如何取出然後應用。

#store

from

sklearn.model_selection

import

train_test_split

from

pickle

import

dump

from

pickle

import

load

X_train, X_test, Y_train, Y_test = train_test_split(X, Y, test_size=

0.33

, random_state=

7

)

from

sklearn.ensemble

import

GradientBoostingClassifier
seed =

7

num_trees =

100

kfold = KFold(n_splits=

10

, random_state=seed)
model = GradientBoostingClassifier(n_estimators=num_trees, random_state=seed)
model.fit(X_train, Y_train)

# save the model to disk

filename =

"finalized_model.sav"

dump(model, open(filename,

"wb"

))

# some time later...
# load the model from disk

loaded_model = load(open(filename,

"rb"

))
result = loaded_model.score(X_test, Y_test)
print(result)

後記


本文展示了通過用戶流失率問題, 如何把機器學習的預測過程應用到實際項目中。




  • 從業務的角度, 這個只是一個demo性質的應用, 實際場景可能複雜的多。



  • 從流程的角度, 通過對數據的分析可以進一步提升演算法的性能, 對於某些的特徵, 可以採取不同的處理方式。 比如缺失值的處理, 這裡很完整, 就省去了這個步驟。





  • 來自:開源中國



  • https://my.oschina.net/sizhe/blog/1594791



  • Python開發整理髮布,轉載請聯繫作者獲得授權



【點擊成為程序員大咖】

喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 Python開發 的精彩文章:

TAG:Python開發 |