Hito 2

In [1]:
from IPython.display import display
from IPython.display import HTML
import IPython.core.display as di # Example: di.display_html('<h3>%s:</h3>' % str, raw=True)

# This line will hide code by default when the notebook is exported as HTML
di.display_html('<script>jQuery(function() {if (jQuery("body.notebook_app").length == 0) { jQuery(".input_area").toggle(); jQuery(".prompt").toggle();}});</script>', raw=True)

# This line will add a button to toggle visibility of code blocks, for use with the HTML export version
di.display_html('''<button onclick="jQuery('.input_area').toggle(); jQuery('.prompt').toggle();">Toggle code</button>''', raw=True)

Para el Hito 2 se realizarán distintos algoritmos de clasificación con el objetivo de predecir si los clientes de la compañía de telecomunicaciones se irán de esta o no, analizando las características propias de los clientes y los productos que tienen contratados con la empresa de telecomunicaciones.

Transformación de datos previo a aplicar algoritmos de clasificación

Lo primero que se debe realizar es una transformación de los datos no numéricos, por lo tanto, para cada variable del dataset, se crearon tantas columnas nuevas como posibles opciones tiene dicha variable, utilizando dummies para identificar todos los casos, por ejemplo, para la variable género (gender) cuyas posibles opciones son “Male” y “Female”, se crearon dos nuevas columnas, una llamada Male que toma el valor 1 si el cliente es hombre y 0 si no, y otra para el caso contrario (“Female”), posteriormente no se consideró la columna original para ejecutar los algoritmos.

También es importante mencionar que no se consideró el código ID de las personas, debido a que, no entregaba información determinante, ni tampoco la columna del monto total pagado (TotalCharges), ya que, presentaba una alta correlación (0,83) con la variable del tiempo de la persona en la compañía (tenure).

In [14]:
import pandas as pd
import numpy as np

# transformamos los datos numericos representados en otro formato a float
data = pd.read_csv('churn.csv')  # abrimos el archivo csv y lo cargamos en data. 
data['tenure']=data['tenure'].astype('float')
data['Churn'] = data['Churn'].map({'Yes': 1, 'No': 0})

# extrae las columnas que presentan datos no-numericos
names=data.columns.values.tolist()
name=np.array([names[1],names[3],names[4],names[6],names[7],names[8],names[9],names[10],names[11],names[12],
               names[13],names[14],names[15],names[16],names[17]])

# crea una columna por cada clase de dato en una columna
for i in name:
    data=pd.get_dummies(data,columns=[i])

a=[]
nn=len(data.columns.values.tolist())
for i in range(1,nn):
    if i == 5 or i == 4:
        next
    else:
        a.append(i)

dchurn=data[data.columns[5]]  # clases
data=data[data.columns[a]]    # dataset

Desbalanceo

A continuación se puede observar el desbalanceo de las clases.

In [3]:
print("Distribucion de clases original")
dchurn.value_counts()
Distribucion de clases original
Out[3]:
0    5174
1    1869
Name: Churn, dtype: int64

Modelos de clasificación

En primer lugar se corrieron los modelos de clasificación con la base original, implementando árboles de decisión, super vector machine (SVM), naive bayes, random forest y k-nearest neighbors (knn). A través de estos métodos se obtuvieron métricas accuracy promedios menores a 0.8 y una mala predicción para la fuga de los clientes, la cual es la variable más relevante a estimar para este problema. Lo anterior es producido por el desbalance que existe entre las clases, en donde la clase 0 o de los clientes que se mantienen en la compañía presenta 5174 datos, mientras que para la clase 1 o de los clientes que cambiaron de compañía solo se tienen 1869 datos. Para solucionar este problema se utilizan las técnicas de oversampling y subsampling.

In [4]:
## RESPUESTA A PREGUNTA 2.1

from sklearn.tree import DecisionTreeClassifier
from sklearn.model_selection import train_test_split
from sklearn import metrics, model_selection
from sklearn.metrics import classification_report
from sklearn.svm import SVC  # support vector machine classifier
from sklearn.naive_bayes import GaussianNB  # naive bayes
from sklearn.neighbors import KNeighborsClassifier
from sklearn.svm import SVC
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import confusion_matrix
from numpy.core.umath_tests import inner1d

