images
10/11/2020 08:41 am

Gấu Mèo đi làm Data Scientist - Phần 12: Customer Churn Prediction: Never let me go - Part 2

Sau khi nói về lý thuyết chung trong part 1, giờ sẽ là lúc chúng ta lấm bùn: implement quy trình đấy bằng python

 

import pandas as pd

import matplotlib.pyplot as plt

import seaborn as sns

1. Exploratory Data


Dữ liệu được lưu ở file Telco-Customer-Churn.csv. Pandas là thư viện được sử dụng rất nhiều trong việc load và transform dữ liệu.

1.1 Load data

In [2]:

telco = pd.read_csv('Telco-Customer-Churn.csv')

In [3]:

print(telco['Churn'].value_counts())

No     5174

Yes    1869

Name: Churn, dtype: int64

 

 

Xem nhanh về dữ liệu

In [5]:

telco.info()

<class 'pandas.core.frame.DataFrame'>

RangeIndex: 7043 entries, 0 to 7042

Data columns (total 21 columns):

customerID          7043 non-null object

gender              7043 non-null object

SeniorCitizen       7043 non-null int64

Partner             7043 non-null object

Dependents          7043 non-null object

tenure              7043 non-null int64

PhoneService        7043 non-null object

MultipleLines       7043 non-null object

InternetService     7043 non-null object

OnlineSecurity      7043 non-null object

OnlineBackup        7043 non-null object

DeviceProtection    7043 non-null object

TechSupport         7043 non-null object

StreamingTV         7043 non-null object

StreamingMovies     7043 non-null object

Contract            7043 non-null object

PaperlessBilling    7043 non-null object

PaymentMethod       7043 non-null object

MonthlyCharges      7043 non-null float64

TotalCharges        7043 non-null object

Churn               7043 non-null object

dtypes: float64(1), int64(2), object(18)

memory usage: 1.1+ MB

 

In [6]:

telco.hist(bins=100)

Out[6]:

array([[<matplotlib.axes._subplots.AxesSubplot object at 0x1148483c8>,

        <matplotlib.axes._subplots.AxesSubplot object at 0x1024df978>],

       [<matplotlib.axes._subplots.AxesSubplot object at 0x102511048>,

        <matplotlib.axes._subplots.AxesSubplot object at 0x1025386d8>]],

      dtype=object)

Trường churn cho biết khách hàng đã rời bỏ hay chưa. Mục đích của bài toán Churn Prediction là dựa vào dữ liệu của các khách hàng cũ (Đã biết Churn hay Non-Churn) đưa ra một hàm gần đúng. Với hàm gần đúng đấy chúng ta có thể biết được khách hàng mới có xu hướng Churn hay không ?

In [7]:

print(telco['Churn'].value_counts())

No     5174

Yes    1869

Name: Churn, dtype: int64

 

Có 1869 khách hàng rời bỏ

 

Số lượng khách hàng rời bỏ khá lớn. Khi đối mặt với vấn đề khách hàng rời bỏ chúng ta sẽ có một vào câu hỏi để tìm hiểu nguyên nhân mà khách hàng rời bỏ. Ví dụ như:

 

- Liệu khách hàng rời bỏ có gọi thường xuyên gọi tới tổng đài chăm sóc hơn không ?

 

- Có vùng nào mà khách hàng rời bỏ vượt trội hơn chỗ khác hay ko? ... Để trả lời các câu hỏi thế này chúng ta cần thống kê lại theo nhóm. Pandas - một thư viện của python - có hàm giúp chúng ta làm điều này dễ dàng Method: groupby()

1.2 Grouping and summarizing data

In [8]:

print(telco.groupby(['Churn']).mean())

      SeniorCitizen     tenure  MonthlyCharges

Churn                                          

No          0.128721  37.569965       61.265124

Yes         0.254682  17.979133       74.441332

 

Churn by Gender

In [9]:

telco.groupby('gender')['Churn'].value_counts()

Out[9]:

gender  Churn

Female  No       2549

        Yes       939

Male    No       2625

        Yes       930

Name: Churn, dtype: int64

Churn by Contract

In [10]:

telco.groupby('Contract')['Churn'].value_counts()

Out[10]:

Contract        Churn

Month-to-month  No       2220

                Yes      1655

One year        No       1307

                Yes       166

Two year        No       1647

                Yes        48

Name: Churn, dtype: int64

Churn by PaymentMethod

In [11]:

telco.groupby('PaymentMethod')['Churn'].value_counts()

Out[11]:

PaymentMethod              Churn

Bank transfer (automatic)  No       1286

                           Yes       258

Credit card (automatic)    No       1290

                           Yes       232

Electronic check           No       1294

                           Yes      1071

Mailed check               No       1304

                           Yes       308

Name: Churn, dtype: int64

1.3 Exploring data using visualization

Ở phần này chúng ta sẽ sử dụng thư viện seaborn để visualize dữ liệu. Việc visualize đúng cách giúp cho việc tìm ra insight, giải thích một kết luận dễ dàng hơn. Nó góp phần tìm ra tiếng nói chung giữa team kĩ thuật và stakeholder

Việc hiểu được distribution của dữ liệu rất quan trọng. Nó giúp chúng ta có overview về dữ liệu, cũng như nhìn ra một số sai sót về dữ liệu. Việc histogram là một cách visualize dữ liệu rất hiệu quả

Hàm distribution trong statistic là hàm cho biết các giá trị có thể xảy ra của một biến (một trường) và tần suất xảy ra của giá trị đấy

Histogram là một dạng biểu đồ thể hiện tần suất dạng cột. Nó cho thấy hình thái phân bố của dữ liệu, trả lời được cho các câu hỏi

  • Kiểu phân bố dữ liệu?

  • Độ rộng dữ liệu như thế nào?

  • Dữ liệu có đối xứng hay không?

  • Có dữ liệu nào nằm ngoài hay không?

In [12]:

sns.distplot(telco['MonthlyCharges'])

plt.show()


 

In [13]:

sns.boxplot(x='Churn', y='MonthlyCharges', data = telco)

Out[13]:

<matplotlib.axes._subplots.AxesSubplot at 0x114d97e48>

In [14]:

sns.boxplot(x='Churn', y='tenure', data = telco, sym='')

Out[14]:

<matplotlib.axes._subplots.AxesSubplot at 0x114ed5160>

  • Thêm trường thứ 3 *

In [15]:

sns.boxplot(x='Churn', y='tenure', data = telco, hue='gender')

Out[15]:

<matplotlib.axes._subplots.AxesSubplot at 0x11981e908>

2. Preprocessing

Trong thực tế có rất nhiều machine learning model được dựng lên dựa trên các giả thuyết về sự phân bố của dữ liệu. Ví dụ như:

- Các thuộc tính được phân phối theo phân phối chuẩn

- Các thuộc tính có cùng một scale

 

Nếu như dữ liệu thực tế không theo các giả định đấy thì model được tạo ra không đáng tin cậy Bởi thế nên quá trình preprocessing rất quan trọng

Nhiều thuật toán machine learning chỉ chấp nhận đầu vào là dữ liệu dạng số, do đó dữ liệu dạng category cần được chuyển thành dạng số Hàm dtype sẽ cho chúng ta biết về kiểu của các trường đầu vào

In [16]:

telco.dtypes

Out[16]:

customerID           object

gender               object

SeniorCitizen         int64

Partner              object

Dependents           object

tenure                int64

PhoneService         object

MultipleLines        object

InternetService      object

OnlineSecurity       object

OnlineBackup         object

DeviceProtection     object

TechSupport          object

StreamingTV          object

StreamingMovies      object

Contract             object

PaperlessBilling     object

PaymentMethod        object

MonthlyCharges      float64

TotalCharges         object

Churn                object

dtype: object

2.1 Data preprocess

2.1.1 LabelEncoder

In [17]:

telco['gender'].head()

Out[17]:

0    Female

1      Male

2      Male

3      Male

4    Female

Name: gender, dtype: object

Với dạng này chúng ta có thể có hai cách dùng:

  1. Dùng hàm replace
    telco['gender'].replace({'Male': 1, 'Femaile': 0})

  2. Dùng thư viện
    from sklearn.preprocessing import LabelEncoder LabelEncoder().fit_transform(telco['gender'])

In [18]:

from sklearn.preprocessing import LabelEncoder

telco['gender'] = LabelEncoder().fit_transform(telco['gender'])

telco['Partner'] = LabelEncoder().fit_transform(telco['Partner'])

telco['Dependents'] = LabelEncoder().fit_transform(telco['Dependents'])

telco['PhoneService'] = LabelEncoder().fit_transform(telco['PhoneService'])

telco['MultipleLines'] = LabelEncoder().fit_transform(telco['MultipleLines'])

telco['InternetService'] = LabelEncoder().fit_transform(telco['InternetService'])

telco['OnlineSecurity'] = LabelEncoder().fit_transform(telco['OnlineSecurity'])

telco['OnlineBackup'] = LabelEncoder().fit_transform(telco['OnlineBackup'])

telco['DeviceProtection'] = LabelEncoder().fit_transform(telco['DeviceProtection'])

telco['TechSupport'] = LabelEncoder().fit_transform(telco['TechSupport'])

telco['StreamingTV'] = LabelEncoder().fit_transform(telco['StreamingTV'])

telco['StreamingMovies'] = LabelEncoder().fit_transform(telco['StreamingMovies'])

telco['Contract'] = LabelEncoder().fit_transform(telco['Contract'])

telco['PaperlessBilling'] = LabelEncoder().fit_transform(telco['PaperlessBilling'])

telco['PaymentMethod'] = LabelEncoder().fit_transform(telco['PaymentMethod'])

telco['TotalCharges'] = LabelEncoder().fit_transform(telco['TotalCharges'])

telco['Churn'] = LabelEncoder().fit_transform(telco['Churn'])

In [19]:

2.1.2 OneHotEncoder


Với dạng category có nhiều value, sẽ hợp lý hơn nếu chúng ta sử dụng OneHotEncoder

In [20]:

ids = [11, 22, 33, 44, 55, 66, 77]

countries = ['Spain', 'France', 'Spain', 'Germany', 'France']

df = pd.DataFrame(list(zip(ids, countries)),

                 columns=['Ids', 'Countries'])

df.head()

Out[20]:


Ids

Countries

0

11

Spain

1

22

France

2

33

Spain

3

44

Germany

4

55

France

In [21]:

y = pd.get_dummies(df.Countries, prefix='Country')

print(y.head())

  Country_France  Country_Germany  Country_Spain

0               0                0              1

1               1                0              0

2               0                0              1

3               0                1              0

4               1                0              0

 

In [22]:

from sklearn.preprocessing import LabelBinarizer

y = LabelBinarizer().fit_transform(df.Countries)

y

Out[22]:

array([[0, 0, 1],

       [1, 0, 0],

       [0, 0, 1],

       [0, 1, 0],

       [1, 0, 0]])

In [23]:

from sklearn.preprocessing import OneHotEncoder

x = [[11, "Spain"], [22, "France"], [33, "Spain"], [44, "Germany"], [55, "France"]]

y = OneHotEncoder().fit_transform(x)

print(y)

 (0, 0) 1.0

  (0, 7) 1.0

  (1, 1) 1.0

  (1, 5) 1.0

  (2, 2) 1.0

  (2, 7) 1.0

  (3, 3) 1.0

  (3, 6) 1.0

  (4, 4) 1.0

  (4, 5) 1.0

 

2.1.3 Feather scaling

Standardization

  • Tập trung dữ liệu quanh mean

  • Tính Standardizationn tính từ mean trên từng data point

In [24]:

In [25]:

from sklearn.preprocessing import StandardScaler

df = StandardScaler().fit_transform(telco[['SeniorCitizen', 'tenure']])

In [26]:

2.2 Feature Selection and engineering

