<jupyter_start><jupyter_text>Telcom Customer Churn======================================= Predicting customer churn with logistic regression, random trees and random forests.  Initial Setup<jupyter_code>import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import numpy as np
import os
data = pd.read_csv(os.getcwd()+'/churn.csv')
data.info()
data.head()<jupyter_output><class 'pandas.core.frame.DataFrame'>
RangeIndex: 7043 entries, 0 to 7042
Data columns (total 21 columns):
customerID          7043 non-null object
gender              7043 non-null object
SeniorCitizen       7043 non-null int64
Partner             7043 non-null object
Dependents          7043 non-null object
tenure              7043 non-null int64
PhoneService        7043 non-null object
MultipleLines       7043 non-null object
InternetService     7043 non-null object
OnlineSecurity      7043 non-null object
OnlineBackup        7043 non-null object
DeviceProtection    7043 non-null object
TechSupport         7043 non-null object
StreamingTV         7043 non-null object
StreamingMovies     7043 non-null object
Contract            7043 non-null object
PaperlessBilling    7043 non-null object
PaymentMethod       7043 non-null object
MonthlyCharges      7043 non-null float64
TotalCharges        7043 non-null object
Churn               7043 non-null object
dtypes: float64(1), int64(2), obj[...]<jupyter_text>Cleaning the data<jupyter_code># looking into why NaN coming through in TotalCharges

data['TotalCharges'] = pd.to_numeric(data['TotalCharges'], errors='coerce')
data.loc[data['TotalCharges'].isna()==True]
# coerce NaNs to 0 in order to make column numeric
data[data['TotalCharges'].isna()==True] = 0

# check data types
data.dtypes
# coerce Yes/No/Other answers to 0/1s for later use in ML algorithm

data['gender'].replace(['Male','Female'],[0,1],inplace=True)
data['Partner'].replace(['Yes','No'],[1,0],inplace=True)
data['Dependents'].replace(['Yes','No'],[1,0],inplace=True)
data['PhoneService'].replace(['Yes','No'],[1,0],inplace=True)
data['MultipleLines'].replace(['Yes','No phone service','No'],[1,0,0],inplace=True)
data['InternetService'].replace(['No','DSL','Fiber optic'],[0,1,2],inplace=True)
data['OnlineSecurity'].replace(['Yes','No','No internet service'],[1,0,0],inplace=True)
data['OnlineBackup'].replace(['Yes','No','No internet service'],[1,0,0],inplace=True)
data['DeviceProtection'].replace(['Yes','No','No internet service'],[1,0,0],inplace=True)
data['TechSupport'].replace(['Yes','No','No internet service'],[1,0,0],inplace=True)
data['StreamingTV'].replace(['Yes','No','No internet service'],[1,0,0],inplace=True)
data['StreamingMovies'].replace(['Yes','No','No internet service'],[1,0,0],inplace=True)
data['Contract'].replace(['Month-to-month', 'One year', 'Two year'],[0,1,2],inplace=True)
data['PaperlessBilling'].replace(['Yes','No'],[1,0],inplace=True)
data['PaymentMethod'].replace(['Electronic check', 'Mailed check', 'Bank transfer (automatic)','Credit card (automatic)'],[0,1,2,3],inplace=True)
data['Churn'].replace(['Yes','No'],[1,0],inplace=True)

# remove unecessary column
data.pop('customerID')
data.info()<jupyter_output><class 'pandas.core.frame.DataFrame'>
RangeIndex: 7043 entries, 0 to 7042
Data columns (total 20 columns):
gender              7043 non-null int64
SeniorCitizen       7043 non-null int64
Partner             7043 non-null int64
Dependents          7043 non-null int64
tenure              7043 non-null int64
PhoneService        7043 non-null int64
MultipleLines       7043 non-null int64
InternetService     7043 non-null int64
OnlineSecurity      7043 non-null int64
OnlineBackup        7043 non-null int64
DeviceProtection    7043 non-null int64
TechSupport         7043 non-null int64
StreamingTV         7043 non-null int64
StreamingMovies     7043 non-null int64
Contract            7043 non-null int64
PaperlessBilling    7043 non-null int64
PaymentMethod       7043 non-null int64
MonthlyCharges      7043 non-null float64
TotalCharges        7043 non-null float64
Churn               7043 non-null int64
dtypes: float64(2), int64(18)
memory usage: 1.1 MB<jupyter_text>Check correlations between customer data features and churn.<jupyter_code>corr = data.corr()
sns.heatmap(corr, 
            xticklabels=corr.columns.values, 
            yticklabels=corr.columns.values, 
            annot = True, 
            annot_kws={'size' : 12})
heat_map=plt.gcf()
heat_map.set_size_inches(20,15)
plt.xticks(fontsize=10)
plt.yticks(fontsize=10)
plt.show()
# omit total charges to avoid unstable estimates of coefficients in final model
data.pop('TotalCharges')<jupyter_output><empty_output><jupyter_text>Predictive Modelling<jupyter_code>from sklearn.model_selection import train_test_split 
train, test = train_test_split(data, test_size = 0.25)

train_y = train['Churn']
test_y = test['Churn']

