Import required modules¶
In [1]:
# Import required libraries for data manipulation, machine learning, visualization and utility functions
import pandas as pd
from sklearn.model_selection import train_test_split, GridSearchCV, learning_curve
from sklearn.preprocessing import StandardScaler, LabelEncoder
from sklearn.linear_model import LogisticRegression
from sklearn.svm import SVC
from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score, roc_auc_score, classification_report, confusion_matrix, roc_curve
import matplotlib.pyplot as plt
import seaborn as sns
import time
import numpy as np
Import dataset¶
In [2]:
# Load the telco customer churn dataset from CSV file
data = pd.read_csv('Simple_Telco_Customer_Churn.csv')
In [3]:
# Clean TotalCharges column by converting string values to float
# Handle empty or space values by setting them to None
newTotalCharges = []
for i in data['TotalCharges']:
try:
newValue = float(i)
except:
newValue = None
finally:
newTotalCharges.append(newValue)
data['TotalCharges'] = newTotalCharges
data = data.dropna(subset=['TotalCharges'])
In [4]:
# Separate numerical and categorical columns for preprocessing
all_cols = data.columns
num_cols = list(data._get_numeric_data().columns)
cat_cols = list(set(all_cols) - set(num_cols))
# Remove ID and target columns from categorical columns list
cat_cols.remove('CustomerID')
cat_cols.remove('Churn')
print(cat_cols)
print(num_cols)
['StreamingTV', 'gender', 'Contract', 'OnlineBackup', 'PaperlessBilling', 'PaymentMethod', 'Partner', 'StreamingMovies', 'MultipleLines', 'Dependents', 'TechSupport', 'InternetService', 'PhoneService', 'OnlineSecurity', 'DeviceProtection'] ['SeniorCitizen', 'tenure', 'MonthlyCharges', 'TotalCharges']
Display dateset information¶
In [5]:
# Display dataset information and basic statistics
print("Dataset Info:")
print(data.info())
print("\nBasic Statistics:")
print(data.describe())
print("\nClass Distribution:")
print(data['Churn'].value_counts(normalize=True))
# Create correlation heatmap for numerical features
plt.figure(figsize=(15,8))
sns.heatmap(data[num_cols].corr(), annot=True, fmt='0.2f', cmap='YlOrRd')
plt.title('Feature Correlations')
Dataset Info:
<class 'pandas.core.frame.DataFrame'>
Index: 7032 entries, 0 to 7042
Data columns (total 21 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 CustomerID 7032 non-null object
1 gender 7032 non-null object
2 SeniorCitizen 7032 non-null int64
3 Partner 7032 non-null object
4 Dependents 7032 non-null object
5 tenure 7032 non-null int64
6 PhoneService 7032 non-null object
7 MultipleLines 7032 non-null object
8 InternetService 7032 non-null object
9 OnlineSecurity 7032 non-null object
10 OnlineBackup 7032 non-null object
11 DeviceProtection 7032 non-null object
12 TechSupport 7032 non-null object
13 StreamingTV 7032 non-null object
14 StreamingMovies 7032 non-null object
15 Contract 7032 non-null object
16 PaperlessBilling 7032 non-null object
17 PaymentMethod 7032 non-null object
18 MonthlyCharges 7032 non-null float64
19 TotalCharges 7032 non-null float64
20 Churn 7032 non-null object
dtypes: float64(2), int64(2), object(17)
memory usage: 1.2+ MB
None
Basic Statistics:
SeniorCitizen tenure MonthlyCharges TotalCharges
count 7032.000000 7032.000000 7032.000000 7032.000000
mean 0.162400 32.421786 64.798208 2283.300441
std 0.368844 24.545260 30.085974 2266.771362
min 0.000000 1.000000 18.250000 18.800000
25% 0.000000 9.000000 35.587500 401.450000
50% 0.000000 29.000000 70.350000 1397.475000
75% 0.000000 55.000000 89.862500 3794.737500
max 1.000000 72.000000 118.750000 8684.800000
Class Distribution:
Churn
No 0.734215
Yes 0.265785
Name: proportion, dtype: float64
Out[5]:
Text(0.5, 1.0, 'Feature Correlations')
In [6]:
# Remove CustomerID as it's unique for each row and not useful for prediction
data = data.drop('CustomerID', axis=1)
Encode categorical data¶
In [7]:
# Convert categorical variables to dummy variables
encodedData = pd.get_dummies(data[cat_cols])
data = pd.concat([data[num_cols], data['Churn'], encodedData], axis=1)
# Convert Churn to binary values (0/1)
data['Churn'] = (data['Churn'] == 'Yes').astype(int)
Split features and target¶
In [8]:
# Separate features (X) and target variable (Y)
X = data.drop('Churn', axis=1) # Features
Y = data['Churn'] # Target
Split the data¶
In [9]:
# Split data into training (70%) and testing (30%) sets
X_train, X_test, Y_train, Y_test = train_test_split(X, Y, test_size=0.3, random_state=42)
Scale features¶
In [10]:
# Standardize features to have zero mean and unit variance
scaler = StandardScaler()
X_train = scaler.fit_transform(X_train)
X_test = scaler.transform(X_test)
Train data using logistic regression and SVM¶
In [11]:
# Train Logistic Regression model and measure training time
startTime = time.time()
lr_model = LogisticRegression(max_iter=100000)
lr_model.fit(X_train, Y_train)
lr_predictions = lr_model.predict(X_test)
endTime = time.time()
print(f"Linear Regression took {round(endTime-startTime,2)} seconds to finish")
Linear Regression took 0.02 seconds to finish
In [12]:
# Train Support Vector Machine model and measure training time
startTime = time.time()
svm_model = SVC(probability=True, max_iter=-1)
svm_model.fit(X_train, Y_train)
svm_predictions = svm_model.predict(X_test)
endTime = time.time()
print(f"Support Vector Machine took {round(endTime-startTime,2)} seconds to finish")
Support Vector Machine took 2.29 seconds to finish
Model evaluation¶
In [13]:
# Function to evaluate model performance using various metrics
def evaluate_model(y_test, y_pred, y_proba, model_name):
averageMode = "macro"
posLabelMode = "Pos"
multiClassMode = "ovo"
# Calculate performance metrics
accuracy = accuracy_score(y_test, y_pred)
precision = precision_score(y_test, y_pred, average=averageMode)
recall = recall_score(y_test, y_pred, average=averageMode)
f1 = f1_score(y_test, y_pred, average=averageMode)
auc = roc_auc_score(y_test, y_proba, multi_class=multiClassMode)
# Create and plot confusion matrix
plt.figure(figsize=(6, 6))
cm = confusion_matrix(y_test, y_pred)
sns.heatmap(cm, annot=True, fmt='d', cmap='Blues')
plt.title(f'Confusion Matrix - {model_name}')
plt.ylabel('True Label')
plt.xlabel('Predicted Label')
# Create and plot ROC curve
plt.figure(figsize=(6, 6))
fpr, tpr, _ = roc_curve(y_test, y_proba, pos_label=1)
plt.plot(fpr, tpr)
plt.plot([0, 1], [0, 1], 'k--')
plt.xlabel('False Positive Rate')
plt.ylabel('True Positive Rate')
plt.title(f'ROC Curve - {model_name}')
return accuracy, precision, recall, f1, auc
In [14]:
# Evaluate Logistic Regression model
lr_proba = lr_model.predict_proba(X_test)[:,1]
lr_metrics = evaluate_model(Y_test, lr_predictions, lr_proba, "Logistic Regression")
In [15]:
# Evaluate SVM model
svm_proba = svm_model.predict_proba(X_test)[:,1]
svm_metrics = evaluate_model(Y_test, svm_predictions, svm_proba, "SVM")
Metrics information¶
In [16]:
# Function to display model metrics in formatted output
def displayResults(metrics):
decimalPlaces = 4
print(f'''
Accuracy:\t{round(metrics[0], decimalPlaces)},
Precision:\t{round(metrics[1], decimalPlaces)},
Recall:\t{round(metrics[2], decimalPlaces)},
F1 Score:\t{round(metrics[3], decimalPlaces)},
AUC:\t{round(metrics[4], decimalPlaces)}
''')
In [17]:
# Display metrics for both models
print('Logistic Regression Metrics:')
displayResults(lr_metrics)
print('SVM Metrics: Accuracy:')
displayResults(svm_metrics)
Logistic Regression Metrics:
Accuracy: 0.7953,
Precision: 0.7388,
Recall: 0.7128,
F1 Score: 0.7233,
AUC: 0.8392
SVM Metrics: Accuracy:
Accuracy: 0.7938,
Precision: 0.7395,
Recall: 0.697,
F1 Score: 0.712,
AUC: 0.8013
In [18]:
# Perform grid search for SVM hyperparameter optimization
startTime = time.time()
param_grid = {'C': [0.1, 1, 10], 'gamma': ['scale', 'auto', 0.1], 'kernel': ['rbf','linear']}
SVMgrid = GridSearchCV(svm_model, param_grid, refit=True, verbose=1)
SVMgrid.fit(X_train, Y_train)
endTime = time.time()
print(f"SVM grid search optimisation took {round(endTime-startTime,2)} seconds to finish\n")
# Perform grid search for Logistic Regression hyperparameter optimization
startTime = time.time()
param_grid = {'C': [0.1, 1, 10], 'penalty': ['l1', 'l2'], 'solver': ['liblinear']}
LRgrid = GridSearchCV(lr_model, param_grid, refit=True, verbose=1)
LRgrid.fit(X_train, Y_train)
endTime = time.time()
print(f"LR grid search optimisation took {round(endTime-startTime,2)} seconds to finish")
Fitting 5 folds for each of 18 candidates, totalling 90 fits SVM grid search optimisation took 223.48 seconds to finish Fitting 5 folds for each of 6 candidates, totalling 30 fits LR grid search optimisation took 2.67 seconds to finish
Tuned SVM model results¶
In [20]:
# Evaluate optimized SVM model
print("Best Parameters for SVM:", SVMgrid.best_params_)
best_svm = SVMgrid.best_estimator_
y_pred_best_svm = best_svm.predict(X_test)
print("Tuned SVM Performance:")
bestSVM = best_svm.predict_proba(X_test)[:, 1]
tuned_svm_metrics = evaluate_model(Y_test, y_pred_best_svm, bestSVM, "Tuned SVM Performance")
print('Tuned SVM Metrics:')
displayResults(tuned_svm_metrics)
Best Parameters for SVM: {'C': 10, 'gamma': 'scale', 'kernel': 'linear'}
Tuned SVM Performance:
Tuned SVM Metrics:
Accuracy: 0.7905,
Precision: 0.7319,
Recall: 0.7072,
F1 Score: 0.7173,
AUC: 0.8237
Tuned LR model results¶
In [21]:
# Evaluate optimized Logistic Regression model
print("Best Parameters for LR:", LRgrid.best_params_)
best_lr = LRgrid.best_estimator_
y_pred_best_lr = best_lr.predict(X_test)
print("Tuned LR Performance:")
bestLR = best_lr.predict_proba(X_test)[:, 1]
tuned_lr_metrics = evaluate_model(Y_test, y_pred_best_lr, bestLR, "Tuned LR Performance")
print('Tuned LR Metrics:')
displayResults(tuned_lr_metrics)
Best Parameters for LR: {'C': 1, 'penalty': 'l1', 'solver': 'liblinear'}
Tuned LR Performance:
Tuned LR Metrics:
Accuracy: 0.7962,
Precision: 0.7404,
Recall: 0.7123,
F1 Score: 0.7235,
AUC: 0.8391
Feature importance¶
In [22]:
# Function to analyze and visualize feature importance
def analyze_feature_importance(model, feature_names, model_name):
# Extract feature importance based on model type
if model_name == 'Logistic Regression':
importance = pd.DataFrame({
'Feature': feature_names,
'Importance': abs(model.best_estimator_.coef_[0])
})
else: # SVM
importance = pd.DataFrame({
'Feature': feature_names,
'Importance': abs(model.best_estimator_.coef_[0]) if model.best_estimator_.kernel == 'linear'
else [np.nan] * len(feature_names)
})
importance = importance.sort_values('Importance', ascending=False)
# Plot top 10 most important features
plt.figure(figsize=(9, 6))
sns.barplot(data=importance.head(10), x='Importance', y='Feature')
plt.title(f'Top 10 Feature Importance - {model_name}')
return importance
In [23]:
# Analyze feature importance for both models
lr_importance = analyze_feature_importance(SVMgrid, X.columns, "SVM")
lr_importance = analyze_feature_importance(LRgrid, X.columns, "Logistic Regression")
Model comparison¶
In [24]:
# Function to compare and visualize model performances
def compare_models_performance(models_metrics, model_names, fit=None):
metrics_df = pd.DataFrame(models_metrics,
columns=['Accuracy', 'Precision', 'Recall', 'F1', 'AUC'],
index=model_names)
# Create bar plot comparing model performances
plt.figure(figsize=(12, 6))
metrics_df.plot(kind='bar', width=0.8)
plt.xlabel('Models')
plt.ylabel('Score')
plt.legend(bbox_to_anchor=(1.05, 1), loc='upper left')
plt.tight_layout()
if fit:
plt.title('Zoomed in Model Performance Comparison')
min = np.min(models_metrics)
max = np.max(models_metrics)
plt.ylim([min*0.995, max*1.005])
else:
plt.title('Model Performance Comparison')
plt.ylim([0.5, 1])
In [25]:
# Compare performance of all models
models_metrics = [lr_metrics, tuned_lr_metrics, svm_metrics, tuned_svm_metrics]
model_names = ['Base LR', 'Tuned LR', 'Base SVM', 'Tuned SVM']
compare_models_performance(models_metrics, model_names)
compare_models_performance(models_metrics, model_names, True)
<Figure size 1200x600 with 0 Axes>
<Figure size 1200x600 with 0 Axes>
In [ ]: