Import all Libraries¶

In [2]:
# Import necessary libraries
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns

# Scaling and splitting data
from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import train_test_split

# ML Algos
from sklearn.linear_model import LogisticRegression
from sklearn.svm import SVC
from sklearn.model_selection import GridSearchCV

# Evaluation metrics
from sklearn.metrics import confusion_matrix, classification_report, roc_auc_score, roc_curve

# Suppress warnings
import warnings
warnings.filterwarnings("ignore")
In [3]:
# Function to plot confusion matrix for later 
def plot_confusion_matrix(y_true, y_pred, title="Confusion Matrix"):
    cm = confusion_matrix(y_true, y_pred)
    plt.figure(figsize=(8, 6))
    sns.heatmap(cm, annot=True, fmt='d', cmap='Blues', xticklabels=['Not Churn', 'Churn'], yticklabels=['Not Churn', 'Churn'])
    plt.xlabel("Predicted")
    plt.ylabel("Actual")
    plt.title(title)
    plt.show()

Load the Dataset¶

In [5]:
# Load the dataset
df = pd.read_csv('customerChurnDataset.csv')

# Preview the first few rows of data
df.head()
Out[5]:
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

Initial Data Exploration¶

In [7]:
# Check the structure of the dataset
df.info()

# Count unique values in each column
df.nunique()
<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
Out[7]:
customerID          7043
gender                 2
SeniorCitizen          2
Partner                2
Dependents             2
tenure                73
PhoneService           2
MultipleLines          3
InternetService        3
OnlineSecurity         3
OnlineBackup           3
DeviceProtection       3
TechSupport            3
StreamingTV            3
StreamingMovies        3
Contract               3
PaperlessBilling       2
PaymentMethod          4
MonthlyCharges      1585
TotalCharges        6531
Churn                  2
dtype: int64

Handling missing and invalid data¶

In [9]:
# Convert TotalCharges to numeric and handle errors
df['TotalCharges'] = pd.to_numeric(df['TotalCharges'], errors='coerce')

# Check missing values in TotalCharges
missing_total_charges = df['TotalCharges'].isna().sum()
print(f"Missing values in TotalCharges: {missing_total_charges}")

# Drop rows with missing TotalCharges as they constitute only a small percentage
df = df.dropna(subset=['TotalCharges'])
Missing values in TotalCharges: 11

Drop Unnecessary Columns¶

In [11]:
# Drop columns irrelevant to analysis
df = df.drop(['customerID'], axis=1)

Separate Numerical and Categorical Features¶

In [13]:
# Identify numerical and categorical columns
num_cols = ['tenure', 'MonthlyCharges', 'TotalCharges']
cat_cols = ['gender', 'SeniorCitizen', 'Partner', 'Dependents', 'PhoneService', 
            'MultipleLines', 'InternetService', 'OnlineSecurity', 'OnlineBackup', 
            'DeviceProtection', 'TechSupport', 'StreamingTV', 'StreamingMovies', 
            'Contract', 'PaperlessBilling', 'PaymentMethod', 'Churn']

Looking at distribution for Categorical Features¶

In [15]:
# Distribution for each categorical feature
for col in cat_cols:
    print(f"Distribution for {col}:")
    print(df[col].value_counts(normalize=True) * 100)  # Shows percentages
    print('*' * 40)
