Árboles de decisión en python

Los árboles de decisión son algoritmos que generan reglas que nos permiten clasificar conjuntos de datos, es decir, asignarle etiquetas a una serie de observaciones.

Ésta técnica se encuadra dentro de lo que se denomina el aprendizaje supervisado, por tanto, para poder entrenar el modelo necesitaremos un fichero que incluya datos ya etiquetados. Como ejemplo, podemos utilizar el conjunto de datos iris.

Utilizar éstos modelos con python es muy sencillo. Para ello, necesitamos la biblioteca sklearn (y seguramente también pandas para leer los datos y numpy para hacer algunas operaciones).

En primer lugar, debemos cargar el fichero de datos en python. Para ello, podemos utilizar la función read_csv() del paquete pandas.

import numpy as np
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.tree import DecisionTreeClassifier, export_graphviz

#Sustituir la ruta por la ubicación del fichero de datos
Datos = pd.read_csv("/all/crit/Datos/DatosIris.csv", 
                    delimiter = ",", 
                    decimal = ".")

Una vez que tenemos los datos cargados, podemos empezar a trabajar con ellos. Como adelanté hace un momento, para ajustar el modelo utilizaremos la biblioteca sklearn. Al trabajar con ésta biblioteca, debemos dividir nuestro fichero de datos en dos objetos. Uno de ellos contendrá las variables que utilizará nuestro modelo para hacer los pronósticos, y la otra la variable categórica a predecir.

A su vez, debemos dividir cada uno de éstos ficheros en una muestra de entrenamiento y otra de test. Ésta división es muy frecuente en el mundo del aprendizaje automático. Básicamente, utilizamos la muestra de entrenamiento para ajustar el modelo y luego utilizamos la de test para probar el rendimiento del modelo sobre datos que el modelo no ha visto durante su entrenamiento.

Para ello podemos utilizar la función train_test_split() de sklearn. Mediante el parámetro train_size podemos especificar el porcentaje de datos que usaremos para entrenar el modelo. Con 0.7 el 70% de las observaciones de nuestro fichero original serán utilizadas para el entrenamiento y el 30% para la muestra de test.

#Creamos un objeto que contenga solo las variables predictoras
X = Datos.iloc[:, :4]

#Aislamos la variable a predecir en otro objeto
Y = Datos.iloc[:, 4:5]

#Dividimos los datos entre las muestras de entrenamiento y las de prueba
X_train, X_test, Y_train, Y_test = train_test_split(X, Y, 
                                                    train_size = 0.7,
                                                    random_state = 0)

El parámetro random_state sirve para fijar la semilla del generador de números aleatorios. Así nos aseguramos de que siempre obtenemos los mismos resultados

En éste punto tenemos 4 ficheros de datos: X_train y X_test contienen las variables predictoras. Y_train y Y_test contienen la variable a predecir. X_train y Y_train contienen el 70% de los datos mientras que las versiones _test contienen el 30%.

Lo único que falta por hacer es ajustar el modelo como tal. Para ello lo primero es crear una instancia del modelo. Una vez que tenemos la instancia creada, podemos usar el método fit() para comenzar el entrenamiento.

#Creamos la instancia del modelo llamando al constructor
Arbol = DecisionTreeClassifier(random_state = 0)

#Ajustamos el modelo sobre el conjunto de datos
Arbol.fit(X_train, Y_train)
## DecisionTreeClassifier(random_state=0)

Ya tenemos el modelo ajustado. Ahora podemos probar el rendimiento sobre los datos que apartamos. Utilizamos el método predict(), y le pasamos nuestras variables predictoras. Entonces, el modelo procurará asignarle una etiqueta a cada observación.

Importante pasarle los datos de la muestra de test, ya que el rendimiento en la muestra de entrenamiento será mucho más alto (ya ha visto los datos). Por eso generamos dos muestras diferentes, para poder evaluar el rendimiento sobre datos que el modelo no ha visto.

