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()
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
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)")
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")
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
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
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
SVM Confusion Matrix¶
In [42]:
plot_confusion_matrix(y_test, y_pred_test_svm, title="Confusion Matrix for SVM (Test Data)")
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
In [47]:
plot_confusion_matrix(y_test, y_pred_test_tuned_svm, title="Confusion Matrix for Tuned SVM")
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()
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()
In [ ]:
In [ ]:
In [ ]:
In [ ]:
In [ ]:
In [ ]:
In [ ]:
In [ ]: