Importing libraries for both LR and SVM use¶

In [14]:
## Header
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn import datasets
from sklearn.preprocessing import StandardScaler, LabelEncoder
from sklearn.model_selection import train_test_split
from sklearn.impute import SimpleImputer
In [16]:
# Algorithms to use
from sklearn.linear_model import LogisticRegression
from sklearn.svm import SVC
from sklearn import svm
In [18]:
# Metrics to evaluate the model
from sklearn import metrics
from sklearn.metrics import confusion_matrix, classification_report, precision_recall_curve, recall_score
from sklearn.metrics import roc_curve, auc, accuracy_score
In [20]:
#To ignore warnings
import warnings
warnings.filterwarnings ("ignore")

Dataset Analysis¶

In [25]:
# Loads the first 5 rows of the dataset
df = pd.read_excel('WA_Fn-UseC_-Telco-Customer-Churn.xlsx')
df.head()
Out[25]:
customerID gender SeniorCitizen Partner Dependents tenure PhoneService MultipleLines InternetService OnlineSecurity ... DeviceProtection TechSupport StreamingTV StreamingMovies Contract PaperlessBilling PaymentMethod MonthlyCharges TotalCharges Churn
0 7590-VHVEG Female 0 Yes No 1 No No phone service DSL No ... No No No No Month-to-month Yes Electronic check 29.85 29.85 No
1 5575-GNVDE Male 0 No No 34 Yes No DSL Yes ... Yes No No No One year No Mailed check 56.95 1889.5 No
2 3668-QPYBK Male 0 No No 2 Yes No DSL Yes ... No No No No Month-to-month Yes Mailed check 53.85 108.15 Yes
3 7795-CFOCW Male 0 No No 45 No No phone service DSL Yes ... Yes Yes No No One year No Bank transfer (automatic) 42.30 1840.75 No
4 9237-HQITU Female 0 No No 2 Yes No Fiber optic No ... No No No No Month-to-month Yes Electronic check 70.70 151.65 Yes

5 rows × 21 columns

In [26]:
# To visualise the data and its types
df.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
In [29]:
# Dropping columns that add no value

df=df.drop(['customerID', 'gender', 'SeniorCitizen', 'Partner',
            'Dependents', 'PaperlessBilling', 'PaymentMethod', 'TotalCharges'], axis=1) # 'axis=1' means column
# These coloumns are dropped as they offer no proper value to be assessed or just refuse to cooperate, like 'TotalCharges' being mislabed
In [31]:
#Creating numerical columns

num_cols=['tenure', 'MonthlyCharges']

# Creating categorical variables

cat_cols= ['PhoneService', 'MultipleLines', 'InternetService', 'OnlineSecurity',
           'OnlineBackup', 'DeviceProtection', 'TechSupport', 'StreamingTV',
           'StreamingMovies', 'Contract', 'Churn']
In [33]:
# Checking summary statistics 

df[num_cols].describe().T
Out[33]:
count mean std min 25% 50% 75% max
tenure 7043.0 32.371149 24.559481 0.00 9.0 29.00 55.00 72.00
MonthlyCharges 7043.0 64.761692 30.090047 18.25 35.5 70.35 89.85 118.75
In [35]:
# Creating histograms 

df[num_cols].hist (figsize=(14,14)) 
plt.show()
No description has been provided for this image
In [37]:
# Printing the % sub categories of each category 

for i in cat_cols: 
    print(df[i].value_counts (normalize=True)) 
    print('*'*40)

for i in cat_cols:
    if i!='Churn':
        (pd.crosstab(df[i], df['Churn'], normalize='index')*100).plot(kind='bar', figsize=(8,4), stacked=True) 
        plt.ylabel('Percentage Churned %')
PhoneService
Yes    0.903166
No     0.096834
Name: proportion, dtype: float64
****************************************
MultipleLines
No                  0.481329
Yes                 0.421837
No phone service    0.096834
Name: proportion, dtype: float64
****************************************
InternetService
Fiber optic    0.439585
DSL            0.343746
No             0.216669
Name: proportion, dtype: float64
****************************************
OnlineSecurity
No                     0.496663
Yes                    0.286668
No internet service    0.216669
Name: proportion, dtype: float64
****************************************
OnlineBackup
No                     0.438450
Yes                    0.344881
No internet service    0.216669
Name: proportion, dtype: float64
****************************************
DeviceProtection
No                     0.439443
Yes                    0.343888
No internet service    0.216669
Name: proportion, dtype: float64
****************************************
TechSupport
No                     0.493114
Yes                    0.290217
No internet service    0.216669
Name: proportion, dtype: float64
****************************************
StreamingTV
No                     0.398978
Yes                    0.384353
No internet service    0.216669
Name: proportion, dtype: float64
****************************************
StreamingMovies
No                     0.395428
Yes                    0.387903
No internet service    0.216669
Name: proportion, dtype: float64
****************************************
Contract
Month-to-month    0.550192
Two year          0.240664
One year          0.209144
Name: proportion, dtype: float64
****************************************
Churn
No     0.73463
Yes    0.26537
Name: proportion, dtype: float64
****************************************
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
In [39]:
# Plotting the correlation between numerical variables 

plt.figure(figsize=(15,8)) 
sns.heatmap(df[num_cols].corr(), annot=True, fmt='0.5f', cmap='YlOrRd')
Out[39]:
<Axes: >
No description has been provided for this image
In [41]:
#Creating List of dummy columns 
dummies = ['PhoneService', 'MultipleLines', 'InternetService', 'OnlineSecurity',
           'OnlineBackup', 'DeviceProtection', 'TechSupport', 'StreamingTV',
           'StreamingMovies', 'Contract']

#Creating dummy variables 
df = pd.get_dummies (data = df, columns = dummies, drop_first = True)

#  Mapping Churn
dict_Churn = {'No': 0, 'Yes': 1}

df['Churn'] = df.Churn.map(dict_Churn)
In [43]:
# Separating target variable and other variables 

Y= df.Churn
X= df.drop(columns = ['Churn'])
In [45]:
# splitting the data

X_train, X_test, y_train, y_test = train_test_split(X, Y, test_size = 0.3, random_state = 1, stratify = Y)
In [47]:
# scaling the data

sc=StandardScaler()

# Fit_transform on train data 

X_train_scaled=sc.fit_transform(X_train)
X_train_scaled=pd.DataFrame(X_train_scaled, columns=X.columns) 
In [49]:
# Transform on test data 

X_test_scaled=sc.transform(X_test) 
X_test_scaled=pd.DataFrame(X_test_scaled, columns=X.columns)
In [51]:
# Creating metric function 

def metrics_score(actual, predicted): 
    print(classification_report (actual, predicted)) 
    cm = confusion_matrix(actual, predicted) 
    plt.figure(figsize=(8,5)) 
    sns.heatmap(cm, annot=True, fmt='.2f', xticklabels=['Not Churned', 'Churned'], yticklabels=['Not Churned', 'Churned']) 
    plt.ylabel('Actual') 
    plt.xlabel('Predicted') 
    plt.show()

Logistic Regression¶

In [54]:
# Fitting Logistic regression model 

lg=LogisticRegression() 
lg.fit(X_train_scaled, y_train)
Out[54]:
LogisticRegression()
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
LogisticRegression()
In [56]:
# Checking the performance on the training data 

y_pred_train = lg.predict(X_train_scaled) 
metrics_score(y_train, y_pred_train)
              precision    recall  f1-score   support

           0       0.84      0.90      0.87      3622
           1       0.65      0.53      0.58      1308

    accuracy                           0.80      4930
   macro avg       0.74      0.71      0.72      4930
weighted avg       0.79      0.80      0.79      4930

No description has been provided for this image
In [58]:
# Checking the performance on the test dataset 

y_pred_test = lg.predict(X_test_scaled) 
metrics_score(y_test, y_pred_test)
              precision    recall  f1-score   support

           0       0.84      0.90      0.87      1552
           1       0.66      0.52      0.58       561

    accuracy                           0.80      2113
   macro avg       0.75      0.71      0.72      2113
weighted avg       0.79      0.80      0.79      2113

No description has been provided for this image
In [60]:
# Printing the coefficients of Logistic regression 

cols=X.columns 
coef_lg=lg.coef_ 
pd.DataFrame(coef_lg, columns=cols).T.sort_values(by = 0, ascending = False)

# Finding the odds 

odds = np.exp(lg.coef_[0])

# Adding the odds to a dataframe and sorting the values 

pd.DataFrame(odds, X_train_scaled.columns, columns = ['odds']).sort_values(by = 'odds', ascending = False)
Out[60]:
odds
InternetService_Fiber optic 1.994006
StreamingMovies_Yes 1.252254
StreamingTV_Yes 1.232470
MultipleLines_Yes 1.187010
MultipleLines_No phone service 1.039758
DeviceProtection_Yes 1.025867
PhoneService_Yes 0.961762
DeviceProtection_No internet service 0.931628
StreamingMovies_No internet service 0.931628
StreamingTV_No internet service 0.931628
TechSupport_No internet service 0.931628
OnlineBackup_No internet service 0.931628
OnlineSecurity_No internet service 0.931628
InternetService_No 0.931628
OnlineBackup_Yes 0.913290
TechSupport_Yes 0.889813
OnlineSecurity_Yes 0.841905
Contract_One year 0.731311
MonthlyCharges 0.710126
Contract_Two year 0.499443
tenure 0.448343
In [62]:
# Train a Logistic Regression model
lg.fit(X_train, y_train)