train_x = train
train_x.pop('Churn')
test_x = test
test_x.pop('Churn')<jupyter_output><empty_output><jupyter_text>Logistic Regression<jupyter_code>from sklearn.linear_model import LogisticRegression
from sklearn.metrics import confusion_matrix, classification_report

logisticReg = LogisticRegression()
logisticReg.fit(X=train_x, y=train_y)

test_y_pred = logisticReg.predict(test_x)
confusion_matrix = confusion_matrix(test_y, test_y_pred)
print('Intercept: ' + str(logisticReg.intercept_))
print('Regression: ' + str(logisticReg.coef_))
print('Accuracy of logistic regression classifier on test set: {:.2f}'.format(logisticReg.score(test_x, test_y)))
print(classification_report(test_y, test_y_pred))

confusion_matrix_df = pd.DataFrame(confusion_matrix, ('No churn', 'Churn'), ('No churn', 'Churn'))
heatmap = sns.heatmap(confusion_matrix_df, annot=True, annot_kws={"size" : 20}, fmt="d")
heatmap.yaxis.set_ticklabels(heatmap.yaxis.get_ticklabels(), rotation=0, ha='right', fontsize = 14)
heatmap.xaxis.set_ticklabels(heatmap.xaxis.get_ticklabels(), rotation=45, ha='right', fontsize = 14)
plt.ylabel('True label', fontsize = 14)
plt.xlabel('Predicted label', fontsize = 14)<jupyter_output>Intercept: [-0.90933558]
Regression: [[ 0.04310922  0.25828163 -0.02464323 -0.1779526  -0.03141598 -0.62664489
   0.30169896  0.60635221 -0.41203422 -0.21710806 -0.02793958 -0.47732481
   0.16756787  0.10068508 -0.71531669  0.22497293 -0.14997291  0.01182931]]
Accuracy of logistic regression classifier on test set: 0.81
              precision    recall  f1-score   support

           0       0.84      0.91      0.88      1303
           1       0.67      0.52      0.59       458

   micro avg       0.81      0.81      0.81      1761
   macro avg       0.76      0.72      0.73      1761
weighted avg       0.80      0.81      0.80      1761<jupyter_text>ResultsThe model achieved 81% classification accuracy. Precision and recall in the positive class (churn) are low, which suggests data may be imbalanced.---------------------------  Handling Class Imbalance<jupyter_code>data['Churn'].value_counts()<jupyter_output><empty_output><jupyter_text>Up-sampling the minority class<jupyter_code>from sklearn.utils import resample

data_majority = data[data['Churn']==0]
data_minority = data[data['Churn']==1]

data_minority_upsampled = resample(data_minority,
                                   replace=True,
                                   n_samples=5174,   # same number of samples as majority classe
                                   random_state=1)   # set the seed for random resampling

# combine resampled results
data_upsampled = pd.concat([data_majority, data_minority_upsampled])

data_upsampled['Churn'].value_counts()
train, test = train_test_split(data_upsampled, test_size = 0.25)

train_y_upsampled = train['Churn']
test_y_upsampled = test['Churn']

train_x_upsampled = train
train_x_upsampled.pop('Churn')
test_x_upsampled = test
test_x_upsampled.pop('Churn')

logisticRegr_balanced = LogisticRegression()
logisticRegr_balanced.fit(X=train_x_upsampled, y=train_y_upsampled)

test_y_pred_balanced = logisticRegr_balanced.predict(test_x_upsampled)
print('Accuracy of logistic regression classifier on test set: {:.2f}'.format(logisticRegr_balanced.score(test_x_upsampled, test_y_upsampled)))
print(classification_report(test_y_upsampled, test_y_pred_balanced))
import sklearn
import graphviz 
from IPython.display import SVG
from graphviz import Source
from IPython.display import display

# Create each decision tree (pruned and unpruned)
decisionTree_unpruned = tree.DecisionTreeClassifier()
decisionTree = tree.DecisionTreeClassifier(max_depth = 4)

# Fit each tree to our training data
decisionTree_unpruned = decisionTree_unpruned.fit(X=train_x, y=train_y)
decisionTree = decisionTree.fit(X=train_x, y=train_y)

from sklearn.tree import export_graphviz
graph = Source(tree.export_graphviz(decisionTree,
                                    out_file=None,
                                    class_names = ['No churn', 'Churn'],
                                    feature_names = list(train_x.columns.values),
                                    filled=True))

display(SVG(graph.pipe(format='svg')))
test_y_pred_dt = decisionTree.predict(test_x)
print('Accuracy of decision tree classifier on test set: {:.2f}'.format(decisionTree.score(test_x, test_y)))<jupyter_output>Accuracy of decision tree classifier on test set: 0.80
{'fraction_non_alphanumeric': 0.1220324417230637, 'fraction_numerical': 0.06219787302610377, 'mean_word_length': 4.262860373092143, 'pattern_counts': {'":': 0, '<': 27, '<?xml version=': 0, '>': 27, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 12, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '7', 'dir': 'jupyter-structured-clean-dedup', 'id': '229342', 'license': 'MIT', 'n_tokens_mistral': 3431, 'n_tokens_neox': 3022, 'n_words': 672, 'path': 'code/customer_churn/Predicting Customer Churn.ipynb', 'repo_name': 'KeiranJOC/code-collection', 'size': '9309'}