# datos originales 
X_orig = data # todo hasta la penultima columna
y_orig = dchurn

# Arbol de decision
clf_orig = DecisionTreeClassifier()

print("## Arbol de decisión \n")
predictions = model_selection.cross_val_predict(clf_orig, X_orig, y_orig, cv=10)  ## cv es la cantidad de folds
print("Accuracy:", metrics.accuracy_score(y_orig, predictions))
print("Metricas:")
print(metrics.classification_report(y_orig, predictions))
cm1=confusion_matrix(y_orig, predictions)

# Implementacin de KNN
K = 5 # numero de vecinos
knn = KNeighborsClassifier(n_neighbors=K)  

print("## KNN \n")
predictions = model_selection.cross_val_predict(knn, X_orig, y_orig, cv=10)  ## cv es la cantidad de folds
print("Accuracy:", metrics.accuracy_score(y_orig, predictions))
print("Metricas:")
print(metrics.classification_report(y_orig, predictions))
cm2=confusion_matrix(y_orig, predictions)

# naives bayes
gauss = GaussianNB()

print("## Naives Bayes \n")
predictions = model_selection.cross_val_predict(gauss, X_orig, y_orig, cv=10)  ## cv es la cantidad de folds
print("Accuracy:", metrics.accuracy_score(y_orig, predictions))
print("Metricas:")
print(metrics.classification_report(y_orig, predictions))
cm3=confusion_matrix(y_orig, predictions)

# SVM
#clf_svm = SVC(gamma='auto')

#print("## SVM \n")
#predictions = model_selection.cross_val_predict(clf_svm, X_orig, y_orig, cv=10)  ## cv es la cantidad de folds
#print("Accuracy:", metrics.accuracy_score(y_orig, predictions))
#print("Metricas:")
#print(metrics.classification_report(y_orig, predictions))
#cm4=confusion_matrix(y_orig, predictions)

# Random Forest
clf_rf = RandomForestClassifier()

print("## Random Forest \n")
predictions = model_selection.cross_val_predict(clf_rf, X_orig, y_orig, cv=10)  ## cv es la cantidad de folds
print("Accuracy:", metrics.accuracy_score(y_orig, predictions))
print("Metricas:")
print(metrics.classification_report(y_orig, predictions))
cm5=confusion_matrix(y_orig, predictions)
C:\Users\crist\Anaconda3\envs\tf3\lib\site-packages\sklearn\ensemble\weight_boosting.py:29: DeprecationWarning: numpy.core.umath_tests is an internal NumPy module and should not be imported. It will be removed in a future NumPy release.
  from numpy.core.umath_tests import inner1d
## Arbol de decisión 

Accuracy: 0.7279568365753231
Metricas:
             precision    recall  f1-score   support

          0       0.82      0.81      0.81      5174
          1       0.49      0.51      0.50      1869

avg / total       0.73      0.73      0.73      7043

## KNN 

Accuracy: 0.7719721709498794
Metricas:
             precision    recall  f1-score   support

          0       0.84      0.86      0.85      5174
          1       0.58      0.53      0.55      1869

avg / total       0.77      0.77      0.77      7043

## Naives Bayes 

Accuracy: 0.6921766292772966
Metricas:
             precision    recall  f1-score   support

          0       0.92      0.64      0.75      5174
          1       0.46      0.85      0.59      1869

avg / total       0.80      0.69      0.71      7043

## Random Forest 

Accuracy: 0.775947749538549
Metricas:
             precision    recall  f1-score   support

          0       0.82      0.90      0.85      5174
          1       0.61      0.44      0.51      1869

avg / total       0.76      0.78      0.76      7043

Matrices de confusión

A continuación se presentan las matrices de confusión para cada clasificador, en donde queda en evidencia el desbalanceo de las clases, ya que, los verdaderos positivos son cerca de 5 veces los verdaderos negativos.

In [5]:
# print(__doc__)

import itertools
import numpy as np
import matplotlib.pyplot as plt

from sklearn import svm, datasets
from sklearn.model_selection import train_test_split
from sklearn.metrics import confusion_matrix

# import some data to play with

class_names = [0,1]

def plot_confusion_matrix(cm, classes,
                          normalize=False,
                          title='Confusion matrix',
                          cmap=plt.cm.Blues):
    if normalize:
        cm = cm.astype('float') / cm.sum(axis=1)[:, np.newaxis]

    plt.imshow(cm, interpolation='nearest', cmap=cmap)
    plt.title(title)
    plt.colorbar()
    tick_marks = np.arange(len(classes))
    plt.xticks(tick_marks, classes, rotation=45)
    plt.yticks(tick_marks, classes)

    fmt = '.2f' if normalize else 'd'
    thresh = cm.max() / 2.
    for i, j in itertools.product(range(cm.shape[0]), range(cm.shape[1])):
        plt.text(j, i, format(cm[i, j], fmt),
                 horizontalalignment="center",
                 color="white" if cm[i, j] > thresh else "black")

    plt.ylabel('Clase Real')
    plt.xlabel('Clase Predicta')
    plt.tight_layout()


# matriz de confusion para Arbol de decision
cnf_matrix = cm1
np.set_printoptions(precision=2)

plt.figure()
plot_confusion_matrix(cnf_matrix, classes=class_names,
                      title='Matriz de confusión Árbol de decisión')

# matriz de confusion para KNN
cnf_matrix = cm2
np.set_printoptions(precision=2)

plt.figure()
plot_confusion_matrix(cnf_matrix, classes=class_names,
                      title='Matriz de confusión KNN')

# matriz de confusion para Naive Bayes
cnf_matrix = cm3
np.set_printoptions(precision=2)

plt.figure()
plot_confusion_matrix(cnf_matrix, classes=class_names,
                      title='Matriz de confusión Naive Bayes')

# matriz de confusion para SVM
#cnf_matrix = cm4
#np.set_printoptions(precision=2)

#plt.figure()
#plot_confusion_matrix(cnf_matrix, classes=class_names,
                      #title='Matriz de confusión SVM')

# matriz de confusion para Random forest
cnf_matrix = cm5
np.set_printoptions(precision=2)

plt.figure()
plot_confusion_matrix(cnf_matrix, classes=class_names,
                      title='Matriz de confusión Rendom Forest')

plt.show()

Oversampling y subsampling

Se crean los dataset con oversampling y subsampling

In [6]:
data2 = pd.concat([data, dchurn], axis=1)

# oversampling sobre la clase 1
idx = np.random.choice(data2.loc[data2.Churn == 1].index, size=3305)
data_oversampled = pd.concat([data2, data2.iloc[idx]])

X_over = data_oversampled[data_oversampled.columns[:-1]]
y_over = data_oversampled[data_oversampled.columns[-1]]

# subsampling sobre la clase 0
idx = np.random.choice(data2.loc[data2.Churn == 0].index, size=3305, replace=False)
data_subsampled = data2.drop(data2.iloc[idx].index)

X_sub = data_subsampled[data_subsampled.columns[:-1]]
y_sub = data_subsampled[data_subsampled.columns[-1]]

Resultados con oversampling y subsampling

Al realizar las técnicas de oversampling y subsampling, se pudo apreciar que para el árbol de decisión con subsampling los resultados fueron peores que para los datos originales, esto se debe principalmente a que en esta técnica se eliminan datos para balancear las clases, por lo tanto, se pasa de un dataset de 7043 datos a uno de 3738, lo que es casi la mitad. Los clasificadores dependen altamente de la cantidad de datos ingresados, generalmente una mayor cantidad de datos trae mejores resultados, por lo tanto, al acotar el dataset se obtienen peores resultados. Por esta razón, todos los clasificadores fueron entrenados con oversampling, ya que se presentan mejores resultados que con los datos originales, en donde los accuracys se encuentran entre 0,75 y 0,9.

In [7]:
## RESPUESTA A PREGUNTA 2.1

from sklearn.tree import DecisionTreeClassifier
from sklearn.model_selection import train_test_split
from sklearn.model_selection import cross_val_score


## oversampling

clf_over = DecisionTreeClassifier()
from sklearn import metrics, model_selection

print("## Árbol de decisión con Oversampling \n")
pred_over = model_selection.cross_val_predict(clf_over, X_over, y_over, cv=10)  ## cv es la cantidad de folds
print("Accuracy:", metrics.accuracy_score(y_over, pred_over))
print("Metricas:")
print(metrics.classification_report(y_over, pred_over))
cm1=confusion_matrix(y_over, pred_over)