Distribution for gender:
gender
Male      50.469283
Female    49.530717
Name: proportion, dtype: float64
****************************************
Distribution for SeniorCitizen:
SeniorCitizen
0    83.759954
1    16.240046
Name: proportion, dtype: float64
****************************************
Distribution for Partner:
Partner
No     51.749147
Yes    48.250853
Name: proportion, dtype: float64
****************************************
Distribution for Dependents:
Dependents
No     70.150739
Yes    29.849261
Name: proportion, dtype: float64
****************************************
Distribution for PhoneService:
PhoneService
Yes    90.32992
No      9.67008
Name: proportion, dtype: float64
****************************************
Distribution for MultipleLines:
MultipleLines
No                  48.137088
Yes                 42.192833
No phone service     9.670080
Name: proportion, dtype: float64
****************************************
Distribution for InternetService:
InternetService
Fiber optic    44.027304
DSL            34.357224
No             21.615472
Name: proportion, dtype: float64
****************************************
Distribution for OnlineSecurity:
OnlineSecurity
No                     49.729807
Yes                    28.654721
No internet service    21.615472
Name: proportion, dtype: float64
****************************************
Distribution for OnlineBackup:
OnlineBackup
No                     43.899317
Yes                    34.485210
No internet service    21.615472
Name: proportion, dtype: float64
****************************************
Distribution for DeviceProtection:
DeviceProtection
No                     43.998862
Yes                    34.385666
No internet service    21.615472
Name: proportion, dtype: float64
****************************************
Distribution for TechSupport:
TechSupport
No                     49.374289
Yes                    29.010239
No internet service    21.615472
Name: proportion, dtype: float64
****************************************
Distribution for StreamingTV:
StreamingTV
No                     39.945961
Yes                    38.438567
No internet service    21.615472
Name: proportion, dtype: float64
****************************************
Distribution for StreamingMovies:
StreamingMovies
No                     39.547782
Yes                    38.836746
No internet service    21.615472
Name: proportion, dtype: float64
****************************************
Distribution for Contract:
Contract
Month-to-month    55.105233
Two year          23.961889
One year          20.932878
Name: proportion, dtype: float64
****************************************
Distribution for PaperlessBilling:
PaperlessBilling
Yes    59.2719
No     40.7281
Name: proportion, dtype: float64
****************************************
Distribution for PaymentMethod:
PaymentMethod
Electronic check             33.631968
Mailed check                 22.810011
Bank transfer (automatic)    21.928328
Credit card (automatic)      21.629693
Name: proportion, dtype: float64
****************************************
Distribution for Churn:
Churn
No     73.421502
Yes    26.578498
Name: proportion, dtype: float64
****************************************

Explore Data Distribution/Relationships¶

In [17]:
# Histogram for numerical columns
df[num_cols].hist(figsize=(14, 14))
plt.show()

# Bar plots for categorical columns
for col in cat_cols:
    if col != 'Churn':
        (pd.crosstab(df[col], df['Churn'], normalize='index') * 100).plot(kind='bar', stacked=True, figsize=(8, 4))
        plt.ylabel('Percentage Churn %')
        plt.show()
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
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

Encode Categorical Features¶

In [19]:
# Map binary categories
binary_mapping = {'Yes': 1, 'No': 0}
df['gender'] = df['gender'].map({'Female': 0, 'Male': 1})
df['Partner'] = df['Partner'].map(binary_mapping)
df['Dependents'] = df['Dependents'].map(binary_mapping)
df['PhoneService'] = df['PhoneService'].map(binary_mapping)
df['PaperlessBilling'] = df['PaperlessBilling'].map(binary_mapping)
df['Churn'] = df['Churn'].map(binary_mapping)

# One-hot encode multi-category columns
multi_cat_cols = ['MultipleLines', 'InternetService', 'OnlineSecurity', 'OnlineBackup',
                  'DeviceProtection', 'TechSupport', 'StreamingTV', 'StreamingMovies',
                  'Contract', 'PaymentMethod']

df = pd.get_dummies(df, columns=multi_cat_cols, drop_first=True)

Looking at significant monthly increases in cost¶

In [21]:
# Calculate the ratio of customer's monthly charge to the average monthly charge in their tenure bracket
df['MonthlyChargeChange'] = df['MonthlyCharges'] / df.groupby('tenure')['MonthlyCharges'].transform('mean')

# Flag customers with a significant increase 1.2 = 20%
df['SignificantIncrease'] = df['MonthlyChargeChange'] > 1.2  # This number can be adjusted if end user wants.

# Calculate churn rates for customers with and without a significant increase
churn_rate_by_change = df.groupby('SignificantIncrease')['Churn'].mean()

print("Churn rates for significant increase in monthly charges:")
print(churn_rate_by_change)

# Plot the churn rates for visualisation
plt.figure(figsize=(8, 6))
churn_rate_by_change.plot(kind='bar', color=['skyblue', 'orange'])
plt.title('Churn Rates for Significant Increase in Monthly Charges')
plt.xlabel('Significant Increase in Monthly Charges')
plt.ylabel('Churn Rate')
plt.xticks(ticks=[0, 1], labels=['No Significant Increase', 'Significant Increase'], rotation=0)
plt.show()
Churn rates for significant increase in monthly charges:
SignificantIncrease
False    0.157494
True     0.414585
Name: Churn, dtype: float64
No description has been provided for this image

Prepare Data for Model Training¶

In [23]:
# Separate target and features
Y = df['Churn']
X = df.drop(columns=['Churn'])

# Split data into training and test sets
X_train, X_test, y_train, y_test = train_test_split(X, Y, test_size=0.3, random_state=1, stratify=Y)

# Scale numerical features
scaler = StandardScaler()
X_train_scaled = X_train.copy()
X_test_scaled = X_test.copy()
X_train_scaled[num_cols] = scaler.fit_transform(X_train[num_cols])
X_test_scaled[num_cols] = scaler.transform(X_test[num_cols])

Train & Evaluate Logistic Regression Model¶

In [25]:
# Initialize Logistic Regression
lg = LogisticRegression(class_weight='balanced', solver='liblinear', random_state=42)

# Train the model
lg.fit(X_train_scaled, y_train)

# Evaluate on training and test data
y_pred_train = lg.predict(X_train_scaled)
y_pred_test = lg.predict(X_test_scaled)

print("Training Metrics for Logistic Regression:")
print(classification_report(y_train, y_pred_train))

print("Test Metrics for Logistic Regression:")
print(classification_report(y_test, y_pred_test))
Training Metrics for Logistic Regression:
              precision    recall  f1-score   support

           0       0.92      0.73      0.81      3614
           1       0.52      0.82      0.64      1308

    accuracy                           0.75      4922
   macro avg       0.72      0.77      0.73      4922
weighted avg       0.81      0.75      0.77      4922

Test Metrics for Logistic Regression:
              precision    recall  f1-score   support

           0       0.91      0.71      0.80      1549
           1       0.50      0.80      0.62       561

    accuracy                           0.74      2110
   macro avg       0.71      0.76      0.71      2110
weighted avg       0.80      0.74      0.75      2110

In [26]:
plot_confusion_matrix(y_test, y_pred_test, title="Confusion Matrix for Logistic Regression (Test Data)")
No description has been provided for this image

Hyperparameter Tuning for LR Model¶

In [28]:
# Define parameter grid for Logistic Regression
param_grid_lr = {
    'C': [0.01, 0.1, 1, 10, 100],  # Test a range of regularization strengths
}

# Initialize GridSearchCV
grid_search_lr = GridSearchCV(
    estimator=LogisticRegression(class_weight='balanced', solver='liblinear', random_state=42),
    param_grid=param_grid_lr,
    scoring='f1_weighted',  # Optimize for F1-score
    cv=3,                   # Use 3-fold cross-validation
    verbose=1               # Display progress
)

# Fit the model on the training data
grid_search_lr.fit(X_train_scaled, y_train)

# Retrieve the best parameters and score
print("Best Parameters for Logistic Regression:", grid_search_lr.best_params_)
print("Best F1-Score from GridSearchCV:", grid_search_lr.best_score_)
Fitting 3 folds for each of 5 candidates, totalling 15 fits
Best Parameters for Logistic Regression: {'C': 0.1}
Best F1-Score from GridSearchCV: 0.763318810176763
In [29]:
# Initialize the model with the best parameters
best_params_lr = grid_search_lr.best_params_
tuned_lr = LogisticRegression(
    C=best_params_lr['C'],
    class_weight='balanced',
    solver='liblinear',
    random_state=42
)

# Train the tuned model
tuned_lr.fit(X_train_scaled, y_train)

# Generate predictions for training and test sets
y_pred_train_tuned_lr = tuned_lr.predict(X_train_scaled)
y_pred_test_tuned_lr = tuned_lr.predict(X_test_scaled)

# Print classification reports
print("Training Metrics for Tuned Logistic Regression:")
print(classification_report(y_train, y_pred_train_tuned_lr))

print("Test Metrics for Tuned Logistic Regression:")
print(classification_report(y_test, y_pred_test_tuned_lr))
Training Metrics for Tuned Logistic Regression:
              precision    recall  f1-score   support

           0       0.91      0.74      0.82      3614
           1       0.53      0.81      0.64      1308

    accuracy                           0.76      4922
   macro avg       0.72      0.77      0.73      4922
weighted avg       0.81      0.76      0.77      4922

Test Metrics for Tuned Logistic Regression:
              precision    recall  f1-score   support

           0       0.91      0.72      0.80      1549
           1       0.51      0.80      0.62       561

    accuracy                           0.74      2110
   macro avg       0.71      0.76      0.71      2110
weighted avg       0.80      0.74      0.76      2110

In [30]:
plot_confusion_matrix(y_test, y_pred_test_tuned_lr, title="Confusion Matrix for Tuned Logistic Regression")
No description has been provided for this image

