Step 1: Import Libraries¶
In [2]:
# Import essential libraries for data manipulation, preprocessing, modeling, evaluation, and visualization
import pandas as pd # For data manipulation and analysis
import numpy as np # For numerical operations
from sklearn.model_selection import train_test_split, GridSearchCV # For data splitting and hyperparameter tuning
from sklearn.linear_model import LogisticRegression # Logistic Regression model
from sklearn.preprocessing import StandardScaler, LabelEncoder # For feature scaling and encoding categorical variables
from sklearn.pipeline import Pipeline # To create machine learning pipelines
from sklearn.metrics import ( # For evaluating model performance
accuracy_score,
classification_report,
confusion_matrix,
roc_curve,
auc
)
from sklearn.svm import SVC # Support Vector Classifier
import seaborn as sns # For visualization
import matplotlib.pyplot as plt # For plotting graphs
Step 2: Load and Explore Data¶
In [4]:
# Load the dataset
data = pd.read_csv('data/TelcoCustomerChurn.csv') # Replace with the path to your dataset
# Display the first few rows to understand the structure of the data
print(data.head())
# Display column data types and check for null values
print(data.info())
customerID gender SeniorCitizen Partner Dependents tenure PhoneService \
0 7590-VHVEG Female 0 Yes No 1 No
1 5575-GNVDE Male 0 No No 34 Yes
2 3668-QPYBK Male 0 No No 2 Yes
3 7795-CFOCW Male 0 No No 45 No
4 9237-HQITU Female 0 No No 2 Yes
MultipleLines InternetService OnlineSecurity ... DeviceProtection \
0 No phone service DSL No ... No
1 No DSL Yes ... Yes
2 No DSL Yes ... No
3 No phone service DSL Yes ... Yes
4 No Fiber optic No ... No
TechSupport StreamingTV StreamingMovies Contract PaperlessBilling \
0 No No No Month-to-month Yes
1 No No No One year No
2 No No No Month-to-month Yes
3 Yes No No One year No
4 No No No Month-to-month Yes
PaymentMethod MonthlyCharges TotalCharges Churn
0 Electronic check 29.85 29.85 No
1 Mailed check 56.95 1889.5 No
2 Mailed check 53.85 108.15 Yes
3 Bank transfer (automatic) 42.30 1840.75 No
4 Electronic check 70.70 151.65 Yes
[5 rows x 21 columns]
<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
None
Step 3: Data Preprocessing¶
In [6]:
# Replace empty strings with NaN and drop rows with missing values
data.replace(' ', np.nan, inplace=True)
data.dropna(inplace=True)
# Encode categorical variables to numeric using LabelEncoder
encoder = LabelEncoder()
categorical_columns = ['gender', 'Contract', 'PaymentMethod', 'InternetService']
for col in categorical_columns:
data[col] = encoder.fit_transform(data[col])
# Replace specific categorical values with numeric equivalents
data = data.replace({'Yes': 1, 'No': 0, 'No internet service': 2, 'No phone service': 2})
# Split the dataset into features (X) and target (y)
X = data.drop(columns=['Churn', 'customerID', 'gender', 'PhoneService', 'MultipleLines', 'PaperlessBilling'])
y = data['Churn']
# Split data into training and testing sets (80% train, 20% test)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
# Scale features to have mean 0 and variance 1 for better model performance
scaler = StandardScaler()
X_train = scaler.fit_transform(X_train)
X_test = scaler.transform(X_test)
C:\Users\rhys.davies\AppData\Local\Temp\ipykernel_33636\2669118501.py:12: FutureWarning: Downcasting behavior in `replace` is deprecated and will be removed in a future version. To retain the old behavior, explicitly call `result.infer_objects(copy=False)`. To opt-in to the future behavior, set `pd.set_option('future.no_silent_downcasting', True)`
data = data.replace({'Yes': 1, 'No': 0, 'No internet service': 2, 'No phone service': 2})
Step 4: Train Model (LR)¶
In [8]:
# Initialize and train the logistic regression model
lr_model = LogisticRegression(random_state=42)
lr_model.fit(X_train, y_train)
Out[8]:
LogisticRegression(random_state=42)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(random_state=42)
Step 5: Make Predictions (LR)¶
In [10]:
# Predict the target variable using the test set
y_pred = lr_model.predict(X_test)
# Predict probabilities for ROC analysis
y_prob = lr_model.predict_proba(X_test)[:, 1]
Step 6: Evaluate the Model (LR)¶
In [12]:
# Print classification metrics for model performance
print("Classification Report (LR):\n", classification_report(y_test, y_pred))
# Create a confusion matrix heatmap
cm = confusion_matrix(y_test, y_pred)
plt.figure(figsize=(6, 6))
sns.heatmap(
cm,
annot=True,
fmt='d',
cmap='Blues',
xticklabels=['Predicted No', 'Predicted Yes'],
yticklabels=['Actual No', 'Actual Yes']
)
plt.title('Confusion Matrix Heatmap (LR)')
plt.xlabel('Predicted')
plt.ylabel('True')
plt.show()
# Plot the ROC curve
fpr, tpr, _ = roc_curve(y_test, y_prob)
roc_auc = auc(fpr, tpr)
plt.figure()
plt.plot(fpr, tpr, color='darkorange', lw=2, label=f'ROC curve (AUC = {roc_auc:.2f})')
plt.plot([0, 1], [0, 1], color='navy', linestyle='--') # Baseline for random guessing
plt.xlabel('False Positive Rate (1-Specificity)')
plt.ylabel('True Positive Rate (Sensitivity)')
plt.title('Receiver Operating Characteristic (LR)')
plt.legend(loc='lower right')
plt.show()
Classification Report (LR):
precision recall f1-score support
0 0.83 0.88 0.85 1033
1 0.60 0.50 0.55 374
accuracy 0.78 1407
macro avg 0.72 0.69 0.70 1407
weighted avg 0.77 0.78 0.77 1407
Step 7: Hyperparameter Tuning (LR)¶
In [14]:
# Define a grid of hyperparameters for tuning the Logistic Regression model
param_grid = [
{
'solver': ['lbfgs'],
'penalty': ['l2'],
'C': [0.001, 0.01, 0.1, 1, 10, 100],
'max_iter': [100, 200, 300]
},
{
'solver': ['liblinear'],
'penalty': ['l1', 'l2'],
'C': [0.001, 0.01, 0.1, 1, 10, 100],
'max_iter': [100, 200, 300]
}
]
# Initialize the logistic regression model
lr_model = LogisticRegression(random_state=42)
# Set up GridSearchCV for hyperparameter tuning
grid_search = GridSearchCV(
estimator=lr_model,
param_grid=param_grid,
cv=5,
scoring='accuracy',
verbose=1,
n_jobs=-1
)
# Perform grid search on the training data
grid_search.fit(X_train, y_train)
# Print the best parameters and corresponding score
print("Best Parameters:", grid_search.best_params_)
print("Best Score:", grid_search.best_score_)
# Retrain the model with the best parameters
best_lr_model = grid_search.best_estimator_
y_pred = best_lr_model.predict(X_test)
y_prob = best_lr_model.predict_proba(X_test)[:, 1]
Fitting 5 folds for each of 54 candidates, totalling 270 fits
Best Parameters: {'C': 0.1, 'max_iter': 100, 'penalty': 'l2', 'solver': 'lbfgs'}
Best Score: 0.7998222222222222
Step 8: Evaluate Hyperparameter Tuning Results (LR)¶
In [16]:
# Evaluate the model's performance with the tuned parameters
print("Classification Report (LR):\n", classification_report(y_test, y_pred))
Classification Report (LR):
precision recall f1-score support
0 0.82 0.88 0.85 1033
1 0.60 0.48 0.53 374
accuracy 0.78 1407
macro avg 0.71 0.68 0.69 1407
weighted avg 0.76 0.78 0.77 1407
Step 9: Train Model (SVM)¶
In [18]:
# Import and initialize the Support Vector Machine (SVM) model with an RBF kernel
# The `probability=True` parameter enables probability estimates (used later for ROC curve)
# `random_state=42` ensures reproducibility
svm_model = SVC(probability=True, random_state=42, kernel='rbf')
# Train the SVM model on the training data
svm_model.fit(X_train, y_train)
Out[18]:
SVC(probability=True, random_state=42)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(probability=True, random_state=42)
Step 10: Make Predictions (SVM)¶
In [20]:
# Use the trained SVM model to make predictions on the test data
y_pred = svm_model.predict(X_test) # Class predictions for the test set
# Generate probabilities for the positive class (Churn = 1)
# These probabilities are used for performance evaluation like the ROC curve
y_prob = svm_model.predict_proba(X_test)[:, 1] # Get probabilities for the positive class (index 1)
Step 11: Evaluate the Model (SVM)¶
In [22]:
# Print a classification report to evaluate precision, recall, F1 score, and accuracy
print("Classification Report (SVM):\n", classification_report(y_test, y_pred))
# Generate a confusion matrix to visualize model performance
cm = confusion_matrix(y_test, y_pred)
# Plot the confusion matrix as a heatmap for better visualization
plt.figure(figsize=(6, 6))
sns.heatmap(
cm,
annot=True,
fmt='d',
cmap='Reds',
xticklabels=['Predicted No', 'Predicted Yes'],
yticklabels=['Actual No', 'Actual Yes']
)
plt.title('Confusion Matrix Heatmap (SVM)')
plt.xlabel('Predicted')
plt.ylabel('Actual')
plt.show()
# Generate an ROC curve to evaluate the trade-off between sensitivity and specificity
fpr, tpr, _ = roc_curve(y_test, y_prob) # False Positive Rate and True Positive Rate
roc_auc = auc(fpr, tpr) # Compute the Area Under the Curve (AUC)
# Plot the ROC curve
plt.figure()
plt.plot(fpr, tpr, color='darkorange', lw=2, label=f'ROC curve (AUC = {roc_auc:.2f})')
plt.plot([0, 1], [0, 1], color='navy', linestyle='--') # Diagonal line represents random guessing
plt.xlabel('False Positive Rate')
plt.ylabel('True Positive Rate')
plt.title('Receiver Operating Characteristic (SVM)')
plt.legend(loc='lower right')
plt.show()
Classification Report (SVM):
precision recall f1-score support
0 0.82 0.92 0.86 1033
1 0.65 0.44 0.52 374
accuracy 0.79 1407
macro avg 0.73 0.68 0.69 1407
weighted avg 0.77 0.79 0.77 1407
Step 12: SVM Hyperparameter Tuning (SVM)¶
In [24]:
# Define a parameter grid for tuning hyperparameters
# C controls the regularization strength, and kernel specifies the type of SVM (RBF here)
# The probability=True parameter is required for ROC curve evaluation
param_grid = {
'C': [0.001, 0.01, 0.1, 1, 10, 100],
'kernel': ['linear', 'rbf', 'poly', 'sigmoid'],
'gamma': ['scale', 'auto'] # Kernel coefficient for RBF, poly, and sigmoid
}
# Initialize the SVM model
svm_model = SVC(probability=True, random_state=42)
# Set up GridSearchCV to find the best combination of hyperparameters
grid_search = GridSearchCV(
estimator=svm_model,
param_grid=param_grid,
cv=5, # 5-fold cross-validation
scoring='accuracy',
verbose=1, # Displays progress during search
n_jobs=-1 # Utilizes all available CPU cores
)
# Perform grid search on the training data
grid_search.fit(X_train, y_train)
# Display the best parameters and corresponding accuracy score
print("Best Parameters:", grid_search.best_params_)
print("Best Score:", grid_search.best_score_)
# Use the best parameters to initialize a new SVM model
best_svm_model = SVC(
C=grid_search.best_params_['C'],
kernel=grid_search.best_params_['kernel'],
gamma=grid_search.best_params_['gamma'],
probability=True,
random_state=42
)
# Train the optimized model on the training data
best_svm_model.fit(X_train, y_train)
# Make predictions using the optimized model
y_pred = best_svm_model.predict(X_test) # Class predictions
y_prob = best_svm_model.predict_proba(X_test)[:, 1] # Probabilities for positive class
Fitting 5 folds for each of 48 candidates, totalling 240 fits
Best Parameters: {'C': 1, 'gamma': 'scale', 'kernel': 'rbf'}
Best Score: 0.7955555555555556
Step 13: Evaluate Hyperparameter Tuning Results (LR)¶
In [26]:
# Evaluate the optimized model's performance
print("Classification Report (SVM):\n", classification_report(y_test, y_pred))
Classification Report (SVM):
precision recall f1-score support
0 0.82 0.92 0.86 1033
1 0.65 0.44 0.52 374
accuracy 0.79 1407
macro avg 0.73 0.68 0.69 1407
weighted avg 0.77 0.79 0.77 1407
In [ ]: