Fronteras de decisión

En el apartado anterior hemos pedido a nuestro modelo que realice la predicción para un punto del plano. Pero podríamos imaginar una malla de puntos sobre nuestro plano y pedirle al modelo que hiciese la predicción para todos ellos. Si llevamos el resultado a un gráfico de dispersión mostrando con diferentes colores cada punto en función de la clase prevista por el modelo, estaríamos visualizando las áreas que definen las clases y los límites entre ellas, lo que se conoce como “fronteras de decisión”.

Para crear la malla de puntos vamos a recurrir a la función np.meshgrid, a la que deberemos pasar -si estamos trabajando en el plano- los vectores que contienen las coordenadas x y las coordenadas y de los puntos de la malla.

Por ejemplo, con el siguiente código:

x = np.linspace(0, 8, 3)
y = np.linspace(2, 5, 3)
X, Y = np.meshgrid(x, y)

estamos suponiendo que la malla de puntos es de 3 x 3, y estamos generando un array bidimensional X con las coordenadas x de dichos puntos (tres coordenadas entre los valores 0 y 8), y un array bidimensional Y con las coordenadas y (tres coordenadas entre los valores 2 y 5):

Fronteras de decisión

En el código hemos impuesto límites fijos a las coordenadas a considerar, pero, para poder reutilizar el código aplicándolo a diferentes conjuntos de datos X (características predictivas de entrenamiento), podemos extraer el valor mínimo y el valor máximo de las dos características (recordemos que el objetivo es llevar esta información a un plano y no podemos encarar más que dos características), y añadir un cierto margen de, por ejemplo, el 10% del rango en cada eje:

minX = min(X.iloc[:, 0])       # Valor mínimo 1ª característica
maxX = max(X.iloc[:, 0])       # Valor máximo 1ª característica
minY = min(X.iloc[:, 1])       # Valor mínimo 2ª característica
maxY = max(X.iloc[:, 1])       # Valor máximo 2ª característica
marginX = (maxX - minX) * 0.1  # 10% del rango
marginY = (maxY - minY) * 0.1  # para las dos características
x = np.linspace(minX - marginX, maxX + marginX, 100)  # Vector de coord. X
y = np.linspace(minY - marginY, maxY + marginY, 100)  # Vector de coord. Y
X, Y = np.meshgrid(x, y)       # Generamos las matrices de coordenadas

Es decir, partimos de una estructura X conteniendo las características predictivas, extraemos los valores máximo y mínimo de ambas características (de ambas columnas) y calculamos el margen a añadir en cada eje (variables marginX y marginY). Generamos los vectores de coordenadas x e y, y, por último, las matrices de coordenada X e Y. Vemos cómo estamos reutilizando el nombre de variable X, pero coincide que la tradición lleva a usar este nombre para almacenar ambos tipos de datos y, en todo caso, cuando llevemos el código anterior a una función dejará de ser un problema.

Una vez obtenidas las matrices de coordenadas X e Y, para obtener las predicciones podemos aplanar ambas matrices y situar sus valores en sendas columnas de un array NumPy, y pasar dicho array al método .predict(). Es decir:

Z = model.predict(np.c_[X.ravel(), Y.ravel()]).reshape(X.shape)

Por último, no tenemos más que recurrir a la función contourf de matplotlib para mostrar el resultado:

plt.contourf(X, Y, Z, levels = 2)