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()
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 %')
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: >
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
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
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
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
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()
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()