LR AUC-ROC Curve¶

In [32]:
# Get predicted probabilities for the positive class
y_proba_test_tuned_lr = tuned_lr.predict_proba(X_test_scaled)[:, 1]

# Compute AUC-ROC score
auc_score_tuned_lr = roc_auc_score(y_test, y_proba_test_tuned_lr)
print(f"AUC-ROC Score for Tuned Logistic Regression: {auc_score_tuned_lr:.2f}")

# Plot ROC Curve
fpr, tpr, thresholds = roc_curve(y_test, y_proba_test_tuned_lr)
plt.figure(figsize=(8, 6))
plt.plot(fpr, tpr, label=f"AUC-ROC = {auc_score_tuned_lr:.2f}", color='blue')
plt.plot([0, 1], [0, 1], linestyle='--', color='red')
plt.xlabel('False Positive Rate')
plt.ylabel('True Positive Rate')
plt.title('ROC Curve for Tuned Logistic Regression')
plt.legend()
plt.grid()
plt.show()
AUC-ROC Score for Tuned Logistic Regression: 0.84
No description has been provided for this image

Predicted Probabilities using LR¶

In [34]:
# Get predicted probabilities for the test set
y_proba_test = lg.predict_proba(X_test_scaled)[:, 1]

custom_threshold = 0.5
y_pred_custom = (y_proba_test >= custom_threshold).astype(int)
print(classification_report(y_test, y_pred_custom))

results_df = pd.DataFrame({
    'Actual': y_test,
    'Predicted': y_pred_test,  # Predictions with default threshold
    'Probability': y_proba_test  # Predicted probabilities
})
print(results_df.head())

# Compute AUC-ROC score
auc_score_lr = roc_auc_score(y_test, y_proba_test)
print(f"AUC-ROC Score for Logistic Regression: {auc_score_lr:.2f}")

# Plot ROC Curve
fpr, tpr, thresholds = roc_curve(y_test, y_proba_test)
plt.figure(figsize=(8, 6))
plt.plot(fpr, tpr, label=f"AUC-ROC = {auc_score_lr:.2f}", color='blue')
plt.plot([0, 1], [0, 1], linestyle='--', color='red')
plt.xlabel('False Positive Rate')
plt.ylabel('True Positive Rate')
plt.title('ROC Curve for Logistic Regression')
plt.legend()
plt.grid()
plt.show()
              precision    recall  f1-score   support

           0       0.91      0.71      0.80      1549
           1       0.50      0.80      0.62       561

    accuracy                           0.74      2110
   macro avg       0.71      0.76      0.71      2110
weighted avg       0.80      0.74      0.75      2110

      Actual  Predicted  Probability
2057       1          1     0.568074
5570       0          1     0.707883
6528       0          0     0.003269
5718       1          1     0.822459
1495       0          0     0.400832
AUC-ROC Score for Logistic Regression: 0.84
No description has been provided for this image

LR Coefficients¶

In [36]:
# Extract feature coefficients from Logistic Regression
coef_df = pd.DataFrame(tuned_lr.coef_.T, index=X.columns, columns=['Coefficient'])

# Sort coefficients by their magnitude (absolute value)
coef_df['Absolute Coefficient'] = coef_df['Coefficient'].abs()
coef_df = coef_df.sort_values(by='Absolute Coefficient', ascending=False)

# Display the coefficients
print("Feature Coefficients for Logistic Regression:")
print(coef_df)
Feature Coefficients for Logistic Regression:
                                       Coefficient  Absolute Coefficient
Contract_Two year                        -1.339367              1.339367
tenure                                   -1.153744              1.153744
Contract_One year                        -0.694445              0.694445
PhoneService                             -0.564666              0.564666
MonthlyChargeChange                       0.551818              0.551818
TotalCharges                              0.507652              0.507652
InternetService_Fiber optic               0.473490              0.473490
OnlineSecurity_Yes                       -0.423016              0.423016
OnlineBackup_Yes                         -0.371953              0.371953
TechSupport_Yes                          -0.336525              0.336525
PaymentMethod_Electronic check            0.307830              0.307830
PaperlessBilling                          0.284131              0.284131
MultipleLines_No phone service            0.255227              0.255227
SeniorCitizen                             0.185125              0.185125
Dependents                               -0.184105              0.184105
MonthlyCharges                            0.182472              0.182472
MultipleLines_Yes                         0.129734              0.129734
StreamingTV_Yes                           0.099081              0.099081
StreamingMovies_Yes                       0.082191              0.082191
StreamingTV_No internet service          -0.064061              0.064061
StreamingMovies_No internet service      -0.064061              0.064061
DeviceProtection_No internet service     -0.064061              0.064061
TechSupport_No internet service          -0.064061              0.064061
OnlineBackup_No internet service         -0.064061              0.064061
InternetService_No                       -0.064061              0.064061
OnlineSecurity_No internet service       -0.064061              0.064061
SignificantIncrease                      -0.057049              0.057049
DeviceProtection_Yes                     -0.056260              0.056260
PaymentMethod_Credit card (automatic)    -0.055827              0.055827
gender                                   -0.046232              0.046232
Partner                                   0.003036              0.003036
PaymentMethod_Mailed check                0.000224              0.000224

Initiate/Train SVM model¶

In [38]:
# Initialise the SVM model with default parameters
svm = SVC(kernel='rbf', C=1.0, gamma='scale', probability=True, random_state=42)

# Train the SVM model
svm.fit(X_train_scaled, y_train)

# Evaluate on training data
y_pred_train_svm = svm.predict(X_train_scaled)
print("Training Metrics for SVM:")
print(classification_report(y_train, y_pred_train_svm))

# Evaluate on test data
y_pred_test_svm = svm.predict(X_test_scaled)
print("Test Metrics for SVM:")
print(classification_report(y_test, y_pred_test_svm))
Training Metrics for SVM:
              precision    recall  f1-score   support

           0       0.84      0.93      0.88      3614
           1       0.72      0.50      0.59      1308

    accuracy                           0.82      4922
   macro avg       0.78      0.72      0.74      4922
weighted avg       0.81      0.82      0.80      4922

Test Metrics for SVM:
              precision    recall  f1-score   support

           0       0.83      0.91      0.87      1549
           1       0.67      0.48      0.56       561

    accuracy                           0.80      2110
   macro avg       0.75      0.70      0.72      2110
weighted avg       0.79      0.80      0.79      2110

SVM Predictions AUC-ROC curve¶

In [40]:
# Get predicted probabilities for the positive class (Class 1 - Churners)
y_proba_test_svm = svm.predict_proba(X_test_scaled)[:, 1]

# Compute AUC-ROC score for SVM
auc_score_svm = roc_auc_score(y_test, y_proba_test_svm)
print(f"AUC-ROC Score for SVM: {auc_score_svm:.2f}")

# Plot ROC Curve for SVM
fpr, tpr, thresholds = roc_curve(y_test, y_proba_test_svm)
plt.figure(figsize=(8, 6))
plt.plot(fpr, tpr, label=f"AUC-ROC = {auc_score_svm:.2f}", color='blue')
plt.plot([0, 1], [0, 1], linestyle='--', color='red')
plt.xlabel('False Positive Rate')
plt.ylabel('True Positive Rate')
plt.title('ROC Curve for SVM')
plt.legend()
plt.grid()
plt.show()
AUC-ROC Score for SVM: 0.79
No description has been provided for this image

SVM Confusion Matrix¶

In [42]:
plot_confusion_matrix(y_test, y_pred_test_svm, title="Confusion Matrix for SVM (Test Data)")
No description has been provided for this image

Hyperparameter Tuning for SVM model¶

In [44]:
# Define parameter grid for GridSearchCV
param_grid_svm = {
    'C': [1, 10],               # Regularization parameter
    'gamma': ['scale'],         # Kernel coefficient
    'kernel': ['linear', 'poly', 'rbf'],  # Different kernels
    'class_weight': ['balanced']  # Handle class imbalance
}

# Initialize GridSearchCV for SVM
grid_search_svm = GridSearchCV(
    estimator=SVC(probability=True, random_state=42),
    param_grid=param_grid_svm,
    scoring='f1_weighted',      # Optimize for F1-score
    cv=3,                       # 3-fold cross-validation
    verbose=1                   # Display progress
)

# Fit GridSearchCV on the training data
grid_search_svm.fit(X_train_scaled, y_train)

# Print the best parameters and F1-Score
print("Best Parameters for SVM:", grid_search_svm.best_params_)
print("Best F1-Score from GridSearchCV:", grid_search_svm.best_score_)
Fitting 3 folds for each of 6 candidates, totalling 18 fits
Best Parameters for SVM: {'C': 1, 'class_weight': 'balanced', 'gamma': 'scale', 'kernel': 'rbf'}
Best F1-Score from GridSearchCV: 0.7627160151099149

Evaluating tuned SVM Model¶

In [46]:
# Initialise SVM with best parameters from GridSearchCV
best_params_svm = grid_search_svm.best_params_
tuned_svm = SVC(
    C=best_params_svm['C'],
    gamma=best_params_svm['gamma'],
    kernel=best_params_svm['kernel'],
    class_weight=best_params_svm['class_weight'],
    probability=True,
    random_state=42
)

# Train the tuned SVM model
tuned_svm.fit(X_train_scaled, y_train)

# Generate predictions for training and test sets
y_pred_train_tuned_svm = tuned_svm.predict(X_train_scaled)
y_pred_test_tuned_svm = tuned_svm.predict(X_test_scaled)

# Print classification reports
print("Training Metrics for Tuned SVM:")
print(classification_report(y_train, y_pred_train_tuned_svm))

print("Test Metrics for Tuned SVM:")
print(classification_report(y_test, y_pred_test_tuned_svm))

# Compute AUC-ROC for tuned SVM
y_proba_test_tuned_svm = tuned_svm.predict_proba(X_test_scaled)[:, 1]
auc_score_tuned_svm = roc_auc_score(y_test, y_proba_test_tuned_svm)
print(f"AUC-ROC Score for Tuned SVM: {auc_score_tuned_svm:.2f}")

# Plot ROC Curve for Tuned SVM
fpr, tpr, thresholds = roc_curve(y_test, y_proba_test_tuned_svm)
plt.figure(figsize=(8, 6))
plt.plot(fpr, tpr, label=f"AUC-ROC = {auc_score_tuned_svm:.2f}", color='blue')
plt.plot([0, 1], [0, 1], linestyle='--', color='red')
plt.xlabel('False Positive Rate')
plt.ylabel('True Positive Rate')
plt.title('ROC Curve for Tuned SVM')
plt.legend()
plt.grid()
plt.show()
Training Metrics for Tuned SVM:
              precision    recall  f1-score   support

           0       0.92      0.75      0.83      3614
           1       0.54      0.83      0.66      1308

    accuracy                           0.77      4922
   macro avg       0.73      0.79      0.74      4922
weighted avg       0.82      0.77      0.78      4922

Test Metrics for Tuned SVM:
              precision    recall  f1-score   support

           0       0.90      0.72      0.80      1549
           1       0.50      0.79      0.61       561

    accuracy                           0.73      2110
   macro avg       0.70      0.75      0.71      2110
weighted avg       0.80      0.73      0.75      2110

AUC-ROC Score for Tuned SVM: 0.82
No description has been provided for this image
In [47]:
plot_confusion_matrix(y_test, y_pred_test_tuned_svm, title="Confusion Matrix for Tuned SVM")
No description has been provided for this image

Additional Visualisations used in Assignment¶

In [49]:
# Plot the distribution of churned and non-churned customers
df['Churn'].value_counts().plot(kind='bar', color=['skyblue', 'orange'])
plt.title('Churn Distribution')
plt.xlabel('Churn')
plt.ylabel('Count')
plt.xticks(ticks=[0, 1], labels=['No', 'Yes'], rotation=0)
plt.show()
No description has been provided for this image

Looking at cost of Internet Service Type¶

In [51]:
# Add a decoded InternetService column for visualization
def decode_internet_service(row):
    if row['InternetService_Fiber optic'] == 1:
        return 'Fiber optic'
    elif row['InternetService_No'] == 1:
        return 'No Internet'
    else:
        return 'DSL'

df['InternetServiceDecoded'] = df.apply(decode_internet_service, axis=1)
In [52]:
# Boxplot for MonthlyCharges by Internet Service Type
plt.figure(figsize=(8, 6))
sns.boxplot(data=df, x='InternetServiceDecoded', y='MonthlyCharges', palette='pastel')
plt.title('Monthly Charges by Internet Service Type', fontsize=14)
plt.xlabel('Internet Service Type', fontsize=12)
plt.ylabel('Monthly Charges', fontsize=12)
plt.show()
No description has been provided for this image
In [ ]:
 
In [ ]:
 
In [ ]:
 
In [ ]:
 
In [ ]:
 
In [ ]:
 
In [ ]:
 
In [ ]: