In [1]:
import pandas as pd 
import numpy as np 
import matplotlib.pyplot as plt 
import seaborn as sns 
from sklearn.preprocessing import StandardScaler 
from sklearn.model_selection import train_test_split 
from sklearn.linear_model import LogisticRegression 
from sklearn.svm import SVC 
from sklearn.impute import SimpleImputer 
from sklearn import metrics 
from sklearn.metrics import confusion_matrix, classification_report 
from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score, roc_auc_score, roc_curve
from sklearn.model_selection import GridSearchCV
import warnings 
warnings.filterwarnings("ignore")
In [2]:
# Loading the dataset
In [3]:
df = pd.read_excel('Telco_customer_churn.xlsx')
In [4]:
# Manually specifying the columns to be used
In [5]:
df = df[['CustomerID', 'Gender', 'Senior Citizen', 'Partner', 'Dependents', 'Tenure Months', 
         'Phone Service', 'Multiple Lines', 'Internet Service', 'Online Security', 
         'Online Backup', 'Device Protection', 'Tech Support', 'Streaming TV', 
         'Streaming Movies', 'Contract', 'Paperless Billing', 'Payment Method', 
         'Monthly Charges', 'Total Charges', 'Churn Label']]
In [6]:
# Remove superfluous data
In [7]:
df=df.drop(['CustomerID', 'Gender', 'Phone Service', 'Multiple Lines', 'Contract', 'Paperless Billing', 'Partner'], axis = 1)
In [8]:
# Convert 'Yes'/'No' values in 'Senior Citizen' to binary (1/0)
In [9]:
df['Senior Citizen'] = df['Senior Citizen'].replace({'Yes': 1, 'No': 0})
In [10]:
# Converting numeric columns that might have been read as strings
In [11]:
df['Senior Citizen'] = pd.to_numeric(df['Senior Citizen'], errors='coerce')
df['Tenure Months'] = pd.to_numeric(df['Tenure Months'], errors='coerce')
df['Monthly Charges'] = pd.to_numeric(df['Monthly Charges'], errors='coerce')
df['Total Charges'] = pd.to_numeric(df['Total Charges'], errors='coerce')
In [12]:
# Defining numerical columns
In [13]:
num_cols = ['Senior Citizen', 'Tenure Months', 'Monthly Charges', 'Total Charges']
In [14]:
# Separating numerical and categorical columns manually
In [15]:
cat_cols = [ 'Dependents', 
            'Internet Service', 'Online Security', 'Online Backup', 'Device Protection', 
            'Tech Support', 'Streaming TV', 'Streaming Movies', 
             'Payment Method', 'Churn Label']
In [16]:
# Checking for non-numeric values that might be causing issues
In [17]:
print("Non-numeric values in num_cols:")
print(df[num_cols].apply(pd.to_numeric, errors='coerce').notnull().all())
Non-numeric values in num_cols:
Senior Citizen      True
Tenure Months       True
Monthly Charges     True
Total Charges      False
dtype: bool
In [18]:
# Imputing missing values in numerical columns
In [19]:
imputer = SimpleImputer(strategy='mean')
df[num_cols] = pd.DataFrame(imputer.fit_transform(df[num_cols]), columns=num_cols)
In [20]:
# Checking the shape of the DataFrame
In [21]:
print("Shape of df[num_cols]:", df[num_cols].shape)
Shape of df[num_cols]: (7043, 4)
In [22]:
# Checking summary statistics
In [23]:
print(df[num_cols].describe().T)
                  count         mean          std    min      25%      50%  \
Senior Citizen   7043.0     0.162147     0.368612   0.00    0.000     0.00   
Tenure Months    7043.0    32.371149    24.559481   0.00    9.000    29.00   
Monthly Charges  7043.0    64.761692    30.090047  18.25   35.500    70.35   
Total Charges    7043.0  2283.300441  2265.000258  18.80  402.225  1400.55   

                     75%      max  
Senior Citizen      0.00     1.00  
Tenure Months      55.00    72.00  
Monthly Charges    89.85   118.75  
Total Charges    3786.60  8684.80  
In [24]:
# Creating histograms
In [25]:
df[num_cols].hist(figsize=(14, 14))
plt.show()
No description has been provided for this image
In [26]:
# Printing the % sub categories of each category
In [27]:
for col in cat_cols:
    print(df[col].value_counts(normalize=True))
    print('*' * 40)
