Guía definitiva para la agrupación en clústeres de K-Means con Scikit-Learn

En esta guía, analizaremos de manera integral cómo agrupar un conjunto de datos en Python usando el algoritmo K-Means con la biblioteca Scikit-Learn, cómo usar el método elbow, encontrar el número de clúster óptimo e implementar K-Means desde cero. .

Introducción

La agrupación en clústeres de K-Means es uno de los algoritmos de aprendizaje automático no supervisado más utilizados que forman grupos de datos en función de la similitud entre las instancias de datos.

En esta guía, primero veremos un ejemplo simple para comprender cómo funciona el algoritmo K-Means antes de implementarlo usando Scikit-Learn. Luego, discutiremos cómo determinar la cantidad de conglomerados (K) en K-Means, y también cubriremos las métricas de distancia, la varianza y las ventajas y desventajas de K-Means.

Motivación

Imagina la siguiente situación. Un día, mientras caminaba por el vecindario, notó que había 10 tiendas de conveniencia y comenzó a preguntarse qué tiendas eran similares, más cercanas entre sí. Mientras buscaba formas de responder a esa pregunta, se encontró con un enfoque interesante que divide las tiendas en grupos según sus coordenadas en un mapa.

Por ejemplo, si una tienda estuviera ubicada 5 km al oeste y 3 km al norte, le asignaría las coordenadas (5, 3) y las representaría en un gráfico. Tracemos este primer punto para visualizar lo que está pasando:

1
2
3
4
import matplotlib.pyplot as plt

plt.title("Store With Coordinates (5, 3)")
plt.scatter(x=5, y=3)

Este es solo el primer punto, para que podamos hacernos una idea de cómo podemos representar una tienda. Digamos que ya tenemos 10 coordenadas para las 10 tiendas recolectadas. Después de organizarlos en una matriz numpy, también podemos trazar sus ubicaciones:

1
2
3
4
5
6
7
8
9
import numpy as np

points = np.array([[5, 3], [10, 15], [15, 12], [24, 10], [30, 45], [85, 70], [71, 80], [60, 78], [55, 52],[80, 91]])

xs = points[:,0] # Selects all xs from the array
ys = points[:,1]  # Selects all ys from the array

plt.title("10 Stores Coordinates")
plt.scatter(x=xs, y=ys)

Cómo implementar manualmente el algoritmo de K-Means

Ahora podemos ver las 10 tiendas en un gráfico, y el problema principal es encontrar si hay alguna manera de dividirlas en diferentes grupos según la proximidad. Con solo echar un vistazo rápido al gráfico, probablemente notemos dos grupos de tiendas: uno son los puntos inferiores en la parte inferior izquierda y el otro son los puntos superiores a la derecha. Tal vez, incluso podamos diferenciar esos dos puntos en el medio como un grupo separado, creando así tres grupos diferentes.

En esta sección, repasaremos el proceso de agrupar puntos manualmente, dividiéndolos en el número dado de grupos. De esa manera, esencialmente repasaremos cuidadosamente todos los pasos del algoritmo de agrupación en clústeres K-Means. Al final de esta sección, obtendrá una comprensión intuitiva y práctica de todos los pasos realizados durante el agrupamiento de K-Means. Después de eso, lo delegaremos a Scikit-Learn.

¿Cuál sería la mejor manera de determinar si hay dos o tres grupos de puntos? Una forma sencilla sería simplemente elegir un número de grupos, por ejemplo, dos, y luego tratar de agrupar puntos en función de esa elección.

Digamos que hemos decidido que hay dos grupos de nuestras tiendas (puntos). Ahora, necesitamos encontrar una manera de entender qué puntos pertenecen a qué grupo. Esto podría hacerse eligiendo un punto para representar grupo 1 y otro para representar grupo 2. Esos puntos se utilizarán como referencia al medir la distancia de todos los demás puntos a cada grupo.

De esa manera, digamos que el punto ‘(5, 3)’ termina perteneciendo al grupo 1, y el punto ‘(79, 60)’ al grupo 2. Al intentar asignar un nuevo punto ‘(6, 3)’ a los grupos, necesitamos medir su distancia a esos dos puntos. En el caso del punto (6, 3) está más cerca del (5, 3), por lo tanto pertenece al grupo representado por ese punto - grupo 1. De esta manera, podemos agrupar fácilmente todos los puntos en grupos correspondientes.

En este ejemplo, además de determinar el número de grupos (clusters), también estamos eligiendo algunos puntos para que sean una referencia de distancia para nuevos puntos de cada grupo.

Esa es la idea general para entender las similitudes entre nuestras tiendas. Pongámoslo en práctica: primero podemos elegir los dos puntos de referencia aleatoriamente. El punto de referencia del grupo 1 será (5, 3) y el punto de referencia del grupo 2 será (10, 15). Podemos seleccionar ambos puntos de nuestra matriz numpy mediante los índices [0] y [1] y almacenarlos en las variables g1 (grupo 1) y g2 (grupo 2):

1
2
g1 = points[0]
g2 = points[1]

Después de hacer esto, necesitamos calcular la distancia desde todos los demás puntos hasta esos puntos de referencia. Esto plantea una pregunta importante: cómo medir esa distancia. Esencialmente, podemos usar cualquier medida de distancia, pero, para el propósito de esta guía, usemos la Distancia euclidiana_.

{.icon aria-hidden=“true”}

Advice: If you want learn more more about Euclidean distance, you can read our "Cálculo de distancias euclidianas con Numpy" guide.

Puede ser útil saber que la medida de la distancia euclidiana se basa en el teorema de Pitágoras:

$$
c^2 = a^2 + b^2
$$

Cuando se adapta a puntos en un plano - (a1, b1) y (a2, b2), la fórmula anterior se convierte en:

$$
c^2 = (a2-a1)^2 + (b2-b1)^2
$$

La distancia será la raíz cuadrada de c, por lo que también podemos escribir la fórmula como:

$$
euclidiana_{dist} = \sqrt[2][(a2 - a1)^2 + (b2 - b1) ^2)]
$$

{.icon aria-hidden=“true”}

Nota: También puede generalizar la fórmula de la distancia euclidiana para puntos multidimensionales. Por ejemplo, en un espacio tridimensional, los puntos tienen tres coordenadas; nuestra fórmula lo refleja de la siguiente manera:
$$
euclidiana_{dist} = \sqrt[2][(a2 - a1)^2 + (b2 - b1) ^2 + (c2 - c1) ^2)]
$$
Se sigue el mismo principio sin importar el número de dimensiones del espacio en el que estemos operando.

Hasta ahora, hemos elegido los puntos para representar grupos y sabemos cómo calcular distancias. Ahora, juntemos las distancias y los grupos asignando cada uno de nuestros puntos de tienda recopilados a un grupo.

Para visualizar mejor eso, declararemos tres listas. El primero en almacenar puntos del primer grupo - points_in_g1. El segundo para almacenar puntos del grupo 2 - points_in_g2, y el último - group, para etiquetar los puntos como 1 (pertenece al grupo 1) o 2 (pertenece al grupo 2 ):

1
2
3
points_in_g1 = []
points_in_g2 = []
group = []

Ahora podemos iterar a través de nuestros puntos y calcular la distancia euclidiana entre ellos y cada una de las referencias de nuestro grupo. Cada punto estará más cerca de uno de los dos grupos: en función del grupo que esté más cerca, asignaremos cada punto a la lista correspondiente, al mismo tiempo que agregaremos 1 o 2 a la lista de grupo:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
for p in points:
    x1, y1 = p[0], p[1]
    euclidean_distance_g1 = np.sqrt((g1[0] - x1)**2 + (g1[1] - y1)**2)
    euclidean_distance_g2 = np.sqrt((g2[0] - x1)**2 + (g2[1] - y1)**2)
    if euclidean_distance_g1 < euclidean_distance_g2:
        points_in_g1.append(p)
        group.append('1')
    else:
        points_in_g2.append(p)
        group.append('2')

Veamos los resultados de esta iteración para ver qué sucedió:

1
2
3
print(f'points_in_g1:{points_in_g1}\n \
\npoints_in_g2:{points_in_g2}\n \
\ngroup:{group}')

Lo que resulta en:

1
2
3
4
5
6
7
8
9
points_in_g1:[array([5, 3])]
 
points_in_g2:[array([10, 15]), array([15, 12]), 
              array([24, 10]), array([30, 45]), 
              array([85, 70]), array([71, 80]),
              array([60, 78]), array([55, 52]), 
              array([80, 91])]
 
group:[1, 2, 2, 2, 2, 2, 2, 2, 2, 2] 

También podemos trazar el resultado de la agrupación, con diferentes colores según los grupos asignados, usando el scatterplot() de Seaborn con el grupo como argumento tono:

1
2
3
import seaborn as sns

sns.scatterplot(x=points[:, 0], y=points[:, 1], hue=group)

Es claramente visible que solo nuestro primer punto está asignado al grupo 1, y todos los demás puntos fueron asignados al grupo 2. Ese resultado difiere de lo que habíamos imaginado al principio. Teniendo en cuenta la diferencia entre nuestros resultados y nuestras expectativas iniciales, ¿hay alguna forma de que podamos cambiar eso? ¡Parece que hay!

Un enfoque es repetir el proceso y elegir diferentes puntos para que sean las referencias de los grupos. Esto cambiará nuestros resultados, con suerte, más en línea con lo que hemos imaginado al principio. Esta segunda vez, podríamos elegirlos no al azar como lo hicimos anteriormente, sino obteniendo una media de todos nuestros puntos ya agrupados. De esa manera, esos nuevos puntos podrían colocarse en medio de los grupos correspondientes.

Por ejemplo, si el segundo grupo tuviera solo puntos (10, 15), (30, 45). El nuevo punto central sería (10 + 30)/2 y (15+45)/2 - que es igual a (20, 30).

Ya que hemos puesto nuestros resultados en listas, podemos convertirlos primero en matrices numpy, seleccionar sus xs, ys y luego obtener la media:

1
2
3
g1_center = [np.array(points_in_g1)[:, 0].mean(), np.array(points_in_g1)[:, 1].mean()]
g2_center = [np.array(points_in_g2)[:, 0].mean(), np.array(points_in_g2)[:, 1].mean()]
g1_center, g2_center

{.icon aria-hidden=“true”}

Consejo: Trate de usar matrices numpy y NumPy tanto como sea posible. Están optimizados para un mejor rendimiento y simplifican muchas operaciones de álgebra lineal. Siempre que intente resolver algún problema de álgebra lineal, definitivamente debería echar un vistazo a la documentación numpy para verificar si hay algún método numpy diseñado para resolver su problema. ¡La posibilidad es que la haya!

Para ayudar a repetir el proceso con nuestros nuevos puntos centrales, transformemos nuestro código anterior en una función, ejecútelo y vea si hubo algún cambio en la forma en que se agrupan los puntos:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
def assigns_points_to_two_groups(g1_center, g2_center):
    points_in_g1 = []
    points_in_g2 = []
    group = []

    for p in points:
        x1, y1 = p[0], p[1]
        euclidean_distance_g1 = np.sqrt((g1_center[0] - x1)**2 + (g1_center[1] - y1)**2)
        euclidean_distance_g2 = np.sqrt((g2_center[0] - x1)**2 + (g2_center[1] - y1)**2)
        if euclidean_distance_g1 < euclidean_distance_g2:
            points_in_g1.append(p)
            group.append(1)
        else:
            points_in_g2.append(p)
            group.append(2)
    return points_in_g1, points_in_g2, group

{.icon aria-hidden=“true”}

Nota: Si nota que sigue repitiendo el mismo código una y otra vez, debe incluir ese código en una función separada. Se considera una buena práctica organizar el código en funciones, especialmente porque facilitan las pruebas. Es más fácil probar una pieza de código aislada que un código completo sin ninguna función.

Llamemos a la función y almacenemos sus resultados en las variables points_in_g1, points_in_g2 y group:

1
2
points_in_g1, points_in_g2, group = assigns_points_to_two_groups(g1_center, g2_center)
points_in_g1, points_in_g2, group

Y también trace el diagrama de dispersión con los puntos coloreados para visualizar la división de grupos:

1
sns.scatterplot(x=points[:, 0], y=points[:, 1], hue=group)

Parece que la agrupación de nuestros puntos está mejorando. Pero aún así, hay dos puntos en el medio del gráfico que podrían asignarse a cualquier grupo al considerar su proximidad a ambos grupos. El algoritmo que hemos desarrollado hasta ahora asigna ambos puntos al segundo grupo.

Esto significa que probablemente podamos repetir el proceso una vez más tomando los medios de las X y las Y, creando dos nuevos puntos centrales *** (centroides) *** para nuestros grupos y reasignándolos en función de la distancia.

Vamos a crear también una función para actualizar los centroides. Todo el proceso ahora se puede reducir a múltiples llamadas de esa función:

1
2
3
4
5
6
7
8
def updates_centroids(points_in_g1, points_in_g2):
    g1_center = np.array(points_in_g1)[:, 0].mean(), np.array(points_in_g1)[:, 1].mean()
    g2_center = np.array(points_in_g2)[:, 0].mean(), np.array(points_in_g2)[:, 1].mean()
    return g1_center, g2_center

g1_center, g2_center = updates_centroids(points_in_g1, points_in_g2)
points_in_g1, points_in_g2, group = assigns_points_to_two_groups(g1_center, g2_center)
sns.scatterplot(x=points[:, 0], y=points[:, 1], hue=group)

Nótese que después de esta tercera iteración, cada uno de los puntos pertenece ahora a diferentes clusters. Parece que los resultados están mejorando, hagámoslo una vez más. Ahora vamos a la cuarta iteración de nuestro método:

1
2
3
g1_center, g2_center = updates_centroids(points_in_g1, points_in_g2)
points_in_g1, points_in_g2, group = assigns_points_to_two_groups(g1_center, g2_center)
sns.scatterplot(x=points[:, 0], y=points[:, 1], hue=group)

Esta cuarta vez obtuvimos el mismo resultado que la anterior. Así que parece que nuestros puntos ya no cambiarán de grupo, nuestro resultado ha alcanzado algún tipo de estabilidad: ha llegado a un estado inmutable o convergido. Además de eso, tenemos exactamente el mismo resultado que habíamos previsto para los 2 grupos. También podemos ver si esta división alcanzada tiene sentido.

Recapitulemos rápidamente lo que hemos hecho hasta ahora. Hemos dividido nuestras 10 tiendas geográficamente en dos secciones: unas en las regiones del sudoeste inferior y otras en el noreste. Puede ser interesante recopilar más datos además de los que ya tenemos: ingresos, número diario de clientes y muchos más. De esa manera podemos realizar un análisis más rico y posiblemente generar resultados más interesantes.

Los estudios de agrupamiento como este se pueden realizar cuando una marca ya establecida quiere elegir un área para abrir una nueva tienda. En ese caso, se tienen en cuenta muchas más variables además de la ubicación.

¿Qué tiene que ver todo esto con el algoritmo K-Means? {#¿Qué tiene que ver todo esto con el algoritmo de los medios?}

Mientras sigue estos pasos, es posible que se haya preguntado qué tienen que ver con el algoritmo K-Means. El proceso que hemos realizado hasta ahora es el algoritmo K-Means. En resumen, determinamos la cantidad de grupos/conglomerados, elegimos puntos iniciales al azar y actualizamos los centroides en cada iteración hasta que los conglomerados convergieron. Básicamente, hemos realizado todo el algoritmo a mano, realizando cuidadosamente cada paso.

La K en K-Means proviene de la cantidad de clústeres que deben establecerse antes de iniciar el proceso de iteración. En nuestro caso K = 2. Esta característica a veces se ve como negativa teniendo en cuenta que existen otros métodos de agrupamiento, como el agrupamiento jerárquico, que no necesita tener un número fijo de clústeres de antemano.

Debido a su uso de medios, K-means también se vuelve sensible a los valores atípicos y extremos: aumentan la variabilidad y dificultan que nuestros centroides desempeñen su papel. Por lo tanto, sea consciente de la necesidad de realizar valores extremos y análisis de valores atípicos antes de realizar un agrupamiento utilizando el algoritmo K-Means.

Además, observe que nuestros puntos se segmentaron en partes rectas, no hay curvas al crear los grupos. Eso también puede ser una desventaja del algoritmo K-Means.

{.icon aria-hidden=“true”}

Nota: Cuando necesite que sea más flexible y adaptable a elipses y otras formas, intente usar un modelo de mezcla gaussiana de medias K generalizadas. Este modelo puede adaptarse a clústeres de segmentación elíptica.

¡K-Means también tiene muchas ventajas! Funciona bien en grandes conjuntos de datos que pueden volverse difíciles de manejar si está utilizando algunos tipos de algoritmos de agrupamiento jerárquico. También garantiza la convergencia y puede generalizar y adaptarse fácilmente. Además de eso, es probablemente el algoritmo de agrupamiento más utilizado.

Ahora que hemos repasado todos los pasos realizados en el algoritmo K-Means y entendido todos sus pros y contras, finalmente podemos implementar K-Means utilizando la biblioteca Scikit-Learn.

Cómo implementar el algoritmo K-Means usando Scikit-Learn

Para verificar dos veces nuestro resultado, hagamos este proceso nuevamente, pero ahora usando 3 líneas de código con sklearn:

1
2
3
4
5
6
from sklearn.cluster import KMeans

# The random_state needs to be the same number to get reproducible results
kmeans = KMeans(n_clusters=2, random_state=42) 
kmeans.fit(points)
kmeans.labels_

Aquí, las etiquetas son las mismas que en nuestros grupos anteriores. Grafiquemos rápidamente el resultado:

1
sns.scatterplot(x = points[:,0], y = points[:,1], hue=kmeans.labels_)

El gráfico resultante es el mismo que el del apartado anterior.

{.icon aria-hidden=“true”}

Nota: Solo mirar cómo hemos realizado el algoritmo K-Means usando Scikit-Learn puede darle la impresión de que es una obviedad y que no necesita preocuparse demasiado por eso. . Solo 3 líneas de código realizan todos los pasos que hemos discutido en la sección anterior cuando hemos repasado el algoritmo K-Means paso a paso. ¡Pero, el diablo está en los detalles en este caso! Si no comprende todos los pasos y las limitaciones del algoritmo, lo más probable es que se enfrente a una situación en la que el algoritmo K-Means le proporcione resultados que no esperaba.

Con Scikit-Learn, también puede inicializar K-Means para una convergencia más rápida configurando el argumento init='k-means++'. En términos más amplios, K-Means++ sigue eligiendo los k centros de conglomerados iniciales al azar siguiendo una distribución uniforme. Luego, cada centro de grupo posterior se elige de los puntos de datos restantes no calculando solo una medida de distancia, sino usando probabilidad. El uso de la probabilidad acelera el algoritmo y es útil cuando se trata de conjuntos de datos muy grandes.

{.icon aria-hidden=“true”}

Consejo: Puede obtener más información sobre los detalles de K-Means++ leyendo ["K-Means++: Las ventajas de una siembra cuidadosa"](https://theory.stanford.edu/~sergei/ papers/kMeansPP-soda.pdf), propuesto en 2007 por David Arthur y Sergei Vassilvitskii.

El método del codo: elección del mejor número de grupos {#el método del codo: elección del mejor número de grupos}

¡Hasta aquí todo bien! Hemos agrupado 10 tiendas según la distancia euclidiana entre puntos y centroides. Pero, ¿qué pasa con esos dos puntos en el medio del gráfico que son un poco más difíciles de agrupar? ¿No podrían formar un grupo separado también? ¿De verdad cometimos un error al elegir los grupos K=2? ¿Tal vez teníamos grupos K=3? Incluso podríamos tener más de tres grupos y no ser conscientes de ello.

La pregunta que se hace aquí es cómo determinar el número de grupos (K) en K-Means. Para responder a esa pregunta, debemos comprender si habría un grupo "mejor" para un valor diferente de K.

La forma ingenua de averiguarlo es agrupando puntos con diferentes valores de K, por lo tanto, para K=2, K=3, K=4, etc.:

1
2
3
for number_of_clusters in range(1, 11): 
    kmeans = KMeans(n_clusters = number_of_clusters, random_state = 42)
    kmeans.fit(points) 

Pero, agrupar puntos para diferentes Ks solo no será suficiente para entender si hemos elegido el valor ideal para K. Necesitamos una forma de evaluar la calidad del agrupamiento para cada K que hemos elegido.

Cálculo manual de Dentro de la suma de cuadrados de clúster (WCSS) {#calculando manualmente la suma de cuadrados de clúster dentro de swcss}

Este es el lugar ideal para introducir una medida de cuánto están cerca unos de otros nuestros puntos agrupados. Esencialmente describe cuánta varianza tenemos dentro de un solo grupo. Esta medida se denomina Dentro de la suma de cuadrados de los clústeres, o WCSS para abreviar. Cuanto más pequeño es el WCSS, más cerca están nuestros puntos, por lo tanto, tenemos un clúster mejor formado. La fórmula WCSS se puede utilizar para cualquier número de clústeres:

$$
WCSS = \sum(Pi_1 - Centroide_1)^2 + \cdots + \sum(Pi_n - Centroide_n)^2
$$

{.icon aria-hidden=“true”}

Nota: En esta guía, estamos usando la distancia euclidiana para obtener los centroides, pero también se podrían usar otras medidas de distancia, como Manhattan.

Ahora podemos asumir que hemos optado por tener dos clústeres e intentar implementar el WCSS para comprender mejor qué es el WCSS y cómo usarlo. Como dice la fórmula, necesitamos sumar las diferencias al cuadrado entre todos los puntos de conglomerados y los centroides. Entonces, si nuestro primer punto del primer grupo es (5, 3) y nuestro último centroide (después de la convergencia) del primer grupo es (16.8, 17.0), el WCSS será:

$$
WCSS = \sum((5,3) - (16.8, 17.0))^2
$$

$$
WCSS = \sum((5-16.8) + (3-17.0))^2
$$

$$
WCSS = \suma((-11,8) + (-14,0))^2
$$

$$
WCSS = \suma((-25,8))^2
$$

$$
WCSS = 335,24
$$

Este ejemplo ilustra cómo calculamos el WCSS para un punto del clúster. Pero el grupo generalmente contiene más de un punto, y debemos tenerlos en cuenta al calcular el WCSS. Lo haremos definiendo una función que reciba un grupo de puntos y centroides, y devuelva la suma de los cuadrados:

1
2
3
4
5
6
def sum_of_squares(cluster, centroid):
    squares = []
    for p in cluster:
        squares.append((p - centroid)**2)
        ss = np.array(squares).sum()
    return ss

Ahora podemos obtener la suma de cuadrados para cada grupo:

1
2
g1 = sum_of_squares(points_in_g1, g1_center)
g2 = sum_of_squares(points_in_g2, g2_center)

Y suma los resultados para obtener el WCSS total:

1
g1 + g2

Esto resulta en:

1
2964.3999999999996

Entonces, en nuestro caso, cuando K es igual a 2, el WCSS total es 2964.39. Ahora, podemos cambiar Ks y calcular el WCSS para todos ellos. De esa manera, podemos obtener una idea de qué K debemos elegir para que nuestro agrupamiento funcione mejor.

Cálculo de WCSS mediante Scikit-Learn

Afortunadamente, no necesitamos calcular manualmente el WCSS para cada K. Después de realizar el agrupamiento de K-Means para el número de clústeres dado, podemos obtener su WCSS usando el atributo inertia_. Ahora, podemos volver a nuestro ciclo K-Means for, usarlo para cambiar el número de clústeres y enumerar los valores WCSS correspondientes:

1
2
3
4
5
6
wcss = [] 
for number_of_clusters in range(1, 11): 
    kmeans = KMeans(n_clusters = number_of_clusters, random_state = 42)
    kmeans.fit(points) 
    wcss.append(kmeans.inertia_)
wcss

Note que el segundo valor en la lista es exactamente el mismo que hemos calculado antes para K=2:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
[18272.9, # For k=1 
 2964.3999999999996, # For k=2
 1198.75, # For k=3
 861.75,
 570.5,
 337.5,
 175.83333333333334,
 79.5,
 17.0,
 0.0]

Para visualizar esos resultados, tracemos nuestro Ks junto con los valores de WCSS:

1
2
ks = [1, 2, 3, 4, 5 , 6 , 7 , 8, 9, 10]
plt.plot(ks, wcss)

Hay una interrupción en una gráfica cuando x = 2, un punto bajo en la línea y uno aún más bajo cuando x = 3. Fíjate que nos recuerda a la forma de un codo. Al trazar las K junto con el WCSS, usamos el Método del codo para elegir la cantidad de K. Y la K elegida es exactamente el punto del codo más bajo, por lo que sería 3 en lugar de 2, en nuestro caso:

1
2
3
ks = [1, 2, 3, 4, 5 , 6 , 7 , 8, 9, 10]
plt.plot(ks, wcss);
plt.axvline(3, linestyle='--', color='r')

Podemos volver a ejecutar el algoritmo de conglomerados de K-Means para ver cómo se verían nuestros datos con tres conglomerados:

1
2
3
kmeans = KMeans(n_clusters=3, random_state=42)
kmeans.fit(points)
sns.scatterplot(x = points[:,0], y = points[:,1], hue=kmeans.labels_)

Ya estábamos satisfechos con dos grupos, pero según el método del codo, tres grupos se ajustarían mejor a nuestros datos. En este caso, tendríamos tres tipos de tiendas en lugar de dos. Antes de usar el método del codo, pensábamos en los grupos de tiendas del suroeste y noreste, ahora también tenemos tiendas en el centro. Tal vez esa podría ser una buena ubicación para abrir otra tienda ya que tendría menos competencia cerca.

Medidas alternativas de calidad del clúster

También hay otras medidas que se pueden utilizar al evaluar la calidad del conglomerado:

  • Puntuación de silueta: analiza no solo la distancia entre los puntos dentro del grupo, sino también entre los propios grupos.
  • Entre Conglomerados Suma de Cuadrados (BCSS) - métrica complementaria al WCSS
  • Error de suma de cuadrados (SSE)
  • Radio máximo: mide la distancia más grande desde un punto hasta su centroide
  • Radio promedio: la suma de la mayor distancia desde un punto hasta su centroide dividida por el número de grupos.

Se recomienda experimentar y conocer cada uno de ellos ya que dependiendo del problema, algunas de las alternativas pueden ser más aplicables que las métricas más utilizadas (WCSS y Silhouette Score).

Al final, como con muchos algoritmos de ciencia de datos, queremos reducir la variación dentro de cada grupo y maximizar la variación entre diferentes grupos. Entonces tenemos grupos más definidos y separables.

Aplicación de K-Means en otro conjunto de datos

Usemos lo que hemos aprendido en otro conjunto de datos. En esta ocasión, intentaremos encontrar grupos de vinos similares.

{.icon aria-hidden=“true”}

Nota: Puede descargar el conjunto de datos aquí.

Comenzamos importando pandas para leer el archivo CSV (Valores separados por comas) wine-clustering en una estructura Dataframe:

1
2
3
import pandas as pd

df = pd.read_csv('wine-clustering.csv')

Después de cargarlo, echemos un vistazo a los primeros cinco registros de datos con el método head():

1
df.head()

Esto resulta en:

1
2
3
4
5
6
    Alcohol     Malic_Acid  Ash     Ash_Alcanity    Magnesium   Total_Phenols   Flavanoids  Nonflavanoid_Phenols    Proanthocyanins     Color_Intensity     Hue     OD280   Proline
0   14.23       1.71        2.43    15.6            127         2.80            3.06        0.28                    2.29                5.64                1.04    3.92    1065
1   13.20       1.78        2.14    11.2            100         2.65            2.76        0.26                    1.28                4.38                1.05    3.40    1050
2   13.16       2.36        2.67    18.6            101         2.80            3.24        0.30                    2.81                5.68                1.03    3.17    1185
3   14.37       1.95        2.50    16.8            113         3.85            3.49        0.24                    2.18                7.80                0.86    3.45    1480
4   13.24       2.59        2.87    21.0            118         2.80            2.69        0.39                    1.82                4.32                1.04    2.93    735

Disponemos de multitud de medidas de sustancias presentes en los vinos. Aquí, tampoco necesitaremos transformar columnas categóricas porque todas ellas son numéricas. Ahora, echemos un vistazo a las estadísticas descriptivas con el método describe():

1
df.describe().T # T is for transposing the table

La tabla de descripción:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
                        count   mean        std         min     25%     50%     75%         max
Alcohol                 178.0   13.000618   0.811827    11.03   12.3625 13.050  13.6775     14.83
Malic_Acid              178.0   2.336348    1.117146    0.74    1.6025  1.865   3.0825      5.80
Ash                     178.0   2.366517    0.274344    1.36    2.2100  2.360   2.5575      3.23
Ash_Alcanity            178.0   19.494944   3.339564    10.60   17.2000 19.500  21.5000     30.00
Magnesium               178.0   99.741573   14.282484   70.00   88.0000 98.000  107.0000    162.00
Total_Phenols           178.0   2.295112    0.625851    0.98    1.7425  2.355   2.8000      3.88
Flavanoids              178.0   2.029270    0.998859    0.34    1.2050  2.135   2.8750      5.08
Nonflavanoid_Phenols    178.0   0.361854    0.124453    0.13    0.2700  0.340   0.4375      0.66
Proanthocyanins         178.0   1.590899    0.572359    0.41    1.2500  1.555   1.9500      3.58
Color_Intensity         178.0   5.058090    2.318286    1.28    3.2200  4.690   6.2000      13.00
Hue                     178.0   0.957449    0.228572    0.48    0.7825  0.965   1.1200      1.71
OD280                   178.0   2.611685    0.709990    1.27    1.9375  2.780   3.1700      4.00
Proline                 178.0   746.893258  314.907474  278.00  500.500 673.500 985.0000    1680.00

Mirando la tabla, está claro que hay cierta variabilidad en los datos - para algunas columnas como Alchool hay más, y para otras, como Malic_Acid, menos. Ahora podemos verificar si hay valores null o NaN en nuestro conjunto de datos:

1
df.info()
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 178 entries, 0 to 177
Data columns (total 13 columns):
 #   Column                Non-Null Count  Dtype  
---  ------                --------------  -----  
 0   Alcohol               178 non-null    float64
 1   Malic_Acid            178 non-null    float64
 2   Ash                   178 non-null    float64
 3   Ash_Alcanity          178 non-null    float64
 4   Magnesium             178 non-null    int64  
 5   Total_Phenols         178 non-null    float64
 6   Flavanoids            178 non-null    float64
 7   Nonflavanoid_Phenols  178 non-null    float64
 8   Proanthocyanins       178 non-null    float64
 9   Color_Intensity       178 non-null    float64
 10  Hue                   178 non-null    float64
 11  OD280                 178 non-null    float64
 12  Proline               178 non-null    int64  
dtypes: float64(11), int64(2)
memory usage: 18.2 KB

No hay necesidad de soltar o ingresar datos, considerando que no hay valores vacíos en el conjunto de datos. Podemos usar un pairplot() de Seaborn para ver la distribución de datos y verificar si el conjunto de datos forma pares de columnas que pueden ser interesantes para la agrupación:

1
sns.pairplot(df)

Al observar el diagrama de pares, dos columnas parecen prometedoras para fines de agrupación: “Alcohol” y “OD280” (que es un método para determinar la concentración de proteína en los vinos). Parece que hay 3 grupos distintos en parcelas que combinan dos de ellos.

Hay otras columnas que también parecen estar en correlación. En particular, Alcohol y Total_Phenols, y Alcohol y Flavanoids. Tienen grandes relaciones lineales que se pueden observar en el diagrama de pares.

Dado que nuestro enfoque es agrupar con K-Means, elijamos un par de columnas, digamos Alcohol y OD280, y probemos el método del codo para este conjunto de datos.

{.icon aria-hidden=“true”}

Nota: Al usar más columnas del conjunto de datos, será necesario trazar en 3 dimensiones o reducir los datos a [componentes principales (uso de PCA)](/implementando-pca-en-python-con -scikit-aprender/). Este es un enfoque válido y más común, solo asegúrese de elegir los componentes principales en función de cuánto explican y tenga en cuenta que al reducir las dimensiones de los datos, se pierde algo de información, por lo que la gráfica es una *aproximación * de los datos reales, no como realmente son.

Tracemos el diagrama de dispersión con esas dos columnas configuradas como su eje para ver más de cerca los puntos que queremos dividir en grupos:

1
sns.scatterplot(data=df, x='OD280', y='Alcohol')

Ahora podemos definir nuestras columnas y usar el método del codo para determinar el número de grupos. También iniciaremos el algoritmo con kmeans++ solo para asegurarnos de que converge más rápidamente:

1
2
3
4
5
6
7
values = df[['OD280', 'Alcohol']]

wcss_wine = [] 
for i in range(1, 11): 
    kmeans = KMeans(n_clusters = i, init = 'k-means++', random_state = 42)
    kmeans.fit(values) 
    wcss_wine.append(kmeans.inertia_)

Hemos calculado el WCSS, por lo que podemos trazar los resultados:

1
2
3
clusters_wine = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
plt.plot(clusters_wine, wcss_wine)
plt.axvline(3, linestyle='--', color='r')

De acuerdo con el método del codo, deberíamos tener 3 grupos aquí. Para el paso final, agrupemos nuestros puntos en 3 grupos y tracemos esos grupos identificados por colores:

1
2
3
kmeans_wine = KMeans(n_clusters=3, random_state=42)
kmeans_wine.fit(values)
sns.scatterplot(x = values['OD280'], y = values['Alcohol'], hue=kmeans_wine.labels_)

Podemos ver los clústeres 0, 1 y 2 en el gráfico. Según nuestro análisis, el grupo 0 tiene vinos con mayor contenido de proteína y menos alcohol, el grupo 1 tiene vinos con mayor contenido de alcohol y bajo contenido de proteína, y el grupo 2 tiene tanto alto contenido de proteína como de alcohol en sus vinos.

Este es un conjunto de datos muy interesante y lo animo a profundizar en el análisis agrupando los datos después de la normalización y PCA, también interpretando los resultados y encontrando nuevas conexiones.

Yendo más lejos: proyecto de extremo a extremo portátil

¿Tu naturaleza inquisitiva te hace querer ir más allá? Recomendamos consultar nuestro Proyecto guiado: ["Predicción práctica del precio de la vivienda: aprendizaje automático en Python"](https://wikihtp.com/courses/hands-on-house- precio-predicción-aprendizaje-máquina-en-python/#cta){target="_blank"}.

[](https://wikihtp.com/ cursos/predicción-de-precio-de-la-casa-práctica-aprendizaje-de-máquina-en-python/#cta)

En este proyecto guiado, aprenderá a crear potentes modelos tradicionales de aprendizaje automático, así como modelos de aprendizaje profundo, utilizar Ensemble Learning y capacitar a los meta-aprendices para predecir los precios de la vivienda a partir de una bolsa de modelos Scikit-Learn y Keras.

Con Keras, la API de aprendizaje profundo creada sobre Tensorflow, experimentaremos con arquitecturas, crearemos un conjunto de modelos apilados y entrenaremos una red neuronal meta-aprendizaje (modelo de nivel 1) para averiguar el precio de un casa.

El aprendizaje profundo es sorprendente, pero antes de recurrir a él, se recomienda intentar resolver el problema con técnicas más simples, como los algoritmos de aprendizaje superficial. Nuestro rendimiento de referencia se basará en un algoritmo de Regresión de bosque aleatorio. Además, exploraremos la creación de conjuntos de modelos a través de Scikit-Learn a través de técnicas como embalaje y votación.

Este es un proyecto integral y, como todos los proyectos de aprendizaje automático, comenzaremos con Análisis exploratorio de datos, seguido de Preprocesamiento de datos y, finalmente, Creación de modelos de aprendizaje superficial y Profundo para ajustarse a los datos que hemos explorado y limpiado previamente.

Conclusión

La agrupación en clústeres K-Means es un algoritmo de aprendizaje automático no supervisado simple pero muy eficaz para la agrupación de datos. Agrupa los datos en función de la distancia euclidiana entre los puntos de datos. El algoritmo de agrupamiento K-Means tiene muchos usos para agrupar documentos de texto, imágenes, videos y mucho más. ás.