#Elaboramos los pronosticos
Prediccion = Arbol.predict(X_test)
print(Prediccion)
## ['virginica' 'versicolor' 'setosa' 'virginica' 'setosa' 'virginica'
##  'setosa' 'versicolor' 'versicolor' 'versicolor' 'virginica' 'versicolor'
##  'versicolor' 'versicolor' 'versicolor' 'setosa' 'versicolor' 'versicolor'
##  'setosa' 'setosa' 'virginica' 'versicolor' 'setosa' 'setosa' 'virginica'
##  'setosa' 'setosa' 'versicolor' 'versicolor' 'setosa' 'virginica'
##  'versicolor' 'setosa' 'virginica' 'virginica' 'versicolor' 'setosa'
##  'virginica' 'versicolor' 'versicolor' 'virginica' 'setosa' 'virginica'
##  'setosa' 'setosa']

Para evaluar la calidad del modelo podemos utilizar la matriz de confusión.

from sklearn.metrics import confusion_matrix
MC = confusion_matrix(Y_test, Prediccion)
print(MC)
## [[16  0  0]
##  [ 0 17  1]
##  [ 0  0 11]]

En la matriz de confusión las observaciones que han sido clasificadas correctamente están en la diagonal. Podemos ver que al clasificar nuestros datos únicamente hemos cometido un error. Para obtener algunas métricas sobre el rendimiento del modelo podemos utilizar la siguiente función:

def rendimiento_modelo(MC, nombres = None):
    precision_global = np.sum(MC.diagonal()) / np.sum(MC)
    precision_categoria = pd.DataFrame(MC.diagonal() / np.sum(MC, axis = 1)).T
    error_global = 1 - precision_global
    
    if nombres != None:
        precision_categoria.columns = nombres
        
    metricas = {"Matriz de confusión" : MC,
               "Precisión global" : precision_global,
               "Error global" : error_global,
               "Precisión por categoría" : precision_categoria}
               
    return(metricas)
    
indicadores_rendimiento = rendimiento_modelo(MC, list(np.unique(Y)))

for indicador in indicadores_rendimiento:
    print("\n%s:\n%s"%(indicador, str(indicadores_rendimiento[indicador])))
## 
## Matriz de confusión:
## [[16  0  0]
##  [ 0 17  1]
##  [ 0  0 11]]
## 
## Precisión global:
## 0.9777777777777777
## 
## Error global:
## 0.022222222222222254
## 
## Precisión por categoría:
##    setosa  versicolor  virginica
## 0     1.0    0.944444        1.0

Pero ¿cómo realiza el modelo éstas clasificaciones? Para ello genera una serie de reglas. Básicamente al encontrarse con una observación plantea una serie de preguntas, y en función de la respuesta que obtenga plantea otras preguntas. Podemos representar el proceso utilizando mediante un gráfico. Para ello, tendremos que instalar la librería graphviz y matplotlib.

import matplotlib.pyplot as plt
import matplotlib.image as mpimg
import graphviz
GeneraGrafico = export_graphviz(Arbol, 
                           out_file = None,
                           class_names = ["Setosa", "Versicolor", "Virginica"],
                           feature_names = list(X.columns.values),
                           filled = True)
                           
Grafico = graphviz.Source(GeneraGrafico)
Grafico.format = "png"
Archivo = Grafico.render()
img = mpimg.imread(Archivo)
imgplot = plt.imshow(img)
plt.axis("off")
## (-0.5, 730.5, 735.5, -0.5)
plt.show()

Observamos que la primera pregunta que hace es si la variable Petal.Width (el ancho de los pétalos de la flor) es menor o igual que 0.75. Si es menor o igual entonces clasifica la flor como “Setosa”, si es mayor, plantea más preguntas para poder distinguir entre “Virginica” y “Versicolor”.

Conclusiones

Hemos visto que podemos utilizar éstos modelos para etiquetar datos de manera automática. Aunque en éste ejemplo hemos trabajado con datos categóricos, éstos modelos también se pueden aplicar para problemas de regresión, es decir, aquellos en los que la variable a predecir es numérica en vez de categórica.

Los árboles de decisión son la base de otras técnicas algo más avanzadas, como los bosques aleatorios, boruta o isolation forest. Quizás veamos algunas de éstas en el futuro, hasta entonces espero que éste post sea útil.