2.2.1 Drop unnecessary feature

In [27]:

telco = telco.drop(['DeviceProtection'], axis=1)

2.2.2 Drop correlated feature

In [28]:

telco.corr()

 

Nhận xét: Các trường tương đối độc lập, chính vì vậy trong bước này chúng ta sẽ không xoá đi feature nào cả

2.2.3 Engineering new column


Tạo feather mới có thể tăng hiệu năng của model Nên kết hợp với bên biz, những người hiểu sâu về domain, để nhìn nhận ra được tính năng Dữ liệu trong bài không có trường nào có thể kết hợp với nhau, nhưng nếu trong biz của bạn có, thì bạn có thể tạo trường mới theo cú pháp đơn giản như sau: telco['new_feature'] = telco['old_feature_1'] * telco['old_feature_2']

3. Making Predictions


Trong phần này chúng ra sẽ sử dụng thư viện scikit learn

3.1 Model Selection


Việc chọn lựa model loại nào để dùng là một quá trình khá đau đầu.

Bài toán Churn Prediction là bài toán dạng Classification, chúng ta có thể bắt đầu với Logistic Regression, Decision Tree, nếu bị overfit có thể thử thêm Random Forest. Bạn cũng có thể thử Support Vector Machine, deeplearning (Đòi hỏi nhiều tài nguyên để chạy hơn) để dự đoán

3.2 Training your model

 

In [30]:

from sklearn.svm import SVC

svc = SVC()

features = ['gender', 'SeniorCitizen', 'Partner', 'Dependents', 'tenure', 'PhoneService', 'MultipleLines', 'InternetService']

svc.fit(telco[features], telco['Churn'])

Out[30]:

 

In [31]:

from sklearn.linear_model import LogisticRegression

from sklearn.svm import SVC

from sklearn.tree import DecisionTreeClassifier

constructors = [LogisticRegression, SVC, DecisionTreeClassifier]

features = ['gender', 'SeniorCitizen', 'Partner', 'Dependents', 'tenure', 'PhoneService', 'MultipleLines', 'InternetService']

clfs = []

for constructor in constructors:

   clf = constructor()

   clf.fit(telco[features], telco['Churn'])

   clfs.append(clf)

clfs

  

 

3.3 Making Predictions


prediction = svc.predict(new_customer) print(prediction)

3.4 Evaluating Model Performance

3.4.1 Creating training and test set


In [34]:

from sklearn.model_selection import train_test_split

 

In [36]:

X_train, X_test, Y_train, Y_test = train_test_split(telco[features], telco['Churn'], test_size=0.2, random_state = 42)

 

In [44]:

clfs = []

for constructor in constructors:

   clf = constructor()

   clf.fit(X_train, Y_train)

   clfs.append(clf)

 

In [50]:

print('Score on test set:')

for clf in clfs:

   score = clf.score(X_test, Y_test)

   print('Score của {} : {}'.format(clf, score))

 

In [51]:

print('Score on train set:')

for clf in clfs:

   score = clf.score(X_train, Y_train)

   print('Score của {} : {}'.format(clf, score))


Score on train set:

Score của LogisticRegression() : 0.7642882499112531

Score của SVC() : 0.7594959176428825

Score của DecisionTreeClassifier() : 0.8734469293574725

3.4.2 Improving model


Có hai vấn đề thường gặp là overfitting và underfitting

Overfitting là hiện tượng model tạo ra quá khớp với dữ liệu dùng để train, và có hiệu quả kém khi đo bằng dữ liệu test (Dẫn tới việc ko hiệu quả trong thực tế)

Underfitting là hiện tượng model dự đoán có độ chính xác thấp

Trước khi improve model, chúng ta phải biết mình đang gặp vấn đề gì để có phương án giải quyết thích hợp:


Còn để giải quyết hai vấn đề này thế nào, hẹn các bạn ở bài sau…


Mời các bạn xem lại bài Phần 12: Customer Churn Prediction: Never let me go - Part 1.


- Tech Zone -

Thư giãn chút nào!!!

Bài viết liên quan