Created
April 30, 2020 15:57
-
-
Save jeswinaugustine/3dcf1a3500e30bdd5e136b3be444f931 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
== Telecom Churn Case Study | |
With 21 predictor variables we need to predict whether a particular | |
customer will switch to another telecom provider or not. In telecom | |
terminology, this is referred to as churning and not churning, | |
respectively. | |
=== Importing and Merging Data | |
+*In[57]:*+ | |
[source, ipython3] | |
---- | |
# Supress Warnings | |
import warnings | |
warnings.filterwarnings('ignore') | |
---- | |
+*In[1]:*+ | |
[source, ipython3] | |
---- | |
# Importing Pandas and NumPy | |
import pandas as pd | |
import numpy as np | |
---- | |
+*In[2]:*+ | |
[source, ipython3] | |
---- | |
# Importing all datasets | |
churn_data = pd.read_csv("churn_data.csv") | |
customer_data = pd.read_csv("customer_data.csv") | |
internet_data = pd.read_csv("internet_data.csv") | |
---- | |
+*In[3]:*+ | |
[source, ipython3] | |
---- | |
#Merging on 'customerID' | |
df_1 = pd.merge(churn_data, customer_data, how='inner', on='customerID') | |
---- | |
+*In[4]:*+ | |
[source, ipython3] | |
---- | |
#Final dataframe with all predictor variables | |
telecom = pd.merge(df_1, internet_data, how='inner', on='customerID') | |
---- | |
=== Let’s understand the structure of our dataframe | |
+*In[5]:*+ | |
[source, ipython3] | |
---- | |
# Let's see the head of our master dataset | |
telecom.head() | |
---- | |
+*Out[5]:*+ | |
---- | |
[cols=",,,,,,,,,,,,,,,,,,,,,",options="header",] | |
|======================================================================= | |
| |customerID |tenure |PhoneService |Contract |PaperlessBilling | |
|PaymentMethod |MonthlyCharges |TotalCharges |Churn |gender |... | |
|Partner |Dependents |MultipleLines |InternetService |OnlineSecurity | |
|OnlineBackup |DeviceProtection |TechSupport |StreamingTV | |
|StreamingMovies | |
|0 |7590-VHVEG |1 |No |Month-to-month |Yes |Electronic check |29.85 | |
|29.85 |No |Female |... |Yes |No |No phone service |DSL |No |Yes |No |No | |
|No |No | |
|1 |5575-GNVDE |34 |Yes |One year |No |Mailed check |56.95 |1889.5 |No | |
|Male |... |No |No |No |DSL |Yes |No |Yes |No |No |No | |
|2 |3668-QPYBK |2 |Yes |Month-to-month |Yes |Mailed check |53.85 |108.15 | |
|Yes |Male |... |No |No |No |DSL |Yes |Yes |No |No |No |No | |
|3 |7795-CFOCW |45 |No |One year |No |Bank transfer (automatic) |42.30 | |
|1840.75 |No |Male |... |No |No |No phone service |DSL |Yes |No |Yes | |
|Yes |No |No | |
|4 |9237-HQITU |2 |Yes |Month-to-month |Yes |Electronic check |70.70 | |
|151.65 |Yes |Female |... |No |No |No |Fiber optic |No |No |No |No |No | |
|No | |
|======================================================================= | |
5 rows × 21 columns | |
---- | |
+*In[6]:*+ | |
[source, ipython3] | |
---- | |
telecom.describe() | |
---- | |
+*Out[6]:*+ | |
---- | |
[cols=",,,",options="header",] | |
|============================================ | |
| |tenure |MonthlyCharges |SeniorCitizen | |
|count |7043.000000 |7043.000000 |7043.000000 | |
|mean |32.371149 |64.761692 |0.162147 | |
|std |24.559481 |30.090047 |0.368612 | |
|min |0.000000 |18.250000 |0.000000 | |
|25% |9.000000 |35.500000 |0.000000 | |
|50% |29.000000 |70.350000 |0.000000 | |
|75% |55.000000 |89.850000 |0.000000 | |
|max |72.000000 |118.750000 |1.000000 | |
|============================================ | |
---- | |
+*In[7]:*+ | |
[source, ipython3] | |
---- | |
# Let's see the type of each column | |
telecom.info() | |
---- | |
+*Out[7]:*+ | |
---- | |
<class 'pandas.core.frame.DataFrame'> | |
Int64Index: 7043 entries, 0 to 7042 | |
Data columns (total 21 columns): | |
customerID 7043 non-null object | |
tenure 7043 non-null int64 | |
PhoneService 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 | |
gender 7043 non-null object | |
SeniorCitizen 7043 non-null int64 | |
Partner 7043 non-null object | |
Dependents 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 | |
dtypes: float64(1), int64(2), object(18) | |
memory usage: 1.2+ MB | |
---- | |
=== Data Preparation | |
+*In[8]:*+ | |
[source, ipython3] | |
---- | |
# Converting Yes to 1 and No to 0 | |
telecom['PhoneService'] = telecom['PhoneService'].map({'Yes': 1, 'No': 0}) | |
telecom['PaperlessBilling'] = telecom['PaperlessBilling'].map({'Yes': 1, 'No': 0}) | |
telecom['Churn'] = telecom['Churn'].map({'Yes': 1, 'No': 0}) | |
telecom['Partner'] = telecom['Partner'].map({'Yes': 1, 'No': 0}) | |
telecom['Dependents'] = telecom['Dependents'].map({'Yes': 1, 'No': 0}) | |
---- | |
=== Dummy Variable Creation | |
+*In[9]:*+ | |
[source, ipython3] | |
---- | |
# Creating a dummy variable for the variable 'Contract' and dropping the first one. | |
cont = pd.get_dummies(telecom['Contract'],prefix='Contract',drop_first=True) | |
#Adding the results to the master dataframe | |
telecom = pd.concat([telecom,cont],axis=1) | |
# Creating a dummy variable for the variable 'PaymentMethod' and dropping the first one. | |
pm = pd.get_dummies(telecom['PaymentMethod'],prefix='PaymentMethod',drop_first=True) | |
#Adding the results to the master dataframe | |
telecom = pd.concat([telecom,pm],axis=1) | |
# Creating a dummy variable for the variable 'gender' and dropping the first one. | |
gen = pd.get_dummies(telecom['gender'],prefix='gender',drop_first=True) | |
#Adding the results to the master dataframe | |
telecom = pd.concat([telecom,gen],axis=1) | |
# Creating a dummy variable for the variable 'MultipleLines' and dropping the first one. | |
ml = pd.get_dummies(telecom['MultipleLines'],prefix='MultipleLines') | |
# dropping MultipleLines_No phone service column | |
ml1 = ml.drop(['MultipleLines_No phone service'],1) | |
#Adding the results to the master dataframe | |
telecom = pd.concat([telecom,ml1],axis=1) | |
# Creating a dummy variable for the variable 'InternetService' and dropping the first one. | |
iser = pd.get_dummies(telecom['InternetService'],prefix='InternetService',drop_first=True) | |
#Adding the results to the master dataframe | |
telecom = pd.concat([telecom,iser],axis=1) | |
# Creating a dummy variable for the variable 'OnlineSecurity'. | |
os = pd.get_dummies(telecom['OnlineSecurity'],prefix='OnlineSecurity') | |
os1= os.drop(['OnlineSecurity_No internet service'],1) | |
#Adding the results to the master dataframe | |
telecom = pd.concat([telecom,os1],axis=1) | |
# Creating a dummy variable for the variable 'OnlineBackup'. | |
ob =pd.get_dummies(telecom['OnlineBackup'],prefix='OnlineBackup') | |
ob1 =ob.drop(['OnlineBackup_No internet service'],1) | |
#Adding the results to the master dataframe | |
telecom = pd.concat([telecom,ob1],axis=1) | |
# Creating a dummy variable for the variable 'DeviceProtection'. | |
dp =pd.get_dummies(telecom['DeviceProtection'],prefix='DeviceProtection') | |
dp1 = dp.drop(['DeviceProtection_No internet service'],1) | |
#Adding the results to the master dataframe | |
telecom = pd.concat([telecom,dp1],axis=1) | |
# Creating a dummy variable for the variable 'TechSupport'. | |
ts =pd.get_dummies(telecom['TechSupport'],prefix='TechSupport') | |
ts1 = ts.drop(['TechSupport_No internet service'],1) | |
#Adding the results to the master dataframe | |
telecom = pd.concat([telecom,ts1],axis=1) | |
# Creating a dummy variable for the variable 'StreamingTV'. | |
st =pd.get_dummies(telecom['StreamingTV'],prefix='StreamingTV') | |
st1 = st.drop(['StreamingTV_No internet service'],1) | |
#Adding the results to the master dataframe | |
telecom = pd.concat([telecom,st1],axis=1) | |
# Creating a dummy variable for the variable 'StreamingMovies'. | |
sm =pd.get_dummies(telecom['StreamingMovies'],prefix='StreamingMovies') | |
sm1 = sm.drop(['StreamingMovies_No internet service'],1) | |
#Adding the results to the master dataframe | |
telecom = pd.concat([telecom,sm1],axis=1) | |
---- | |
+*In[10]:*+ | |
[source, ipython3] | |
---- | |
#telecom['MultipleLines'].value_counts() | |
---- | |
=== Dropping the repeated variables | |
+*In[11]:*+ | |
[source, ipython3] | |
---- | |
# We have created dummies for the below variables, so we can drop them | |
telecom = telecom.drop(['Contract','PaymentMethod','gender','MultipleLines','InternetService', 'OnlineSecurity', 'OnlineBackup', 'DeviceProtection', | |
'TechSupport', 'StreamingTV', 'StreamingMovies'], 1) | |
---- | |
+*In[21]:*+ | |
[source, ipython3] | |
---- | |
#The varaible was imported as a string we need to convert it to float | |
telecom['TotalCharges'] = pd.to_numeric(telecom['TotalCharges'],errors='coerce') | |
#telecom['tenure'] = telecom['tenure'].astype(int).astype(float) | |
---- | |
+*In[25]:*+ | |
[source, ipython3] | |
---- | |
telecom['TotalCharges'] | |
---- | |
+*Out[25]:*+ | |
----0 29.85 | |
1 1889.50 | |
2 108.15 | |
3 1840.75 | |
4 151.65 | |
... | |
7038 1990.50 | |
7039 7362.90 | |
7040 346.45 | |
7041 306.60 | |
7042 6844.50 | |
Name: TotalCharges, Length: 7043, dtype: float64---- | |
+*In[26]:*+ | |
[source, ipython3] | |
---- | |
telecom.info() | |
---- | |
+*Out[26]:*+ | |
---- | |
<class 'pandas.core.frame.DataFrame'> | |
Int64Index: 7043 entries, 0 to 7042 | |
Data columns (total 32 columns): | |
customerID 7043 non-null object | |
tenure 7043 non-null int64 | |
PhoneService 7043 non-null int64 | |
PaperlessBilling 7043 non-null int64 | |
MonthlyCharges 7043 non-null float64 | |
TotalCharges 7032 non-null float64 | |
Churn 7043 non-null int64 | |
SeniorCitizen 7043 non-null int64 | |
Partner 7043 non-null int64 | |
Dependents 7043 non-null int64 | |
Contract_One year 7043 non-null uint8 | |
Contract_Two year 7043 non-null uint8 | |
PaymentMethod_Credit card (automatic) 7043 non-null uint8 | |
PaymentMethod_Electronic check 7043 non-null uint8 | |
PaymentMethod_Mailed check 7043 non-null uint8 | |
gender_Male 7043 non-null uint8 | |
MultipleLines_No 7043 non-null uint8 | |
MultipleLines_Yes 7043 non-null uint8 | |
InternetService_Fiber optic 7043 non-null uint8 | |
InternetService_No 7043 non-null uint8 | |
OnlineSecurity_No 7043 non-null uint8 | |
OnlineSecurity_Yes 7043 non-null uint8 | |
OnlineBackup_No 7043 non-null uint8 | |
OnlineBackup_Yes 7043 non-null uint8 | |
DeviceProtection_No 7043 non-null uint8 | |
DeviceProtection_Yes 7043 non-null uint8 | |
TechSupport_No 7043 non-null uint8 | |
TechSupport_Yes 7043 non-null uint8 | |
StreamingTV_No 7043 non-null uint8 | |
StreamingTV_Yes 7043 non-null uint8 | |
StreamingMovies_No 7043 non-null uint8 | |
StreamingMovies_Yes 7043 non-null uint8 | |
dtypes: float64(2), int64(7), object(1), uint8(22) | |
memory usage: 756.6+ KB | |
---- | |
Now we can see we have all variables as integer. | |
=== Checking for Outliers | |
+*In[27]:*+ | |
[source, ipython3] | |
---- | |
# Checking for outliers in the continuous variables | |
num_telecom = telecom[['tenure','MonthlyCharges','SeniorCitizen','TotalCharges']] | |
---- | |
+*In[28]:*+ | |
[source, ipython3] | |
---- | |
# Checking outliers at 25%,50%,75%,90%,95% and 99% | |
num_telecom.describe(percentiles=[.25,.5,.75,.90,.95,.99]) | |
---- | |
+*Out[28]:*+ | |
---- | |
[cols=",,,,",options="header",] | |
|========================================================= | |
| |tenure |MonthlyCharges |SeniorCitizen |TotalCharges | |
|count |7043.000000 |7043.000000 |7043.000000 |7032.000000 | |
|mean |32.371149 |64.761692 |0.162147 |2283.300441 | |
|std |24.559481 |30.090047 |0.368612 |2266.771362 | |
|min |0.000000 |18.250000 |0.000000 |18.800000 | |
|25% |9.000000 |35.500000 |0.000000 |401.450000 | |
|50% |29.000000 |70.350000 |0.000000 |1397.475000 | |
|75% |55.000000 |89.850000 |0.000000 |3794.737500 | |
|90% |69.000000 |102.600000 |1.000000 |5976.640000 | |
|95% |72.000000 |107.400000 |1.000000 |6923.590000 | |
|99% |72.000000 |114.729000 |1.000000 |8039.883000 | |
|max |72.000000 |118.750000 |1.000000 |8684.800000 | |
|========================================================= | |
---- | |
From the distribution shown above, you can see that there no outliner in | |
your data. The numbers are gradually increasing. | |
=== Checking for Missing Values and Inputing Them | |
+*In[29]:*+ | |
[source, ipython3] | |
---- | |
# Adding up the missing values (column-wise) | |
telecom.isnull().sum() | |
---- | |
+*Out[29]:*+ | |
----customerID 0 | |
tenure 0 | |
PhoneService 0 | |
PaperlessBilling 0 | |
MonthlyCharges 0 | |
TotalCharges 11 | |
Churn 0 | |
SeniorCitizen 0 | |
Partner 0 | |
Dependents 0 | |
Contract_One year 0 | |
Contract_Two year 0 | |
PaymentMethod_Credit card (automatic) 0 | |
PaymentMethod_Electronic check 0 | |
PaymentMethod_Mailed check 0 | |
gender_Male 0 | |
MultipleLines_No 0 | |
MultipleLines_Yes 0 | |
InternetService_Fiber optic 0 | |
InternetService_No 0 | |
OnlineSecurity_No 0 | |
OnlineSecurity_Yes 0 | |
OnlineBackup_No 0 | |
OnlineBackup_Yes 0 | |
DeviceProtection_No 0 | |
DeviceProtection_Yes 0 | |
TechSupport_No 0 | |
TechSupport_Yes 0 | |
StreamingTV_No 0 | |
StreamingTV_Yes 0 | |
StreamingMovies_No 0 | |
StreamingMovies_Yes 0 | |
dtype: int64---- | |
It means that 11/7043 = 0.001561834 i.e 0.1%, best is to remove these | |
observations from the analysis | |
+*In[30]:*+ | |
[source, ipython3] | |
---- | |
# Checking the percentage of missing values | |
round(100*(telecom.isnull().sum()/len(telecom.index)), 2) | |
---- | |
+*Out[30]:*+ | |
----customerID 0.00 | |
tenure 0.00 | |
PhoneService 0.00 | |
PaperlessBilling 0.00 | |
MonthlyCharges 0.00 | |
TotalCharges 0.16 | |
Churn 0.00 | |
SeniorCitizen 0.00 | |
Partner 0.00 | |
Dependents 0.00 | |
Contract_One year 0.00 | |
Contract_Two year 0.00 | |
PaymentMethod_Credit card (automatic) 0.00 | |
PaymentMethod_Electronic check 0.00 | |
PaymentMethod_Mailed check 0.00 | |
gender_Male 0.00 | |
MultipleLines_No 0.00 | |
MultipleLines_Yes 0.00 | |
InternetService_Fiber optic 0.00 | |
InternetService_No 0.00 | |
OnlineSecurity_No 0.00 | |
OnlineSecurity_Yes 0.00 | |
OnlineBackup_No 0.00 | |
OnlineBackup_Yes 0.00 | |
DeviceProtection_No 0.00 | |
DeviceProtection_Yes 0.00 | |
TechSupport_No 0.00 | |
TechSupport_Yes 0.00 | |
StreamingTV_No 0.00 | |
StreamingTV_Yes 0.00 | |
StreamingMovies_No 0.00 | |
StreamingMovies_Yes 0.00 | |
dtype: float64---- | |
+*In[31]:*+ | |
[source, ipython3] | |
---- | |
# Removing NaN TotalCharges rows | |
telecom = telecom[~np.isnan(telecom['TotalCharges'])] | |
---- | |
+*In[32]:*+ | |
[source, ipython3] | |
---- | |
# Checking percentage of missing values after removing the missing values | |
round(100*(telecom.isnull().sum()/len(telecom.index)), 2) | |
---- | |
+*Out[32]:*+ | |
----customerID 0.0 | |
tenure 0.0 | |
PhoneService 0.0 | |
PaperlessBilling 0.0 | |
MonthlyCharges 0.0 | |
TotalCharges 0.0 | |
Churn 0.0 | |
SeniorCitizen 0.0 | |
Partner 0.0 | |
Dependents 0.0 | |
Contract_One year 0.0 | |
Contract_Two year 0.0 | |
PaymentMethod_Credit card (automatic) 0.0 | |
PaymentMethod_Electronic check 0.0 | |
PaymentMethod_Mailed check 0.0 | |
gender_Male 0.0 | |
MultipleLines_No 0.0 | |
MultipleLines_Yes 0.0 | |
InternetService_Fiber optic 0.0 | |
InternetService_No 0.0 | |
OnlineSecurity_No 0.0 | |
OnlineSecurity_Yes 0.0 | |
OnlineBackup_No 0.0 | |
OnlineBackup_Yes 0.0 | |
DeviceProtection_No 0.0 | |
DeviceProtection_Yes 0.0 | |
TechSupport_No 0.0 | |
TechSupport_Yes 0.0 | |
StreamingTV_No 0.0 | |
StreamingTV_Yes 0.0 | |
StreamingMovies_No 0.0 | |
StreamingMovies_Yes 0.0 | |
dtype: float64---- | |
Now we don’t have any missing values | |
=== Feature Standardisation | |
+*In[33]:*+ | |
[source, ipython3] | |
---- | |
# Normalising continuous features | |
df = telecom[['tenure','MonthlyCharges','TotalCharges']] | |
---- | |
+*In[34]:*+ | |
[source, ipython3] | |
---- | |
normalized_df=(df-df.mean())/df.std() | |
---- | |
+*In[35]:*+ | |
[source, ipython3] | |
---- | |
telecom = telecom.drop(['tenure','MonthlyCharges','TotalCharges'], 1) | |
---- | |
+*In[36]:*+ | |
[source, ipython3] | |
---- | |
telecom = pd.concat([telecom,normalized_df],axis=1) | |
---- | |
+*In[37]:*+ | |
[source, ipython3] | |
---- | |
telecom | |
---- | |
+*Out[37]:*+ | |
---- | |
[cols=",,,,,,,,,,,,,,,,,,,,,",options="header",] | |
|======================================================================= | |
| |customerID |PhoneService |PaperlessBilling |Churn |SeniorCitizen | |
|Partner |Dependents |Contract_One year |Contract_Two year | |
|PaymentMethod_Credit card (automatic) |... |DeviceProtection_Yes | |
|TechSupport_No |TechSupport_Yes |StreamingTV_No |StreamingTV_Yes | |
|StreamingMovies_No |StreamingMovies_Yes |tenure |MonthlyCharges | |
|TotalCharges | |
|0 |7590-VHVEG |0 |1 |0 |0 |1 |0 |0 |0 |0 |... |0 |1 |0 |1 |0 |1 |0 | |
|-1.280157 |-1.161611 |-0.994123 | |
|1 |5575-GNVDE |1 |0 |0 |0 |0 |0 |1 |0 |0 |... |1 |1 |0 |1 |0 |1 |0 | |
|0.064298 |-0.260859 |-0.173727 | |
|2 |3668-QPYBK |1 |1 |1 |0 |0 |0 |0 |0 |0 |... |0 |1 |0 |1 |0 |1 |0 | |
|-1.239416 |-0.363897 |-0.959581 | |
|3 |7795-CFOCW |0 |0 |0 |0 |0 |0 |1 |0 |0 |... |1 |0 |1 |1 |0 |1 |0 | |
|0.512450 |-0.747797 |-0.195234 | |
|4 |9237-HQITU |1 |1 |1 |0 |0 |0 |0 |0 |0 |... |0 |1 |0 |1 |0 |1 |0 | |
|-1.239416 |0.196164 |-0.940391 | |
|... |... |... |... |... |... |... |... |... |... |... |... |... |... | |
|... |... |... |... |... |... |... |... | |
|7038 |6840-RESVB |1 |1 |0 |0 |1 |1 |1 |0 |0 |... |1 |0 |1 |0 |1 |0 |1 | |
|-0.343113 |0.664821 |-0.129171 | |
|7039 |2234-XADUH |1 |1 |0 |0 |1 |1 |1 |0 |1 |... |1 |1 |0 |0 |1 |0 |1 | |
|1.612459 |1.276402 |2.240896 | |
|7040 |4801-JZAZL |0 |1 |0 |0 |1 |1 |0 |0 |0 |... |0 |1 |0 |1 |0 |1 |0 | |
|-0.872746 |-1.169921 |-0.854453 | |
|7041 |8361-LTMKD |1 |1 |1 |1 |1 |0 |0 |0 |0 |... |0 |1 |0 |1 |0 |1 |0 | |
|-1.157934 |0.319145 |-0.872033 | |
|7042 |3186-AJIEK |1 |1 |0 |0 |0 |0 |0 |1 |0 |... |1 |0 |1 |0 |1 |0 |1 | |
|1.368012 |1.357835 |2.012201 | |
|======================================================================= | |
7032 rows × 32 columns | |
---- | |
=== Checking the Churn Rate | |
+*In[38]:*+ | |
[source, ipython3] | |
---- | |
churn = (sum(telecom['Churn'])/len(telecom['Churn'].index))*100 | |
---- | |
+*In[39]:*+ | |
[source, ipython3] | |
---- | |
churn | |
---- | |
+*Out[39]:*+ | |
----26.578498293515356---- | |
We have almost 27% churn rate | |
== Model Building | |
Let’s start by splitting our data into a training set and a test set. | |
=== Splitting Data into Training and Test Sets | |
+*In[40]:*+ | |
[source, ipython3] | |
---- | |
from sklearn.model_selection import train_test_split | |
---- | |
+*In[44]:*+ | |
[source, ipython3] | |
---- | |
# Putting feature variable to X | |
X = telecom.drop(['Churn','customerID'],axis=1) | |
# Putting response variable to y | |
y = telecom['Churn'] | |
---- | |
+*In[45]:*+ | |
[source, ipython3] | |
---- | |
y.head() | |
---- | |
+*Out[45]:*+ | |
----0 0 | |
1 0 | |
2 1 | |
3 0 | |
4 1 | |
Name: Churn, dtype: int64---- | |
+*In[47]:*+ | |
[source, ipython3] | |
---- | |
# Splitting the data into train and test | |
X_train, X_test, y_train, y_test = train_test_split(X,y, train_size=0.7,test_size=0.3,random_state=100) | |
---- | |
=== Running Your First Training Model | |
+*In[48]:*+ | |
[source, ipython3] | |
---- | |
import statsmodels.api as sm | |
---- | |
+*In[58]:*+ | |
[source, ipython3] | |
---- | |
# Logistic regression model | |
logm1 = sm.GLM(y_train,(sm.add_constant(X_train)), family = sm.families.Binomial()) | |
logm1.fit().summary() | |
---- | |
+*Out[58]:*+ | |
---- | |
.Generalized Linear Model Regression Results | |
[cols=",,,",] | |
|============================================== | |
|Dep. Variable: |Churn |No. Observations: |4922 | |
|Model: |GLM |Df Residuals: |4898 | |
|Model Family: |Binomial |Df Model: |23 | |
|Link Function: |logit |Scale: |1.0000 | |
|Method: |IRLS |Log-Likelihood: |-2004.7 | |
|Date: |Thu, 30 Apr 2020 |Deviance: |4009.4 | |
|Time: |21:05:11 |Pearson chi2: |6.07e+03 | |
|No. Iterations: |37 | | | |
|Covariance Type: |nonrobust | | | |
|============================================== | |
[cols=",,,,,,",] | |
|======================================================================= | |
| |coef |std err |z |P>|z| |[0.025 |0.975] | |
|const |-3.2783 |1.187 |-2.762 |0.006 |-5.605 |-0.952 | |
|PhoneService |0.8213 |0.588 |1.396 |0.163 |-0.332 |1.974 | |
|PaperlessBilling |0.3254 |0.090 |3.614 |0.000 |0.149 |0.502 | |
|SeniorCitizen |0.3984 |0.102 |3.924 |0.000 |0.199 |0.597 | |
|Partner |0.0374 |0.094 |0.399 |0.690 |-0.146 |0.221 | |
|Dependents |-0.1430 |0.107 |-1.332 |0.183 |-0.353 |0.067 | |
|Contract_One year |-0.6578 |0.129 |-5.106 |0.000 |-0.910 |-0.405 | |
|Contract_Two year |-1.2455 |0.212 |-5.874 |0.000 |-1.661 |-0.830 | |
|PaymentMethod_Credit card (automatic) |-0.2577 |0.137 |-1.883 |0.060 | |
|-0.526 |0.011 | |
|PaymentMethod_Electronic check |0.1615 |0.113 |1.434 |0.152 |-0.059 | |
|0.382 | |
|PaymentMethod_Mailed check |-0.2536 |0.137 |-1.845 |0.065 |-0.523 | |
|0.016 | |
|gender_Male |-0.0346 |0.078 |-0.442 |0.658 |-0.188 |0.119 | |
|MultipleLines_No |0.1295 |0.205 |0.632 |0.527 |-0.272 |0.531 | |
|MultipleLines_Yes |0.6918 |0.392 |1.763 |0.078 |-0.077 |1.461 | |
|InternetService_Fiber optic |2.5124 |0.967 |2.599 |0.009 |0.618 |4.407 | |
|InternetService_No |-3.4348 |1.324 |-2.594 |0.009 |-6.030 |-0.839 | |
|OnlineSecurity_No |0.0905 |0.058 |1.558 |0.119 |-0.023 |0.204 | |
|OnlineSecurity_Yes |0.0660 |0.174 |0.380 |0.704 |-0.275 |0.407 | |
|OnlineBackup_No |-0.0088 |0.055 |-0.161 |0.872 |-0.116 |0.098 | |
|OnlineBackup_Yes |0.1653 |0.172 |0.960 |0.337 |-0.172 |0.503 | |
|DeviceProtection_No |-0.0832 |0.056 |-1.487 |0.137 |-0.193 |0.026 | |
|DeviceProtection_Yes |0.2397 |0.174 |1.379 |0.168 |-0.101 |0.580 | |
|TechSupport_No |0.0935 |0.058 |1.604 |0.109 |-0.021 |0.208 | |
|TechSupport_Yes |0.0630 |0.174 |0.362 |0.717 |-0.278 |0.404 | |
|StreamingTV_No |-0.4016 |0.133 |-3.027 |0.002 |-0.662 |-0.142 | |
|StreamingTV_Yes |0.5581 |0.267 |2.094 |0.036 |0.036 |1.081 | |
|StreamingMovies_No |-0.3459 |0.133 |-2.609 |0.009 |-0.606 |-0.086 | |
|StreamingMovies_Yes |0.5024 |0.266 |1.886 |0.059 |-0.020 |1.025 | |
|tenure |-1.5198 |0.190 |-8.015 |0.000 |-1.891 |-1.148 | |
|MonthlyCharges |-2.1817 |1.160 |-1.880 |0.060 |-4.456 |0.092 | |
|TotalCharges |0.7329 |0.198 |3.705 |0.000 |0.345 |1.121 | |
|======================================================================= | |
---- | |
=== Correlation Matrix | |
+*In[59]:*+ | |
[source, ipython3] | |
---- | |
# Importing matplotlib and seaborn | |
import matplotlib.pyplot as plt | |
import seaborn as sns | |
%matplotlib inline | |
---- | |
+*In[60]:*+ | |
[source, ipython3] | |
---- | |
# Let's see the correlation matrix | |
plt.figure(figsize = (20,10)) # Size of the figure | |
sns.heatmap(telecom.corr(),annot = True) | |
---- | |
+*Out[60]:*+ | |
----<matplotlib.axes._subplots.AxesSubplot at 0x20afb7d8fc8> | |
![png](output_54_1.png) | |
---- | |
=== Dropping highly correlated variables. | |
+*In[61]:*+ | |
[source, ipython3] | |
---- | |
X_test2 = X_test.drop(['MultipleLines_No','OnlineSecurity_No','OnlineBackup_No','DeviceProtection_No','TechSupport_No','StreamingTV_No','StreamingMovies_No'],1) | |
X_train2 = X_train.drop(['MultipleLines_No','OnlineSecurity_No','OnlineBackup_No','DeviceProtection_No','TechSupport_No','StreamingTV_No','StreamingMovies_No'],1) | |
---- | |
=== Checking the Correlation Matrix | |
After dropping highly correlated variables now let’s check the | |
correlation matrix again. | |
+*In[62]:*+ | |
[source, ipython3] | |
---- | |
plt.figure(figsize = (20,10)) | |
sns.heatmap(X_train2.corr(),annot = True) | |
---- | |
+*Out[62]:*+ | |
----<matplotlib.axes._subplots.AxesSubplot at 0x20a80bf6e88> | |
![png](output_59_1.png) | |
---- | |
=== Re-Running the Model | |
Now let’s run our model again after dropping highly correlated variables | |
+*In[63]:*+ | |
[source, ipython3] | |
---- | |
logm2 = sm.GLM(y_train,(sm.add_constant(X_train2)), family = sm.families.Binomial()) | |
logm2.fit().summary() | |
---- | |
+*Out[63]:*+ | |
---- | |
.Generalized Linear Model Regression Results | |
[cols=",,,",] | |
|============================================== | |
|Dep. Variable: |Churn |No. Observations: |4922 | |
|Model: |GLM |Df Residuals: |4898 | |
|Model Family: |Binomial |Df Model: |23 | |
|Link Function: |logit |Scale: |1.0000 | |
|Method: |IRLS |Log-Likelihood: |-2004.7 | |
|Date: |Thu, 30 Apr 2020 |Deviance: |4009.4 | |
|Time: |21:05:23 |Pearson chi2: |6.07e+03 | |
|No. Iterations: |7 | | | |
|Covariance Type: |nonrobust | | | |
|============================================== | |
[cols=",,,,,,",] | |
|======================================================================= | |
| |coef |std err |z |P>|z| |[0.025 |0.975] | |
|const |-3.9338 |1.545 |-2.545 |0.011 |-6.963 |-0.905 | |
|PhoneService |0.9507 |0.789 |1.205 |0.228 |-0.595 |2.497 | |
|PaperlessBilling |0.3254 |0.090 |3.614 |0.000 |0.149 |0.502 | |
|SeniorCitizen |0.3984 |0.102 |3.924 |0.000 |0.199 |0.597 | |
|Partner |0.0374 |0.094 |0.399 |0.690 |-0.146 |0.221 | |
|Dependents |-0.1430 |0.107 |-1.332 |0.183 |-0.353 |0.067 | |
|Contract_One year |-0.6578 |0.129 |-5.106 |0.000 |-0.910 |-0.405 | |
|Contract_Two year |-1.2455 |0.212 |-5.874 |0.000 |-1.661 |-0.830 | |
|PaymentMethod_Credit card (automatic) |-0.2577 |0.137 |-1.883 |0.060 | |
|-0.526 |0.011 | |
|PaymentMethod_Electronic check |0.1615 |0.113 |1.434 |0.152 |-0.059 | |
|0.382 | |
|PaymentMethod_Mailed check |-0.2536 |0.137 |-1.845 |0.065 |-0.523 | |
|0.016 | |
|gender_Male |-0.0346 |0.078 |-0.442 |0.658 |-0.188 |0.119 | |
|MultipleLines_Yes |0.5623 |0.214 |2.628 |0.009 |0.143 |0.982 | |
|InternetService_Fiber optic |2.5124 |0.967 |2.599 |0.009 |0.618 |4.407 | |
|InternetService_No |-2.7792 |0.982 |-2.831 |0.005 |-4.703 |-0.855 | |
|OnlineSecurity_Yes |-0.0245 |0.216 |-0.113 |0.910 |-0.448 |0.399 | |
|OnlineBackup_Yes |0.1740 |0.212 |0.822 |0.411 |-0.241 |0.589 | |
|DeviceProtection_Yes |0.3229 |0.215 |1.501 |0.133 |-0.099 |0.744 | |
|TechSupport_Yes |-0.0305 |0.216 |-0.141 |0.888 |-0.455 |0.394 | |
|StreamingTV_Yes |0.9598 |0.396 |2.423 |0.015 |0.183 |1.736 | |
|StreamingMovies_Yes |0.8484 |0.396 |2.143 |0.032 |0.072 |1.624 | |
|tenure |-1.5198 |0.190 |-8.015 |0.000 |-1.891 |-1.148 | |
|MonthlyCharges |-2.1817 |1.160 |-1.880 |0.060 |-4.456 |0.092 | |
|TotalCharges |0.7329 |0.198 |3.705 |0.000 |0.345 |1.121 | |
|======================================================================= | |
---- | |
=== Feature Selection Using RFE | |
+*In[64]:*+ | |
[source, ipython3] | |
---- | |
from sklearn.linear_model import LogisticRegression | |
logreg = LogisticRegression() | |
from sklearn.feature_selection import RFE | |
rfe = RFE(logreg, 13) # running RFE with 13 variables as output | |
rfe = rfe.fit(X,y) | |
print(rfe.support_) # Printing the boolean results | |
print(rfe.ranking_) # Printing the ranking | |
---- | |
+*Out[64]:*+ | |
---- | |
[ True True False False False True True False True False False True | |
False True True False True False False False False False True False | |
False True False True False True] | |
[ 1 1 2 18 6 1 1 11 1 12 14 1 8 1 1 4 1 15 5 13 10 7 1 3 | |
16 1 17 1 9 1] | |
---- | |
+*In[65]:*+ | |
[source, ipython3] | |
---- | |
# Variables selected by RFE | |
col = ['PhoneService', 'PaperlessBilling', 'Contract_One year', 'Contract_Two year', | |
'PaymentMethod_Electronic check','MultipleLines_No','InternetService_Fiber optic', 'InternetService_No', | |
'OnlineSecurity_Yes','TechSupport_Yes','StreamingMovies_No','tenure','TotalCharges'] | |
---- | |
+*In[66]:*+ | |
[source, ipython3] | |
---- | |
# Let's run the model using the selected variables | |
from sklearn.linear_model import LogisticRegression | |
from sklearn import metrics | |
logsk = LogisticRegression() | |
logsk.fit(X_train[col], y_train) | |
---- | |
+*Out[66]:*+ | |
----LogisticRegression(C=1.0, class_weight=None, dual=False, fit_intercept=True, | |
intercept_scaling=1, l1_ratio=None, max_iter=100, | |
multi_class='warn', n_jobs=None, penalty='l2', | |
random_state=None, solver='warn', tol=0.0001, verbose=0, | |
warm_start=False)---- | |
+*In[67]:*+ | |
[source, ipython3] | |
---- | |
#Comparing the model with StatsModels | |
logm4 = sm.GLM(y_train,(sm.add_constant(X_train[col])), family = sm.families.Binomial()) | |
logm4.fit().summary() | |
---- | |
+*Out[67]:*+ | |
---- | |
.Generalized Linear Model Regression Results | |
[cols=",,,",] | |
|============================================== | |
|Dep. Variable: |Churn |No. Observations: |4922 | |
|Model: |GLM |Df Residuals: |4908 | |
|Model Family: |Binomial |Df Model: |13 | |
|Link Function: |logit |Scale: |1.0000 | |
|Method: |IRLS |Log-Likelihood: |-2024.2 | |
|Date: |Thu, 30 Apr 2020 |Deviance: |4048.4 | |
|Time: |21:05:48 |Pearson chi2: |6.19e+03 | |
|No. Iterations: |7 | | | |
|Covariance Type: |nonrobust | | | |
|============================================== | |
[cols=",,,,,,",] | |
|======================================================================= | |
| |coef |std err |z |P>|z| |[0.025 |0.975] | |
|const |-1.0162 |0.169 |-6.017 |0.000 |-1.347 |-0.685 | |
|PhoneService |-0.3090 |0.173 |-1.784 |0.074 |-0.648 |0.030 | |
|PaperlessBilling |0.3595 |0.089 |4.029 |0.000 |0.185 |0.534 | |
|Contract_One year |-0.7012 |0.127 |-5.516 |0.000 |-0.950 |-0.452 | |
|Contract_Two year |-1.3187 |0.210 |-6.271 |0.000 |-1.731 |-0.907 | |
|PaymentMethod_Electronic check |0.3668 |0.083 |4.446 |0.000 |0.205 | |
|0.529 | |
|MultipleLines_No |-0.2311 |0.095 |-2.435 |0.015 |-0.417 |-0.045 | |
|InternetService_Fiber optic |0.7937 |0.116 |6.836 |0.000 |0.566 |1.021 | |
|InternetService_No |-1.1832 |0.182 |-6.484 |0.000 |-1.541 |-0.826 | |
|OnlineSecurity_Yes |-0.4107 |0.102 |-4.031 |0.000 |-0.610 |-0.211 | |
|TechSupport_Yes |-0.4181 |0.101 |-4.135 |0.000 |-0.616 |-0.220 | |
|StreamingMovies_No |-0.2024 |0.094 |-2.160 |0.031 |-0.386 |-0.019 | |
|tenure |-1.4974 |0.181 |-8.251 |0.000 |-1.853 |-1.142 | |
|TotalCharges |0.7373 |0.186 |3.965 |0.000 |0.373 |1.102 | |
|======================================================================= | |
---- | |
+*In[68]:*+ | |
[source, ipython3] | |
---- | |
# UDF for calculating vif value | |
def vif_cal(input_data, dependent_col): | |
vif_df = pd.DataFrame( columns = ['Var', 'Vif']) | |
x_vars=input_data.drop([dependent_col], axis=1) | |
xvar_names=x_vars.columns | |
for i in range(0,xvar_names.shape[0]): | |
y=x_vars[xvar_names[i]] | |
x=x_vars[xvar_names.drop(xvar_names[i])] | |
rsq=sm.OLS(y,x).fit().rsquared | |
vif=round(1/(1-rsq),2) | |
vif_df.loc[i] = [xvar_names[i], vif] | |
return vif_df.sort_values(by = 'Vif', axis=0, ascending=False, inplace=False) | |
---- | |
+*In[69]:*+ | |
[source, ipython3] | |
---- | |
telecom.columns | |
['PhoneService', 'PaperlessBilling', 'Contract_One year', 'Contract_Two year', | |
'PaymentMethod_Electronic check','MultipleLines_No','InternetService_Fiber optic', 'InternetService_No', | |
'OnlineSecurity_Yes','TechSupport_Yes','StreamingMovies_No','tenure','TotalCharges'] | |
---- | |
+*Out[69]:*+ | |
----['PhoneService', | |
'PaperlessBilling', | |
'Contract_One year', | |
'Contract_Two year', | |
'PaymentMethod_Electronic check', | |
'MultipleLines_No', | |
'InternetService_Fiber optic', | |
'InternetService_No', | |
'OnlineSecurity_Yes', | |
'TechSupport_Yes', | |
'StreamingMovies_No', | |
'tenure', | |
'TotalCharges']---- | |
+*In[71]:*+ | |
[source, ipython3] | |
---- | |
# Calculating Vif value | |
vif_cal(input_data=telecom.drop(['customerID','SeniorCitizen', 'Partner', 'Dependents', | |
'PaymentMethod_Credit card (automatic)','PaymentMethod_Mailed check', | |
'gender_Male','MultipleLines_Yes','OnlineSecurity_No','OnlineBackup_No', | |
'OnlineBackup_Yes', 'DeviceProtection_No', 'DeviceProtection_Yes', | |
'TechSupport_No','StreamingTV_No','StreamingTV_Yes','StreamingMovies_Yes', | |
'MonthlyCharges'], axis=1), dependent_col='Churn') | |
---- | |
+*Out[71]:*+ | |
---- | |
[cols=",,",options="header",] | |
|======================================= | |
| |Var |Vif | |
|0 |PhoneService |10.87 | |
|12 |TotalCharges |8.58 | |
|11 |tenure |6.80 | |
|1 |PaperlessBilling |2.61 | |
|7 |InternetService_No |0.65 | |
|3 |Contract_Two year |0.28 | |
|2 |Contract_One year |0.24 | |
|9 |TechSupport_Yes |0.24 | |
|8 |OnlineSecurity_Yes |0.21 | |
|10 |StreamingMovies_No |0.19 | |
|4 |PaymentMethod_Electronic check |0.05 | |
|5 |MultipleLines_No |0.05 | |
|6 |InternetService_Fiber optic |0.03 | |
|======================================= | |
---- | |
=== Dropping Variable with high VIF | |
+*In[72]:*+ | |
[source, ipython3] | |
---- | |
col = ['PaperlessBilling', 'Contract_One year', 'Contract_Two year', | |
'PaymentMethod_Electronic check','MultipleLines_No','InternetService_Fiber optic', 'InternetService_No', | |
'OnlineSecurity_Yes','TechSupport_Yes','StreamingMovies_No','tenure','TotalCharges'] | |
---- | |
+*In[73]:*+ | |
[source, ipython3] | |
---- | |
logm5 = sm.GLM(y_train,(sm.add_constant(X_train[col])), family = sm.families.Binomial()) | |
logm5.fit().summary() | |
---- | |
+*Out[73]:*+ | |
---- | |
.Generalized Linear Model Regression Results | |
[cols=",,,",] | |
|============================================== | |
|Dep. Variable: |Churn |No. Observations: |4922 | |
|Model: |GLM |Df Residuals: |4909 | |
|Model Family: |Binomial |Df Model: |12 | |
|Link Function: |logit |Scale: |1.0000 | |
|Method: |IRLS |Log-Likelihood: |-2025.8 | |
|Date: |Thu, 30 Apr 2020 |Deviance: |4051.5 | |
|Time: |21:07:06 |Pearson chi2: |6.00e+03 | |
|No. Iterations: |7 | | | |
|Covariance Type: |nonrobust | | | |
|============================================== | |
[cols=",,,,,,",] | |
|======================================================================= | |
| |coef |std err |z |P>|z| |[0.025 |0.975] | |
|const |-1.1915 |0.138 |-8.607 |0.000 |-1.463 |-0.920 | |
|PaperlessBilling |0.3563 |0.089 |3.998 |0.000 |0.182 |0.531 | |
|Contract_One year |-0.6965 |0.127 |-5.483 |0.000 |-0.945 |-0.448 | |
|Contract_Two year |-1.3078 |0.210 |-6.230 |0.000 |-1.719 |-0.896 | |
|PaymentMethod_Electronic check |0.3700 |0.082 |4.487 |0.000 |0.208 | |
|0.532 | |
|MultipleLines_No |-0.2990 |0.087 |-3.442 |0.001 |-0.469 |-0.129 | |
|InternetService_Fiber optic |0.7227 |0.108 |6.666 |0.000 |0.510 |0.935 | |
|InternetService_No |-1.2732 |0.175 |-7.276 |0.000 |-1.616 |-0.930 | |
|OnlineSecurity_Yes |-0.4100 |0.102 |-4.025 |0.000 |-0.610 |-0.210 | |
|TechSupport_Yes |-0.4202 |0.101 |-4.157 |0.000 |-0.618 |-0.222 | |
|StreamingMovies_No |-0.2205 |0.093 |-2.366 |0.018 |-0.403 |-0.038 | |
|tenure |-1.4276 |0.177 |-8.066 |0.000 |-1.774 |-1.081 | |
|TotalCharges |0.6495 |0.179 |3.622 |0.000 |0.298 |1.001 | |
|======================================================================= | |
---- | |
+*In[74]:*+ | |
[source, ipython3] | |
---- | |
# Calculating Vif value | |
vif_cal(input_data=telecom.drop(['customerID','PhoneService','SeniorCitizen', 'Partner', 'Dependents', | |
'PaymentMethod_Credit card (automatic)','PaymentMethod_Mailed check', | |
'gender_Male','MultipleLines_Yes','OnlineSecurity_No','OnlineBackup_No', | |
'OnlineBackup_Yes', 'DeviceProtection_No', 'DeviceProtection_Yes', | |
'TechSupport_No','StreamingTV_No','StreamingTV_Yes','StreamingMovies_Yes', | |
'MonthlyCharges'], axis=1), dependent_col='Churn') | |
---- | |
+*Out[74]:*+ | |
---- | |
[cols=",,",options="header",] | |
|======================================= | |
| |Var |Vif | |
|11 |TotalCharges |8.24 | |
|10 |tenure |6.56 | |
|0 |PaperlessBilling |2.44 | |
|6 |InternetService_No |0.45 | |
|2 |Contract_Two year |0.26 | |
|8 |TechSupport_Yes |0.24 | |
|1 |Contract_One year |0.23 | |
|7 |OnlineSecurity_Yes |0.21 | |
|9 |StreamingMovies_No |0.17 | |
|3 |PaymentMethod_Electronic check |0.05 | |
|4 |MultipleLines_No |0.04 | |
|5 |InternetService_Fiber optic |0.02 | |
|======================================= | |
---- | |
+*In[75]:*+ | |
[source, ipython3] | |
---- | |
# Let's run the model using the selected variables | |
from sklearn.linear_model import LogisticRegression | |
from sklearn import metrics | |
logsk = LogisticRegression() | |
logsk.fit(X_train[col], y_train) | |
---- | |
+*Out[75]:*+ | |
----LogisticRegression(C=1.0, class_weight=None, dual=False, fit_intercept=True, | |
intercept_scaling=1, l1_ratio=None, max_iter=100, | |
multi_class='warn', n_jobs=None, penalty='l2', | |
random_state=None, solver='warn', tol=0.0001, verbose=0, | |
warm_start=False)---- | |
=== Making Predictions | |
+*In[76]:*+ | |
[source, ipython3] | |
---- | |
# Predicted probabilities | |
y_pred = logsk.predict_proba(X_test[col]) | |
---- | |
+*In[77]:*+ | |
[source, ipython3] | |
---- | |
# Converting y_pred to a dataframe which is an array | |
y_pred_df = pd.DataFrame(y_pred) | |
---- | |
+*In[78]:*+ | |
[source, ipython3] | |
---- | |
# Converting to column dataframe | |
y_pred_1 = y_pred_df.iloc[:,[1]] | |
---- | |
+*In[88]:*+ | |
[source, ipython3] | |
---- | |
# Let's see the head | |
y_pred_1.head() | |
---- | |
+*Out[88]:*+ | |
---- | |
[cols=",",options="header",] | |
|=========== | |
| |1 | |
|0 |0.499083 | |
|1 |0.372696 | |
|2 |0.006738 | |
|3 |0.635453 | |
|4 |0.007533 | |
|=========== | |
---- | |
+*In[89]:*+ | |
[source, ipython3] | |
---- | |
# Converting y_test to dataframe | |
y_test_df = pd.DataFrame(y_test) | |
---- | |
+*In[90]:*+ | |
[source, ipython3] | |
---- | |
# Putting CustID to index | |
y_test_df['CustID'] = y_test_df.index | |
---- | |
+*In[91]:*+ | |
[source, ipython3] | |
---- | |
# Removing index for both dataframes to append them side by side | |
y_pred_1.reset_index(drop=True, inplace=True) | |
y_test_df.reset_index(drop=True, inplace=True) | |
---- | |
+*In[92]:*+ | |
[source, ipython3] | |
---- | |
# Appending y_test_df and y_pred_1 | |
y_pred_final = pd.concat([y_test_df,y_pred_1],axis=1) | |
---- | |
+*In[93]:*+ | |
[source, ipython3] | |
---- | |
# Renaming the column | |
y_pred_final= y_pred_final.rename(columns={ 1 : 'Churn_Prob'}) | |
---- | |
+*In[94]:*+ | |
[source, ipython3] | |
---- | |
# Rearranging the columns | |
y_pred_final = y_pred_final.reindex(['CustID','Churn','Churn_Prob'], axis=1) | |
---- | |
+*In[95]:*+ | |
[source, ipython3] | |
---- | |
# Let's see the head of y_pred_final | |
y_pred_final.head() | |
---- | |
+*Out[95]:*+ | |
---- | |
[cols=",,,",options="header",] | |
|=========================== | |
| |CustID |Churn |Churn_Prob | |
|0 |942 |0 |0.499083 | |
|1 |3730 |1 |0.372696 | |
|2 |1761 |0 |0.006738 | |
|3 |2283 |1 |0.635453 | |
|4 |1872 |0 |0.007533 | |
|=========================== | |
---- | |
+*In[96]:*+ | |
[source, ipython3] | |
---- | |
# Creating new column 'predicted' with 1 if Churn_Prob>0.5 else 0 | |
y_pred_final['predicted'] = y_pred_final.Churn_Prob.map( lambda x: 1 if x > 0.5 else 0) | |
---- | |
+*In[97]:*+ | |
[source, ipython3] | |
---- | |
# Let's see the head | |
y_pred_final.head() | |
---- | |
+*Out[97]:*+ | |
---- | |
[cols=",,,,",options="header",] | |
|====================================== | |
| |CustID |Churn |Churn_Prob |predicted | |
|0 |942 |0 |0.499083 |0 | |
|1 |3730 |1 |0.372696 |0 | |
|2 |1761 |0 |0.006738 |0 | |
|3 |2283 |1 |0.635453 |1 | |
|4 |1872 |0 |0.007533 |0 | |
|====================================== | |
---- | |
=== Model Evaluation | |
+*In[98]:*+ | |
[source, ipython3] | |
---- | |
from sklearn import metrics | |
---- | |
+*In[99]:*+ | |
[source, ipython3] | |
---- | |
help(metrics.confusion_matrix) | |
---- | |
+*Out[99]:*+ | |
---- | |
Help on function confusion_matrix in module sklearn.metrics.classification: | |
confusion_matrix(y_true, y_pred, labels=None, sample_weight=None) | |
Compute confusion matrix to evaluate the accuracy of a classification | |
By definition a confusion matrix :math:`C` is such that :math:`C_{i, j}` | |
is equal to the number of observations known to be in group :math:`i` but | |
predicted to be in group :math:`j`. | |
Thus in binary classification, the count of true negatives is | |
:math:`C_{0,0}`, false negatives is :math:`C_{1,0}`, true positives is | |
:math:`C_{1,1}` and false positives is :math:`C_{0,1}`. | |
Read more in the :ref:`User Guide <confusion_matrix>`. | |
Parameters | |
---------- | |
y_true : array, shape = [n_samples] | |
Ground truth (correct) target values. | |
y_pred : array, shape = [n_samples] | |
Estimated targets as returned by a classifier. | |
labels : array, shape = [n_classes], optional | |
List of labels to index the matrix. This may be used to reorder | |
or select a subset of labels. | |
If none is given, those that appear at least once | |
in ``y_true`` or ``y_pred`` are used in sorted order. | |
sample_weight : array-like of shape = [n_samples], optional | |
Sample weights. | |
Returns | |
------- | |
C : array, shape = [n_classes, n_classes] | |
Confusion matrix | |
References | |
---------- | |
.. [1] `Wikipedia entry for the Confusion matrix | |
<https://en.wikipedia.org/wiki/Confusion_matrix>`_ | |
(Wikipedia and other references may use a different | |
convention for axes) | |
Examples | |
-------- | |
>>> from sklearn.metrics import confusion_matrix | |
>>> y_true = [2, 0, 2, 2, 0, 1] | |
>>> y_pred = [0, 0, 2, 2, 0, 2] | |
>>> confusion_matrix(y_true, y_pred) | |
array([[2, 0, 0], | |
[0, 0, 1], | |
[1, 0, 2]]) | |
>>> y_true = ["cat", "ant", "cat", "cat", "ant", "bird"] | |
>>> y_pred = ["ant", "ant", "cat", "cat", "ant", "cat"] | |
>>> confusion_matrix(y_true, y_pred, labels=["ant", "bird", "cat"]) | |
array([[2, 0, 0], | |
[0, 0, 1], | |
[1, 0, 2]]) | |
In the binary case, we can extract true positives, etc as follows: | |
>>> tn, fp, fn, tp = confusion_matrix([0, 1, 0, 1], [1, 1, 1, 0]).ravel() | |
>>> (tn, fp, fn, tp) | |
(0, 2, 1, 1) | |
---- | |
+*In[100]:*+ | |
[source, ipython3] | |
---- | |
# Confusion matrix | |
confusion = metrics.confusion_matrix( y_pred_final.Churn, y_pred_final.predicted ) | |
confusion | |
---- | |
+*Out[100]:*+ | |
----array([[1362, 166], | |
[ 249, 333]], dtype=int64)---- | |
+*In[103]:*+ | |
[source, ipython3] | |
---- | |
# Predicted not_churn churn | |
# Actual | |
# not_churn 1326 166 | |
# churn 249 333 | |
---- | |
+*In[104]:*+ | |
[source, ipython3] | |
---- | |
#Let's check the overall accuracy. | |
metrics.accuracy_score( y_pred_final.Churn, y_pred_final.predicted) | |
---- | |
+*Out[104]:*+ | |
----0.8033175355450237---- | |
+*In[105]:*+ | |
[source, ipython3] | |
---- | |
TP = confusion[1,1] # true positive | |
TN = confusion[0,0] # true negatives | |
FP = confusion[0,1] # false positives | |
FN = confusion[1,0] # false negatives | |
---- | |
+*In[106]:*+ | |
[source, ipython3] | |
---- | |
# Let's see the sensitivity of our logistic regression model | |
TP / float(TP+FN) | |
---- | |
+*Out[106]:*+ | |
----0.5721649484536082---- | |
+*In[107]:*+ | |
[source, ipython3] | |
---- | |
# Let us calculate specificity | |
TN / float(TN+FP) | |
---- | |
+*Out[107]:*+ | |
----0.8913612565445026---- | |
+*In[108]:*+ | |
[source, ipython3] | |
---- | |
# Calculate false postive rate - predicting churn when customer does not have churned | |
print(FP/ float(TN+FP)) | |
---- | |
+*Out[108]:*+ | |
---- | |
0.10863874345549739 | |
---- | |
+*In[109]:*+ | |
[source, ipython3] | |
---- | |
# positive predictive value | |
print (TP / float(TP+FP)) | |
---- | |
+*Out[109]:*+ | |
---- | |
0.6673346693386774 | |
---- | |
+*In[110]:*+ | |
[source, ipython3] | |
---- | |
# Negative predictive value | |
print (TN / float(TN+ FN)) | |
---- | |
+*Out[110]:*+ | |
---- | |
0.845437616387337 | |
---- | |
=== ROC Curve | |
An ROC curve demonstrates several things: | |
* It shows the tradeoff between sensitivity and specificity (any | |
increase in sensitivity will be accompanied by a decrease in | |
specificity). | |
* The closer the curve follows the left-hand border and then the top | |
border of the ROC space, the more accurate the test. | |
* The closer the curve comes to the 45-degree diagonal of the ROC space, | |
the less accurate the test. | |
+*In[111]:*+ | |
[source, ipython3] | |
---- | |
def draw_roc( actual, probs ): | |
fpr, tpr, thresholds = metrics.roc_curve( actual, probs, | |
drop_intermediate = False ) | |
auc_score = metrics.roc_auc_score( actual, probs ) | |
plt.figure(figsize=(6, 4)) | |
plt.plot( fpr, tpr, label='ROC curve (area = %0.2f)' % auc_score ) | |
plt.plot([0, 1], [0, 1], 'k--') | |
plt.xlim([0.0, 1.0]) | |
plt.ylim([0.0, 1.05]) | |
plt.xlabel('False Positive Rate or [1 - True Negative Rate]') | |
plt.ylabel('True Positive Rate') | |
plt.title('Receiver operating characteristic example') | |
plt.legend(loc="lower right") | |
plt.show() | |
return fpr, tpr, thresholds | |
---- | |
+*In[112]:*+ | |
[source, ipython3] | |
---- | |
draw_roc(y_pred_final.Churn, y_pred_final.predicted) | |
---- | |
+*Out[112]:*+ | |
---- | |
![png](output_105_0.png) | |
(array([0. , 0.10863874, 1. ]), | |
array([0. , 0.57216495, 1. ]), | |
array([2, 1, 0], dtype=int64))---- | |
=== Finding Optimal Cutoff Point | |
Optimal cutoff probability is that prob where we get balanced | |
sensitivity and specificity | |
+*In[113]:*+ | |
[source, ipython3] | |
---- | |
# Let's create columns with different probability cutoffs | |
numbers = [float(x)/10 for x in range(10)] | |
for i in numbers: | |
y_pred_final[i]= y_pred_final.Churn_Prob.map( lambda x: 1 if x > i else 0) | |
y_pred_final.head() | |
---- | |
+*Out[113]:*+ | |
---- | |
[cols=",,,,,,,,,,,,,,",options="header",] | |
|======================================================================= | |
| |CustID |Churn |Churn_Prob |predicted |0.0 |0.1 |0.2 |0.3 |0.4 |0.5 | |
|0.6 |0.7 |0.8 |0.9 | |
|0 |942 |0 |0.499083 |0 |1 |1 |1 |1 |1 |0 |0 |0 |0 |0 | |
|1 |3730 |1 |0.372696 |0 |1 |1 |1 |1 |0 |0 |0 |0 |0 |0 | |
|2 |1761 |0 |0.006738 |0 |1 |0 |0 |0 |0 |0 |0 |0 |0 |0 | |
|3 |2283 |1 |0.635453 |1 |1 |1 |1 |1 |1 |1 |1 |0 |0 |0 | |
|4 |1872 |0 |0.007533 |0 |1 |0 |0 |0 |0 |0 |0 |0 |0 |0 | |
|======================================================================= | |
---- | |
+*In[114]:*+ | |
[source, ipython3] | |
---- | |
# Now let's calculate accuracy sensitivity and specificity for various probability cutoffs. | |
cutoff_df = pd.DataFrame( columns = ['prob','accuracy','sensi','speci']) | |
from sklearn.metrics import confusion_matrix | |
# TP = confusion[1,1] # true positive | |
# TN = confusion[0,0] # true negatives | |
# FP = confusion[0,1] # false positives | |
# FN = confusion[1,0] # false negatives | |
num = [0.0,0.1,0.2,0.3,0.4,0.5,0.6,0.7,0.8,0.9] | |
for i in num: | |
cm1 = metrics.confusion_matrix( y_pred_final.Churn, y_pred_final[i] ) | |
total1=sum(sum(cm1)) | |
accuracy = (cm1[0,0]+cm1[1,1])/total1 | |
speci = cm1[0,0]/(cm1[0,0]+cm1[0,1]) | |
sensi = cm1[1,1]/(cm1[1,0]+cm1[1,1]) | |
cutoff_df.loc[i] =[ i ,accuracy,sensi,speci] | |
print(cutoff_df) | |
---- | |
+*Out[114]:*+ | |
---- | |
prob accuracy sensi speci | |
0.0 0.0 0.275829 1.000000 0.000000 | |
0.1 0.1 0.605687 0.943299 0.477094 | |
0.2 0.2 0.695261 0.831615 0.643325 | |
0.3 0.3 0.750237 0.743986 0.752618 | |
0.4 0.4 0.783886 0.666667 0.828534 | |
0.5 0.5 0.803318 0.572165 0.891361 | |
0.6 0.6 0.795735 0.412371 0.941754 | |
0.7 0.7 0.757820 0.178694 0.978403 | |
0.8 0.8 0.727962 0.013746 1.000000 | |
0.9 0.9 0.724171 0.000000 1.000000 | |
---- | |
+*In[115]:*+ | |
[source, ipython3] | |
---- | |
# Let's plot accuracy sensitivity and specificity for various probabilities. | |
cutoff_df.plot.line(x='prob', y=['accuracy','sensi','speci']) | |
---- | |
+*Out[115]:*+ | |
----<matplotlib.axes._subplots.AxesSubplot at 0x20a8118e9c8> | |
![png](output_110_1.png) | |
---- | |
=== From the curve above, 0.3 is the optimum point to take it as a | |
cutoff probability. | |
+*In[116]:*+ | |
[source, ipython3] | |
---- | |
y_pred_final['final_predicted'] = y_pred_final.Churn_Prob.map( lambda x: 1 if x > 0.3 else 0) | |
---- | |
+*In[117]:*+ | |
[source, ipython3] | |
---- | |
y_pred_final.head() | |
---- | |
+*Out[117]:*+ | |
---- | |
[cols=",,,,,,,,,,,,,,,",options="header",] | |
|======================================================================= | |
| |CustID |Churn |Churn_Prob |predicted |0.0 |0.1 |0.2 |0.3 |0.4 |0.5 | |
|0.6 |0.7 |0.8 |0.9 |final_predicted | |
|0 |942 |0 |0.499083 |0 |1 |1 |1 |1 |1 |0 |0 |0 |0 |0 |1 | |
|1 |3730 |1 |0.372696 |0 |1 |1 |1 |1 |0 |0 |0 |0 |0 |0 |1 | |
|2 |1761 |0 |0.006738 |0 |1 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 | |
|3 |2283 |1 |0.635453 |1 |1 |1 |1 |1 |1 |1 |1 |0 |0 |0 |1 | |
|4 |1872 |0 |0.007533 |0 |1 |0 |0 |0 |0 |0 |0 |0 |0 |0 |0 | |
|======================================================================= | |
---- | |
+*In[118]:*+ | |
[source, ipython3] | |
---- | |
#Let's check the overall accuracy. | |
metrics.accuracy_score( y_pred_final.Churn, y_pred_final.final_predicted) | |
---- | |
+*Out[118]:*+ | |
----0.7502369668246446---- | |
+*In[119]:*+ | |
[source, ipython3] | |
---- | |
metrics.confusion_matrix( y_pred_final.Churn, y_pred_final.final_predicted ) | |
---- | |
+*Out[119]:*+ | |
----array([[1150, 378], | |
[ 149, 433]], dtype=int64)---- |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment