Clustering de K-Means con el método Elbow

En esta breve guía, aprenda a aplicar el método Elbow para determinar el K (número de clústeres) óptimo para el agrupamiento de K-means utilizando Python y Scikit-Learn.

El agrupamiento de K-means es un algoritmo de aprendizaje no supervisado que agrupa datos en función de la distancia euclidiana de cada punto a un punto central llamado centroide. Los centroides se definen por medio de todos los puntos que están en el mismo grupo. El algoritmo primero elige puntos aleatorios como centroides y luego itera ajustándolos hasta la convergencia total.

Una cosa importante que debe recordar al usar K-means es que el número de clústeres es un hiperparámetro, se definirá antes de ejecutar el modelo.

K-means se puede implementar usando Scikit-Learn con solo 3 líneas de código. Scikit-learn también tiene disponible un método de optimización de centroide, kmeans++, que ayuda a que el modelo converja más rápido.

{.icon aria-hidden=“true”}

Advice If you'd like to read an in-depth guide to K-Means Clustering, read our ¡Guía definitiva para la agrupación en clústeres de K-Means con Scikit-Learn"!

Para aplicar el algoritmo de agrupación en clústeres K-means, carguemos el conjunto de datos Palmer Penguins, elija las columnas que se agruparán y use Seaborn para trazar un diagrama de dispersión con clústeres codificados por colores.

{.icon aria-hidden=“true”}

Nota: Puedes descargar el dataset desde este Enlace.

Importemos las bibliotecas y carguemos el conjunto de datos de Penguins, recortándolo en las columnas elegidas y eliminando filas con datos faltantes (solo había 2):

1
2
3
4
5
6
7
8
9
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
from sklearn.cluster import KMeans

df = pd.read_csv('penguins.csv')
print(df.shape) # (344, 9)
df = df[['bill_length_mm', 'flipper_length_mm']]
df = df.dropna(axis=0)

Podemos usar el método Elbow para tener una indicación de grupos para nuestros datos. Consiste en la interpretación de un gráfico de líneas con forma de codo. El número de racimos es donde se dobla el codo. El eje x de la gráfica es el número de conglomerados y el eje y es la suma de cuadrados dentro de los conglomerados (WCSS) para cada número de conglomerados:

1
2
3
4
5
6
7
8
9
wcss = []

for i in range(1, 11):
    clustering = KMeans(n_clusters=i, init='k-means++', random_state=42)
    clustering.fit(df)
    wcss.append(clustering.inertia_)
    
ks = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
sns.lineplot(x = ks, y = wcss);

El método del codo indica que nuestros datos tienen 2 grupos. Tracemos los datos antes y después de la agrupación:

1
2
3
fig, axes = plt.subplots(nrows=1, ncols=2, figsize=(15,5))
sns.scatterplot(ax=axes[0], data=df, x='bill_length_mm', y='flipper_length_mm').set_title('Without clustering')
sns.scatterplot(ax=axes[1], data=df, x='bill_length_mm', y='flipper_length_mm', hue=clustering.labels_).set_title('Using the elbow method');

Este ejemplo muestra cómo el método Elbow es solo una referencia cuando se usa para elegir el número de conglomerados. Ya sabemos que tenemos 3 tipos de pingüinos en el conjunto de datos, pero si tuviéramos que determinar su número usando el método Elbow, nuestro resultado sería 2 grupos.

Dado que K-means es sensible a la variación de datos, veamos las estadísticas descriptivas de las columnas que estamos agrupando:

1
df.describe().T # T is to transpose the table and make it easier to read

Esto resulta en:

1
2
3
                    count   mean        std         min     25%     50%     75%     max
bill_length_mm      342.0   43.921930   5.459584    32.1    39.225  44.45   48.5    59.6
flipper_length_mm   342.0   200.915205  14.061714   172.0   190.000 197.00  213.0   231.0

Observe que la media está lejos de la desviación estándar (std), esto indica una alta varianza. Intentemos reducirlo escalando los datos con Standard Scaler:

1
2
3
4
from sklearn.preprocessing import StandardScaler

ss = StandardScaler()
scaled = ss.fit_transform(df)

Ahora, repitamos el proceso del método Elbow para los datos escalados:

1
2
3
4
5
6
7
8
9
wcss_sc = []

for i in range(1, 11):
    clustering_sc = KMeans(n_clusters=i, init='k-means++', random_state=42)
    clustering_sc.fit(scaled)
    wcss_sc.append(clustering_sc.inertia_)
    
ks = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
sns.lineplot(x = ks, y = wcss_sc);

Esta vez, el número sugerido de conglomerados es 3. Podemos graficar los datos con las etiquetas de conglomerados nuevamente junto con los dos gráficos anteriores para comparar:

1
2
3
4
fig, axes = plt.subplots(nrows=1, ncols=3, figsize=(15,5))
sns.scatterplot(ax=axes[0], data=df, x='bill_length_mm', y='flipper_length_mm').set_title('Without cliustering')
sns.scatterplot(ax=axes[1], data=df, x='bill_length_mm', y='flipper_length_mm', hue=clustering.labels_).set_title('With the Elbow method')
sns.scatterplot(ax=axes[2], data=df, x='bill_length_mm', y='flipper_length_mm', hue=clustering_sc.labels_).set_title('With the Elbow method and scaled data');

Al utilizar la agrupación en clústeres de K-means, debe predeterminar la cantidad de clústeres. Como hemos visto al usar un método para elegir nuestro k número de conglomerados, el resultado es solo una sugerencia y puede verse afectado por la cantidad de variación en los datos. Es importante realizar un análisis en profundidad y generar más de un modelo con diferentes _k_s al agrupar.

Si no hay una indicación previa de cuántos clústeres hay en los datos, visualícelos, pruébelos e interprételos para ver si los resultados del agrupamiento tienen sentido. Si no, vuelva a agrupar. Además, mire más de una métrica e instancia diferentes modelos de agrupamiento: para K-means, mire el puntaje de silueta y tal vez el agrupamiento jerárquico para ver si los resultados siguen siendo los mismos.