## subsampling

clf_sub = DecisionTreeClassifier()
from sklearn import metrics, model_selection

print("## Árbol de decisión con Subsampling \n")
pred_sub = model_selection.cross_val_predict(clf_sub, X_sub, y_sub, cv=10)  ## cv es la cantidad de folds
print("Accuracy:", metrics.accuracy_score(y_sub, pred_sub))
print("Metricas:")
print(metrics.classification_report(y_sub, pred_sub))

# Implementacin de KNN
K = 5 # numero de vecinos
knn = KNeighborsClassifier(n_neighbors=K)  

print("## KNN \n")
predictions = model_selection.cross_val_predict(knn, X_over, y_over, cv=10)  ## cv es la cantidad de folds
print("Accuracy:", metrics.accuracy_score(y_over, predictions))
print("Metricas:")
print(metrics.classification_report(y_over, predictions))
cm2=confusion_matrix(y_over, predictions)

# naives bayes
gauss = GaussianNB()

print("## Naives Bayes \n")
predictions = model_selection.cross_val_predict(gauss, X_over, y_over, cv=10)  ## cv es la cantidad de folds
print("Accuracy:", metrics.accuracy_score(y_over, predictions))
print("Metricas:")
print(metrics.classification_report(y_over, predictions))
cm3=confusion_matrix(y_over, predictions)

# SVM
#clf_svm = SVC(gamma='auto')

#print("## SVM \n")
#predictions = model_selection.cross_val_predict(clf_svm, X_over, y_over, cv=10)  ## cv es la cantidad de folds
#print("Accuracy:", metrics.accuracy_score(y_over, predictions))
#print("Metricas:")
#print(metrics.classification_report(y_over, predictions))
#cm4=confusion_matrix(y_over, predictions)

# Random Forest
clf_rf = RandomForestClassifier()

print("## Random Forest \n")
predictions = model_selection.cross_val_predict(clf_rf, X_over, y_over, cv=10)  ## cv es la cantidad de folds
print("Accuracy:", metrics.accuracy_score(y_over, predictions))
print("Metricas:")
print(metrics.classification_report(y_over, predictions))
cm5=confusion_matrix(y_over, predictions)
## Árbol de decisión con Oversampling 

Accuracy: 0.8839389253962119
Metricas:
             precision    recall  f1-score   support

          0       0.96      0.80      0.87      5174
          1       0.83      0.97      0.89      5174

avg / total       0.89      0.88      0.88     10348

## Árbol de decisión con Subsampling 

Accuracy: 0.6789727126805778
Metricas:
             precision    recall  f1-score   support

          0       0.68      0.68      0.68      1869
          1       0.68      0.68      0.68      1869

avg / total       0.68      0.68      0.68      3738

## KNN 

Accuracy: 0.782759953614225
Metricas:
             precision    recall  f1-score   support

          0       0.85      0.69      0.76      5174
          1       0.74      0.87      0.80      5174

avg / total       0.79      0.78      0.78     10348

## Naives Bayes 

Accuracy: 0.7380170081175106
Metricas:
             precision    recall  f1-score   support

          0       0.81      0.62      0.70      5174
          1       0.69      0.86      0.77      5174

avg / total       0.75      0.74      0.73     10348

## Random Forest 

Accuracy: 0.9012369540007731
Metricas:
             precision    recall  f1-score   support

          0       0.95      0.85      0.90      5174
          1       0.86      0.95      0.91      5174

avg / total       0.91      0.90      0.90     10348

Matrices de confusión over sampling

A continuación se presentan las matrices de confusión de los clasificadores con oversampling, en ellas se puede apreciar el balanceo de los datos, ya que, los verdaderos positivos y verdaderos negativos son muy semejantes.

In [8]:
# print(__doc__)

import itertools
import numpy as np
import matplotlib.pyplot as plt

from sklearn import svm, datasets
from sklearn.model_selection import train_test_split
from sklearn.metrics import confusion_matrix

# import some data to play with

class_names = [0,1]

# matriz de confusion para Arbol de decision
cnf_matrix = cm1
np.set_printoptions(precision=2)

plt.figure()
plot_confusion_matrix(cnf_matrix, classes=class_names,
                      title='Matriz de confusión Árbol de decisión')

# matriz de confusion para KNN
cnf_matrix = cm2
np.set_printoptions(precision=2)

plt.figure()
plot_confusion_matrix(cnf_matrix, classes=class_names,
                      title='Matriz de confusión KNN')

# matriz de confusion para Naive Bayes
cnf_matrix = cm3
np.set_printoptions(precision=2)

plt.figure()
plot_confusion_matrix(cnf_matrix, classes=class_names,
                      title='Matriz de confusión Naive Bayes')

# matriz de confusion para SVM
#cnf_matrix = cm4
#np.set_printoptions(precision=2)

#plt.figure()
#plot_confusion_matrix(cnf_matrix, classes=class_names,
                      #title='Matriz de confusión SVM')

# matriz de confusion para Random forest
cnf_matrix = cm5
np.set_printoptions(precision=2)

plt.figure()
plot_confusion_matrix(cnf_matrix, classes=class_names,
                      title='Matriz de confusión Rendom Forest')

plt.show()

Variables mas relevantes

De los resultados mostrados anteriormente, se puede observar que el modelo random forest es el que entrega mejores resultados con un 90% de accuracy. De este modelo es posible obtener la importancia de cada variable en el proceso de clasificación, a continuación se encuentran todas las variables ordenadas por su varianza explicada, es decir, su importancia dentro de la predicción, en donde se puede ver que las características más importantes son tenure, MonthlyCharges y Contract_Month-to-month, lo que tiene relación con las hipotesis planteadas anteriormente, en donde se postulaba que los clientes con mayor tiempo o mayor tenure en la compañia tenían mayor afinidad con ésta y por ende, más posibilidades de permanecer a ella. Con respecto a cargos mensuales se postuló que que las personas con mayor gastos eran mas propensas a cambiar de compañía para disminuir estos gastos. Finalmente, con el tipo de contrato, se vio en el análisis exploratorio que las personas con contratos mes a mes eran mas propensas a irse de la compañía.

In [35]:
clf_rf.fit(X_over,y_over)
print('\nCaracteristicas ordenadas por importancia (RF)')
feature_importances = clf_rf.feature_importances_
importance_order = np.argsort(-feature_importances)
feature_names = data.columns.values.tolist() 
for index in importance_order:
    print('\t%.3f %s' % (feature_importances[index], feature_names[index]))
Caracteristicas ordenadas por importancia (RF)
	0.179 tenure
	0.172 MonthlyCharges
	0.078 Contract_Month-to-month
	0.053 TechSupport_No
	0.039 PaymentMethod_Electronic check
	0.028 OnlineSecurity_No
	0.022 InternetService_Fiber optic
	0.021 OnlineBackup_No
	0.021 gender_Female
	0.019 SeniorCitizen
	0.019 Contract_Two year
	0.019 OnlineSecurity_Yes
	0.017 gender_Male
	0.017 PaperlessBilling_No
	0.016 Contract_One year
	0.015 PaperlessBilling_Yes
	0.014 MultipleLines_Yes
	0.014 PaymentMethod_Bank transfer (automatic)
	0.014 Partner_Yes
	0.014 Partner_No
	0.013 DeviceProtection_No
	0.013 Dependents_Yes
	0.013 PaymentMethod_Credit card (automatic)
	0.013 MultipleLines_No
	0.013 Dependents_No
	0.013 StreamingMovies_No
	0.012 OnlineBackup_Yes
	0.012 StreamingTV_Yes
	0.012 DeviceProtection_Yes
	0.012 PaymentMethod_Mailed check
	0.012 StreamingMovies_Yes
	0.012 StreamingTV_No
	0.011 InternetService_DSL
	0.011 StreamingMovies_No internet service
	0.010 TechSupport_Yes
	0.009 DeviceProtection_No internet service
	0.008 TechSupport_No internet service
	0.004 PhoneService_No
	0.004 PhoneService_Yes
	0.003 MultipleLines_No phone service
	0.001 InternetService_No
	0.000 StreamingTV_No internet service
	0.000 OnlineSecurity_No internet service
	0.000 OnlineBackup_No internet service