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?