Dependents
No     0.76899
Yes    0.23101
Name: proportion, dtype: float64
****************************************
Internet Service
Fiber optic    0.439585
DSL            0.343746
No             0.216669
Name: proportion, dtype: float64
****************************************
Online Security
No                     0.496663
Yes                    0.286668
No internet service    0.216669
Name: proportion, dtype: float64
****************************************
Online Backup
No                     0.438450
Yes                    0.344881
No internet service    0.216669
Name: proportion, dtype: float64
****************************************
Device Protection
No                     0.439443
Yes                    0.343888
No internet service    0.216669
Name: proportion, dtype: float64
****************************************
Tech Support
No                     0.493114
Yes                    0.290217
No internet service    0.216669
Name: proportion, dtype: float64
****************************************
Streaming TV
No                     0.398978
Yes                    0.384353
No internet service    0.216669
Name: proportion, dtype: float64
****************************************
Streaming Movies
No                     0.395428
Yes                    0.387903
No internet service    0.216669
Name: proportion, dtype: float64
****************************************
Payment Method
Electronic check             0.335794
Mailed check                 0.228880
Bank transfer (automatic)    0.219225
Credit card (automatic)      0.216101
Name: proportion, dtype: float64
****************************************
Churn Label
No     0.73463
Yes    0.26537
Name: proportion, dtype: float64
****************************************
In [28]:
for col in cat_cols:
    if col != 'Churn Label':
        (pd.crosstab(df[col], df['Churn Label'], normalize='index') * 100).plot(kind='bar', figsize=(8, 4), stacked=True)
        plt.ylabel('Percentage Churn %')
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 [29]:
# Plotting the correlation between numerical variables
In [30]:
plt.figure(figsize=(15, 8))
sns.heatmap(df[num_cols].corr(), annot=True, fmt='.2f', cmap='YlOrRd')
Out[30]:
<Axes: >
No description has been provided for this image
In [31]:
# Creating List of dummy columns
In [32]:
to_get_dummies_for = ['Dependents',  
                      'Internet Service', 'Online Security', 'Online Backup', 'Device Protection', 
                      'Tech Support', 'Streaming TV', 'Streaming Movies',  
                      'Payment Method']
In [33]:
# Creating dummy variables
In [34]:
df = pd.get_dummies(data=df, columns=to_get_dummies_for, drop_first=True)
In [35]:
# Mapping Churn Label
In [36]:
dict_churnLabel = {'Yes': 1, 'No': 0}
df['Churn Label'] = df['Churn Label'].map(dict_churnLabel)
In [37]:
# Separating target variable and other variables
In [38]:
Y = df['Churn Label']
X = df.drop(columns=['Churn Label'])
In [39]:
# Splitting the data
In [40]:
X_train, X_test, y_train, y_test = train_test_split(X, Y, test_size=0.3, random_state=1, stratify=Y)
In [41]:
# Scaling the data
In [42]:
sc = StandardScaler()
In [43]:
# Fit_transform on train data
In [44]:
X_train_scaled = sc.fit_transform(X_train)
X_train_scaled = pd.DataFrame(X_train_scaled, columns=X_train.columns)
In [45]:
# Transform on test data
In [46]:
X_test_scaled = sc.transform(X_test)
X_test_scaled = pd.DataFrame(X_test_scaled, columns=X_test.columns)
In [47]:
# Creating metric function
In [48]:
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 Churn', 'Churn'], yticklabels=['Not Churn', 'Churn'])
    plt.ylabel('Actual')
    plt.xlabel('Predicted')
    plt.show()
In [49]:
# Fitting Logistic Regression model
In [50]:
lg = LogisticRegression()
lg.fit(X_train_scaled, y_train)
Out[50]:
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 [51]:
# Checking the performance on the training data
In [52]:
y_pred_train_lg = lg.predict(X_train_scaled)
print("Logistic Regression - Training Data")
metrics_score(y_train, y_pred_train_lg)
Logistic Regression - Training Data
              precision    recall  f1-score   support

           0       0.84      0.90      0.87      3622
           1       0.66      0.54      0.60      1308

    accuracy                           0.81      4930
   macro avg       0.75      0.72      0.73      4930
weighted avg       0.80      0.81      0.80      4930

No description has been provided for this image
In [53]:
# Checking the performance on the test dataset
In [54]:
y_pred_test_lg = lg.predict(X_test_scaled)
print("Logistic Regression - Test Data")
metrics_score(y_test, y_pred_test_lg)
Logistic Regression - Test Data
              precision    recall  f1-score   support

           0       0.85      0.91      0.88      1552
           1       0.68      0.55      0.61       561

    accuracy                           0.81      2113
   macro avg       0.76      0.73      0.74      2113
weighted avg       0.80      0.81      0.80      2113

No description has been provided for this image
In [55]:
# Fitting SVM model
In [56]:
svm = SVC(kernel='linear')
svm.fit(X_train_scaled, y_train)
Out[56]:
SVC(kernel='linear')
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')
In [57]:
# Checking the performance on the training data
In [58]:
y_pred_train_svm = svm.predict(X_train_scaled)
print("SVM - Training Data")
metrics_score(y_train, y_pred_train_svm)
SVM - Training Data
              precision    recall  f1-score   support

           0       0.84      0.90      0.87      3622
           1       0.65      0.53      0.59      1308

    accuracy                           0.80      4930
   macro avg       0.75      0.72      0.73      4930
weighted avg       0.79      0.80      0.79      4930

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

           0       0.84      0.91      0.88      1552
           1       0.68      0.53      0.60       561

    accuracy                           0.81      2113
   macro avg       0.76      0.72      0.74      2113
weighted avg       0.80      0.81      0.80      2113

No description has been provided for this image
In [61]:
# Creating a function to print performance metrics
In [62]:
def print_performance_metrics(model_name, y_true, y_pred, y_pred_prob):
    print(f"{model_name} - Performance Metrics:")
    print(f"Accuracy: {accuracy_score(y_true, y_pred):.4f}")
    print(f"Precision: {precision_score(y_true, y_pred):.4f}")
    print(f"Recall: {recall_score(y_true, y_pred):.4f}")
    print(f"F1 Score: {f1_score(y_true, y_pred):.4f}")
    if y_pred_prob is not None:
        print(f"AUC-ROC: {roc_auc_score(y_true, y_pred_prob):.4f}")
    print("\n")
In [63]:
# Logistic Regression Evaluation
# Note: For Logistic Regression, we can directly use predict_proba for AUC-ROC
In [64]:
y_pred_train_lg = lg.predict(X_train_scaled)
y_pred_test_lg = lg.predict(X_test_scaled)
y_pred_train_prob_lg = lg.predict_proba(X_train_scaled)[:, 1]
y_pred_test_prob_lg = lg.predict_proba(X_test_scaled)[:, 1]

print_performance_metrics("Logistic Regression - Training Data", y_train, y_pred_train_lg, y_pred_train_prob_lg)
print_performance_metrics("Logistic Regression - Test Data", y_test, y_pred_test_lg, y_pred_test_prob_lg)
Logistic Regression - Training Data - Performance Metrics:
Accuracy: 0.8059
Precision: 0.6645
Recall: 0.5420
F1 Score: 0.5971
AUC-ROC: 0.8534


Logistic Regression - Test Data - Performance Metrics:
Accuracy: 0.8112
Precision: 0.6784
Recall: 0.5490
F1 Score: 0.6069
AUC-ROC: 0.8437


In [65]:
# SVM Evaluation
# Note: For SVM with linear kernel, use decision_function for AUC-ROC
In [66]:
svm = SVC(kernel='linear', probability=True)
svm.fit(X_train_scaled, y_train)

y_pred_train_svm = svm.predict(X_train_scaled)
y_pred_test_svm = svm.predict(X_test_scaled)
y_pred_train_prob_svm = svm.decision_function(X_train_scaled)
y_pred_test_prob_svm = svm.decision_function(X_test_scaled)

print_performance_metrics("Linear SVM - Training Data", y_train, y_pred_train_svm, y_pred_train_prob_svm)
print_performance_metrics("Linear SVM - Test Data", y_test, y_pred_test_svm, y_pred_test_prob_svm)
Linear SVM - Training Data - Performance Metrics:
Accuracy: 0.8010
Precision: 0.6529
Recall: 0.5336
F1 Score: 0.5873
AUC-ROC: 0.8487


Linear SVM - Test Data - Performance Metrics:
Accuracy: 0.8102
Precision: 0.6843
Recall: 0.5294
F1 Score: 0.5970
AUC-ROC: 0.8391


In [67]:
# Plotting AUC-ROC Curve for the Test Data
In [68]:
plt.figure(figsize=(12, 6))
fpr_lg, tpr_lg, _ = roc_curve(y_test, y_pred_test_prob_lg)
fpr_svm, tpr_svm, _ = roc_curve(y_test, y_pred_test_prob_svm)

plt.plot(fpr_lg, tpr_lg, label='Logistic Regression')
plt.plot(fpr_svm, tpr_svm, label='Linear SVM')
plt.plot([0, 1], [0, 1], 'k--')  # Diagonal line for random classifier
plt.xlabel('False Positive Rate')
plt.ylabel('True Positive Rate')
plt.title('ROC Curve')
plt.legend(loc='best')
plt.show()
No description has been provided for this image
In [69]:
# Logistic Regression hyperparameter tuning
In [70]:
param_grid_lr = {
    'C': [0.01, 0.1, 1, 10, 100],
    'solver': ['liblinear', 'saga']
}

grid_lr = GridSearchCV(LogisticRegression(), param_grid_lr, cv=5, scoring='accuracy')
grid_lr.fit(X_train_scaled, y_train)
Out[70]:
GridSearchCV(cv=5, estimator=LogisticRegression(),
             param_grid={'C': [0.01, 0.1, 1, 10, 100],
                         'solver': ['liblinear', 'saga']},
             scoring='accuracy')
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.
GridSearchCV(cv=5, estimator=LogisticRegression(),
             param_grid={'C': [0.01, 0.1, 1, 10, 100],
                         'solver': ['liblinear', 'saga']},
             scoring='accuracy')
LogisticRegression(C=10, solver='saga')
LogisticRegression(C=10, solver='saga')
In [71]:
# Best hyperparameters for Logistic Regression
In [72]:
print(f"Best parameters for Logistic Regression: {grid_lr.best_params_}")
best_lr = grid_lr.best_estimator_
Best parameters for Logistic Regression: {'C': 10, 'solver': 'saga'}
In [73]:
# Evaluation with best hyperparameters
In [74]:
y_pred_train_lr = best_lr.predict(X_train_scaled)
y_pred_test_lr = best_lr.predict(X_test_scaled)
y_pred_train_prob_lr = best_lr.predict_proba(X_train_scaled)[:, 1]
y_pred_test_prob_lr = best_lr.predict_proba(X_test_scaled)[:, 1]

print_performance_metrics("Tuned Logistic Regression - Training Data", y_train, y_pred_train_lr, y_pred_train_prob_lr)
print_performance_metrics("Tuned Logistic Regression - Test Data", y_test, y_pred_test_lr, y_pred_test_prob_lr)
Tuned Logistic Regression - Training Data - Performance Metrics:
Accuracy: 0.8057
Precision: 0.6645
Recall: 0.5405
F1 Score: 0.5961
AUC-ROC: 0.8534


Tuned Logistic Regression - Test Data - Performance Metrics:
Accuracy: 0.8107
Precision: 0.6777
Recall: 0.5472
F1 Score: 0.6055
AUC-ROC: 0.8436


In [75]:
# Linear SVM hyperparameter tuning
In [76]:
param_grid_svm = {
    'C': [0.1, 1, 10], # Reduced C values as processing time was taking too long
    'kernel': ['linear']
}

grid_svm = GridSearchCV(SVC(probability=True), param_grid_svm, cv=5, scoring='accuracy', n_jobs=-1)
grid_svm.fit(X_train_scaled, y_train)
Out[76]:
GridSearchCV(cv=5, estimator=SVC(probability=True), n_jobs=-1,
             param_grid={'C': [0.1, 1, 10], 'kernel': ['linear']},
             scoring='accuracy')
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.
GridSearchCV(cv=5, estimator=SVC(probability=True), n_jobs=-1,
             param_grid={'C': [0.1, 1, 10], 'kernel': ['linear']},
             scoring='accuracy')
SVC(C=10, kernel='linear', probability=True)
SVC(C=10, kernel='linear', probability=True)
In [77]:
# Best hyperparameters for Linear SVM
In [78]:
print(f"Best parameters for Linear SVM: {grid_svm.best_params_}")
best_svm = grid_svm.best_estimator_
Best parameters for Linear SVM: {'C': 10, 'kernel': 'linear'}
In [79]:
# Evaluation with best hyperparameters
In [80]:
y_pred_train_svm = best_svm.predict(X_train_scaled)
y_pred_test_svm = best_svm.predict(X_test_scaled)
y_pred_train_prob_svm = best_svm.decision_function(X_train_scaled)
y_pred_test_prob_svm = best_svm.decision_function(X_test_scaled)

print_performance_metrics("Tuned Linear SVM - Training Data", y_train, y_pred_train_svm, y_pred_train_prob_svm)
print_performance_metrics("Tuned Linear SVM - Test Data", y_test, y_pred_test_svm, y_pred_test_prob_svm)
Tuned Linear SVM - Training Data - Performance Metrics:
Accuracy: 0.8008
Precision: 0.6526
Recall: 0.5329
F1 Score: 0.5867
AUC-ROC: 0.8488


Tuned Linear SVM - Test Data - Performance Metrics:
Accuracy: 0.8102
Precision: 0.6843
Recall: 0.5294
F1 Score: 0.5970
AUC-ROC: 0.8393


In [81]:
# Plotting AUC-ROC Curve for the Test Data
In [82]:
plt.figure(figsize=(12, 6))
fpr_lr, tpr_lr, _ = roc_curve(y_test, y_pred_test_prob_lr)
fpr_svm, tpr_svm, _ = roc_curve(y_test, y_pred_test_prob_svm)

plt.plot(fpr_lr, tpr_lr, label='Tuned Logistic Regression')
plt.plot(fpr_svm, tpr_svm, label='Tuned Linear SVM')
plt.plot([0, 1], [0, 1], 'k--')  # Diagonal line for random classifier
plt.xlabel('False Positive Rate')
plt.ylabel('True Positive Rate')
plt.title('ROC Curve')
plt.legend(loc='best')
plt.show()
No description has been provided for this image