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()
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 ****************************************
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: >
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
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
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()
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
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
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()
In [ ]: