Generando datos con figuras
Dada la famosa festividad del 14 de febrero, curiosamente
busqué por internet como crear datasets con formas
predefinidas. Como la búsqueda no fue exitosa, decidí
implementarla para crear un lindo scatter plot
con
forma de corazón.
Implementación
import numpy as np
import matplotlib.pyplot as plt
from scipy.misc import imread
from sklearn.metrics import r2_score
numpy
& matplotlib
fueron las herramientas fundamentales,
y la gracia de imread
es que te permite cargar una imagen
como un numpy.array
de inmediato.
Lo siguiente fue buscar una imagen (en blanco y negro) de un corazón lo que pasaría a ser la máscara
loaded_image = imread('test.png')
Al ser una imagen en formato PNG
, la matríz queda como $$m \times n \times 4$$,
debido a que este formato tiene los 3 colores RGB y además el canal Alpha.
Como es una imagen en blanco y negro, sólo necesitamos el canal Alpha que define
lo negro de la imagen ((0, 0, 0, 1) es negro). Es por esto que se puede reducir la dimensión a sólo
$$ m \times n $$
loaded_image = np.squeeze(loaded_image[:, :, 3])
Luego, como se tiene la matriz $$ m \times n $$, en el fondo los datos
serían $$ {(i, j): M_{ij} \neq 0 }$$, y numpy
tiene una función que hace
exáctamente esto:
x, y = loaded_image.nonzero()
# se plotea la primera imagen
plt.scatter(x, y)
plt.savefig("prev.png", dpi=300)
Ya se tiene un primer resultado. Obviamente, quiero que el corazón esté en vertical, por lo tanto se transpone la matriz y luego se hace un flip en el eje horizontal:
loaded_image = np.flip(np.transpose(loaded_image), 1)
Finalmente, para generar una imagen más atractiva, se reduce la densidad de los datos generando una máscara de valores a tomar (usando las bondades de numpy):
mask = np.random.choice(len(x), int(len(x)*0.02))
X = x[mask]
Y = y[mask]
plt.clf() # se limpia la figura
plt.scatter(X, Y, s=10)
plt.savefig("prev_final.png", dpi=300)
La densidad de los datos se reduce al 2%, para finalmente tener un resultado así:
Luego, haciendo unos ajustes estéticos, se obtiene la figura final
scale=1.5
fig, ax = plt.subplots(figsize=np.array((4,4.1))*scale)
# simplemente se crea una función lineal y = x
x_ = np.linspace(X.min(), X.max(), Y.shape[0])
# se calcula la correlacion entre la funcion lineal y los valores
r_value = r2_score(Y, x_)
ax.scatter(X, Y, s=10, color='red')
ax.plot(x_, x_, color='black', lw=1)
# se agrega una flecha simulando la flecha del típico logo de San Valentin
ax.arrow(x_.min(), x_.min(), x_.max() - x_.min(), x_.max() - x_.min(), head_width=10, head_length=10, fc='k', ec='k')
# funcion custom para crear titulo: https://gist.github.com/etra0/153a8fc12279c235e99ea3a1d1ed5a29
set_title(fig, ax, "Feliz San Valentín", "Desde Matplotlib", r"$r^2$: {0:.2f}".format(r_value))
plt.savefig("result.png", dpi=300, bbox_inches='tight')
Post-data
Un coeficiente $$ r^2 $$ negativo indica que el modelo no representa a los datos. Relevant Link: When is R squared negative?