# Get predicted probabilities for the positive class (churn = 1)
y_prob = lg.predict_proba(X_test)[:, 1]  # Probability for the positive class (churn = 1)

# Compute the ROC curve
fpr, tpr, thresholds = roc_curve(y_test, y_prob)

# Compute the AUC (Area Under the Curve)
roc_auc = auc(fpr, tpr)

# Plot the ROC curve
plt.figure(figsize=(8, 6))
plt.plot(fpr, tpr, color='darkorange', lw=2, label=f'ROC curve (AUC = {roc_auc:.2f})')
plt.plot([0, 1], [0, 1], color='navy', lw=2, linestyle='--')  # Diagonal line (random model)
plt.xlabel('False Positive Rate')
plt.ylabel('True Positive Rate')
plt.title('Receiver Operating Characteristic (ROC) Curve')
plt.legend(loc='lower right')
plt.show()
No description has been provided for this image

Support Vector Machines (SVM)¶

In [65]:
# Fitting Linear SVM model
svm = SVC(kernel='linear', probability=True, random_state=1)
svm.fit(X_train_scaled, y_train)
Out[65]:
SVC(kernel='linear', probability=True, random_state=1)
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
SVC(kernel='linear', probability=True, random_state=1)
In [66]:
# Checking the performance on the training data
y_pred_train_svm = svm.predict(X_train_scaled)
metrics_score(y_train, y_pred_train_svm)
              precision    recall  f1-score   support

           0       0.84      0.88      0.86      3622
           1       0.63      0.55      0.59      1308

    accuracy                           0.79      4930
   macro avg       0.74      0.72      0.72      4930
weighted avg       0.79      0.79      0.79      4930

No description has been provided for this image
In [69]:
# Checking the performance on the test dataset
y_pred_test_svm = svm.predict(X_test_scaled)
metrics_score(y_test, y_pred_test_svm)
              precision    recall  f1-score   support

           0       0.84      0.89      0.87      1552
           1       0.64      0.55      0.59       561

    accuracy                           0.80      2113
   macro avg       0.74      0.72      0.73      2113
weighted avg       0.79      0.80      0.79      2113

No description has been provided for this image
In [71]:
# Printing the coefficients of Logistic regression 

cols=X.columns 
coef_svm=svm.coef_ 
pd.DataFrame(coef_svm, columns=cols).T.sort_values(by = 0, ascending = False)

# Finding the odds 

odds = np.exp(svm.coef_[0])

# Adding the odds to a dataframe and sorting the values 

pd.DataFrame(odds, X_train_scaled.columns, columns = ['odds']).sort_values(by = 'odds', ascending = False)
Out[71]:
odds
InternetService_Fiber optic 2.255276
StreamingMovies_Yes 1.165376
StreamingTV_Yes 1.133543
MultipleLines_Yes 1.124441
MultipleLines_No phone service 1.045852
DeviceProtection_Yes 1.004508
StreamingTV_No internet service 0.957415
InternetService_No 0.957415
OnlineSecurity_No internet service 0.957415
StreamingMovies_No internet service 0.957415
OnlineBackup_No internet service 0.957415
DeviceProtection_No internet service 0.957415
TechSupport_No internet service 0.957415
PhoneService_Yes 0.956159
OnlineBackup_Yes 0.939551
TechSupport_Yes 0.891087
OnlineSecurity_Yes 0.873086
Contract_Two year 0.858707
Contract_One year 0.855589
MonthlyCharges 0.726118
tenure 0.465126
In [73]:
# Train a Logistic Regression model
svm.fit(X_train, y_train)

# Get predicted probabilities for the positive class (churn = 1)
y_prob = svm.predict_proba(X_test)[:, 1]  # Probability for the positive class (churn = 1)

# Compute the ROC curve
fpr, tpr, thresholds = roc_curve(y_test, y_prob)

# Compute the AUC (Area Under the Curve)
roc_auc = auc(fpr, tpr)

# Plot the ROC curve
plt.figure(figsize=(8, 6))
plt.plot(fpr, tpr, color='darkorange', lw=2, label=f'ROC curve (AUC = {roc_auc:.2f})')
plt.plot([0, 1], [0, 1], color='navy', lw=2, linestyle='--')  # Diagonal line (random model)
plt.xlabel('False Positive Rate')
plt.ylabel('True Positive Rate')
plt.title('Receiver Operating Characteristic (ROC) Curve')
plt.legend(loc='lower right')
plt.show()
No description has been provided for this image
In [ ]: