Trabajo Práctico 2: Análisis con Random Forest - Organización de Datos

Alumnos y Padrón

  • Grassano, Bruno - 103855
  • Romero, Adrián - 103371

https://github.com/brunograssano/TP-Organizacion-de-datos

Importamos las bibiliotecas que utilizaremos a lo largo del notebook

In [1]:
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
In [2]:
from sklearn.model_selection import train_test_split
from sklearn.metrics import classification_report
from sklearn.metrics import confusion_matrix
from sklearn.metrics import roc_auc_score
from sklearn.metrics import roc_curve
from sklearn.model_selection import KFold, StratifiedKFold
In [3]:
from sklearn.ensemble import RandomForestClassifier
In [4]:
from preprocessing import prepararSetDeDatos
from preprocessing import prepararSetDeHoldout
from preprocessing import prepararSetDeValidacion
from preprocessing import expansionDelDataset
from preprocessing import conversionAVariablesNormalizadas
In [5]:
from funcionesAuxiliares import mostrarAUCScore
from funcionesAuxiliares import mostrarROCCurve
from funcionesAuxiliares import mostrarMatrizDeConfusion
from funcionesAuxiliares import escribirPrediccionesAArchivo
from funcionesAuxiliares import obtenerDatasets
from funcionesAuxiliares import obtenerHoldout

Importamos los datos y los procesamos

In [6]:
X, y = obtenerDatasets()
X = prepararSetDeDatos(X)
y = prepararSetDeValidacion(y)
In [7]:
X_rf = conversionAVariablesNormalizadas(X)

Funciones Auxiliares

In [8]:
def obtenerMejoresParametros(datosPreprocesados):
    mejor_valor = 0
    mejor_profundidad = None
    mejor_cantidad_estimadores = None
    mejor_criterio = None
    y_array=np.array(y)
    for profundidad in [1,2,3,4,5,6,7,9,10]:
        for criterio in ["gini", "entropy"]:
            for cantidad_estimadores in [1,10,50,100,200]:
                kf = StratifiedKFold(n_splits=7)
                metricas = []
                for fold_idx, (train_index, test_index) in enumerate(kf.split(datosPreprocesados, y_array)):
                    rf = RandomForestClassifier(criterion=criterio, max_depth=profundidad, n_estimators=cantidad_estimadores)
                    rf.fit(datosPreprocesados[train_index], y_array[train_index].ravel())
                    predicciones = rf.predict(datosPreprocesados[test_index])
                    score_obtenida = roc_auc_score(y_array[test_index],predicciones)
                    metricas.append(score_obtenida)

                if np.mean(metricas) >= mejor_valor:
                    mejor_valor = np.mean(metricas)
                    mejor_profundidad = profundidad
                    mejor_criterio = criterio
                    mejor_cantidad_estimadores = cantidad_estimadores
            
    return mejor_valor, mejor_profundidad, mejor_criterio, mejor_cantidad_estimadores

Dividimos el set de datos en sets de training y test

In [9]:
X_train, X_test, y_train, y_test = train_test_split(X_rf, y, test_size=0.25, random_state=0)
Para el dataset expandido:
In [10]:
X_expandido = expansionDelDataset(X)

columnas_codificables_extra = ['pago_categorizado','edades_estratificadas','categoria_invitados']
columnas_numericas_extra = ['4_clusters','10_clusters','total_pagado']

X_exp  = conversionAVariablesNormalizadas(X_expandido,columnas_codificables_extra,columnas_numericas_extra)
X_exp_train, X_exp_test, y_exp_train, y_exp_test = train_test_split(X_exp, y, test_size=0.25, random_state=0)

Random Forest

Random forest es un ensamble que consiste en el entrenamiento de varios clasificadores de árbol de decisión.

Cada árbol del ensamble es construido a partir de una muestra simple con reposición del set de entrenamiento. Además se intenta forzar heterogeneidad entre los árboles al escoger el nodo principal de cada árbol a partir de un subconjunto aleatorio de los features.

En la implementación de sci-kit learn, cada árbol indica la probabilidad de un feature de pertenecer a una clase. Para la predicción final se promedian estas probabilidades.

Los parámetros que se deben definir son:

Profundidad máxima: le impone un limite a la profundidad de máxima de cada árbol, los árboles muy profundos tienden a overfittear.

Criterio: es el criterio con el que se escoge el "mejor feature" para ocupar un nodo. Decimos que un feature es mejor que otro si maximiza la ganancia de información o si tiene baja impureza segun el criterio de impureza Gini.

Cantidad de estimadores: es la cantidad de árboles que se entrenarán.

Buscaremos dos modelos, uno utilizando dataset expandido y otro sin expandir.

In [11]:
mejor_valor, mejor_profundidad, mejor_criterio, mejor_cantidad_estimadores = obtenerMejoresParametros(X_rf)
In [12]:
print(f"El mejor valor fue de AUC fue: {round(mejor_valor,3)}")
print(f"La profundidad encontrada que maximiza el AUC fue: {mejor_profundidad}")
print(f"El criterio encontrado que maximiza el AUC fue: {mejor_criterio}")
print(f"La cantidad de estimadores que maximizan el AUC fue: {mejor_cantidad_estimadores}")
El mejor valor fue de AUC fue: 0.801
La profundidad encontrada que maximiza el AUC fue: 5
El criterio encontrado que maximiza el AUC fue: gini
La cantidad de estimadores que maximizan el AUC fue: 200
Para el dataset expandido:
In [13]:
mejor_valor_exp, mejor_profundidad_exp, mejor_criterio_exp, mejor_cantidad_estimadores_exp = obtenerMejoresParametros(X_exp)
In [14]:
print(f"El mejor valor fue de AUC fue: {round(mejor_valor_exp,3)}")
print(f"La profundidad encontrada que maximiza el AUC fue: {mejor_profundidad_exp}")
print(f"El criterio encontrado que maximiza el AUC fue: {mejor_criterio_exp}")
print(f"La cantidad de estimadores que maximizan el AUC fue: {mejor_cantidad_estimadores_exp}")
El mejor valor fue de AUC fue: 0.809
La profundidad encontrada que maximiza el AUC fue: 7
El criterio encontrado que maximiza el AUC fue: entropy
La cantidad de estimadores que maximizan el AUC fue: 100

Evaluamos las métricas

In [15]:
rf = RandomForestClassifier(max_depth=mejor_profundidad, random_state=0, n_estimators = mejor_cantidad_estimadores, criterion=mejor_criterio)
rf.fit(X_train, y_train)
Out[15]:
RandomForestClassifier(max_depth=5, n_estimators=200, random_state=0)
In [16]:
y_pred = rf.predict(X_test)
In [17]:
print(classification_report(y_test, y_pred, target_names=['No vuelve','Vuelve']))
              precision    recall  f1-score   support

   No vuelve       0.83      0.95      0.88       121
      Vuelve       0.90      0.70      0.79        80

    accuracy                           0.85       201
   macro avg       0.87      0.83      0.84       201
weighted avg       0.86      0.85      0.85       201

Para el dataset expandido:
In [18]:
rf_exp = RandomForestClassifier(max_depth=mejor_profundidad_exp, random_state=0, n_estimators = mejor_cantidad_estimadores_exp, criterion=mejor_criterio_exp)
rf_exp.fit(X_exp_train, y_exp_train)
Out[18]:
RandomForestClassifier(criterion='entropy', max_depth=7, random_state=0)
In [19]:
y_exp_pred = rf_exp.predict(X_exp_test)
In [20]:
print(classification_report(y_exp_test, y_exp_pred, target_names=['No vuelve','Vuelve']))
              precision    recall  f1-score   support

   No vuelve       0.82      0.91      0.86       121
      Vuelve       0.84      0.70      0.76        80

    accuracy                           0.83       201
   macro avg       0.83      0.80      0.81       201
weighted avg       0.83      0.83      0.82       201

Matriz de confusión

In [21]:
mostrarMatrizDeConfusion(y_pred,y_test)

Se puede ver que el random forest entrenado sin el dataset expandido tiene un muy buen accuracy pues lo valores en la diagonal principal son mucho mayores que los de la diagonal invertida.

Se puede observar que tenemos un muy buen precision también: de los que predecimos que volverian (55 + 6), efectivamente 55 vuelven, un 90% de precision. Por otro lado el recall no es tan bueno, de los 55 + 25 que volverian pudimos detectar que 55 volverían. Es decir que casi dos tercios de los que volverían los clasificamos correctamente.

Para el dataset expandido:
In [22]:
mostrarMatrizDeConfusion(y_exp_pred,y_exp_test)

Graficamos la curva ROC

In [23]:
mostrarROCCurve(rf,"Random Forest",X_test, X_train, y_test, y_train)

Como era de esperarse, la curva ROC sobre el set de entrenamiento es más cercana a la ideal que aquella sobre el set de test, sin embargo esta diferencia no es tan grande, ambas curvas son bastante similares y además muy buenas, su área bajo la curva debe ser cercana a 1.

In [24]:
mostrarAUCScore(rf,"Random Forest",X_test, y_test)
AUC para Random Forest: 0.873
Para el dataset expandido:
In [25]:
mostrarROCCurve(rf_exp,"Random Forest",X_exp_test, X_exp_train, y_exp_test, y_exp_train)
In [26]:
mostrarAUCScore(rf_exp,"Random Forest",X_exp_test, y_exp_test)
AUC para Random Forest: 0.873

Como se puede ver, obtuvimos significativamente peores resultados con la expansión del dataset. Por lo tanto realizaremos las predicciones con el modelo que no fue entrenado con el dataset expandido.

Predicciones sobre el nuevo archivo

Realizamos ahora las predicciones del nuevo archivo entregado.

In [27]:
holdout = obtenerHoldout()
ids_usuarios = np.array(holdout['id_usuario'])
holdout = prepararSetDeHoldout(holdout)
holdout_rf = conversionAVariablesNormalizadas(holdout) 
In [28]:
predicciones_holdout = rf.predict(holdout_rf)
In [29]:
escribirPrediccionesAArchivo(predicciones_holdout,"Random Forest",ids_usuarios)