Cómo programar el algoritmo Kmeans en Python

El algoritmo kMeans es uno de los algoritmos de clusterización más utilizados en el mundo del machine learning. Usar el algoritmo kMeans en Python es muy sencillo gracias a scikit-learn. Sin embargo, ¿conoces cómo funciona el algoritmo kMeans por dentro, los problemas que puede tener y las buenas prácticas que debemos seguir a la hora de utilizarlo?

En este post vamos a aprender todo eso y mucho más. Para ello, vamos a programar el algoritmo kMeans desce 0 y vamos a ir resolviendo todos los problemas que nos vayamos encontrando. ¿Suena interesante? ¡Pues vamos a ello!

Cómo funciona el algoritmo Kmeans

El algoritmo kMeans encuentra aquellos k puntos (llamados centroides) que minimizan la raíz de la suma de errores al cuadrado. Este proceso lo realiza de forma iterativa, hasta que el error total no baje más. En ese momento habremos llegado a un mínimo y nuestras observaciones estarán clasificados en diferentes grupos o clusters.

Así pues, el algoritmo Kmeans consta de los siguientes puntos:

  1. Inicializamos k centroides de forma aleatoria
  2. Calcular la raíz de la suma de desviaciones al cuadrado.
  3. Asignar un centroide a cada una de las observaciones.
  4. Calcular el error total y compararla con la suma en la anterior iteración.
  5. Si disminuye el error, recalcular centroides y repetir el proceso.

Como ves, a nivel conceptual es un algoritmo muy sencillo, aunque verás cómo vamos a encontrarnos unos puntos más complicados. En cualquier caso, para ver cómo funciona, primero necesitaré unos datos sobre los que usar el algoritmo kmeans en Python.

Creación de datos ficticios

Así pues, voy a crear unos datos ficticios muy diferenciados para tres grupos, al igual que hice en el post cómo programar una red neuronal desde 0 en Python.

Para ello, voy a crear una función que, dadas unas coordenadas, un radio y un número de datos, creará esa cantidad de datos distribuidos de forma aleatoria alrededor del centro elegido.

De esta forma, crearemos tres grupos diferenciados en los que, puede ser que datos de diferentes grupos estén cerca, pudiendo dar así lugar a ciertos errores.

import math
import numpy as np
import pandas as pd
np.random.seed(123)

def circulo(num_datos = 100,R = 1, minimo = 0,maximo= 1, center_x = 0 , center_y = 0):
    pi = math.pi
    r = R * np.sqrt(np.random.uniform(minimo, maximo, size = num_datos)) 
    theta = np.random.uniform(minimo, maximo, size= num_datos) * 2 * pi

    x = center_x + np.cos(theta) * r
    y = center_y + np.sin(theta) * r

    x = np.round(x,3)
    y = np.round(y,3)

    df = np.column_stack([x,y])
    df = pd.DataFrame(df)
    df.columns = ['x','y']
    return(df)

# Create data
datos_1 = circulo(num_datos = 20,R = 10, center_x = 5, center_y = 30)
datos_2 = circulo(num_datos = 20,R = 10, center_x = 20, center_y = 10)
datos_3 = circulo(num_datos = 20,R = 10, center_x = 50, center_y = 50)

data = datos_1.append(datos_2).append(datos_3)
data.head()
xy
0-0.54223.761
18.12925.661
24.23925.298
3-0.69125.230
43.53921.645

Si visualizamos nuestros datos veremos como se trata de unos datos en los que hay tres grupos diferenciados:

import matplotlib.pyplot as plt
%matplotlib inline

plt.scatter(datos_1['x'], datos_1['y'], c = 'b')
plt.scatter(datos_2['x'], datos_2['y'], c = 'r')
plt.scatter(datos_3['x'], datos_3['y'], c = 'g')
plt.show()
Datos a Clasificar por algoritmo kmeans

Como vemos, tenemos tres grupos de datos bien diferenciados. Nuestro objetivo será crear un algorimto kmeans en Python que sea capaz de resolver este problema.

Siguiendo la explicación anterior, el primer paso para crear nuestro algoritmo kmeans en Python será calcular la raíz de la suma de errores al cuadrado. Así pues, ¡vamos a por ello!

Programar algoritmo kMeans en Python desde 0

Inicializamos los k centroides de forma aleatoria

Lo primero de todo, debemos inicializar k centroides de forma aleatoria. Esto no tiene mucho misterio. Sin embargo, para que el proceso de asignación sea más rápido, es interesante que los centroides estén dentro del rango de los propios datos. Esto ayudará a que el algoritmo encuentre los valores óptimos antes, similar a lo que ocurre en la iniciación de parámetros de otros algoritmos, como las redes neuronales.

Así pues, creamos una función en la que, dados unos datos y una cantidad de centroide, los inicializa:

def initialize_centroids(k, data):

    n_dims = data.shape[1]
    centroid_min = data.min().min()
    centroid_max = data.max().max()
    centroids = []

    for centroid in range(k):
        centroid = np.random.uniform(centroid_min, centroid_max, n_dims)
        centroids.append(centroid)

    centroids = pd.DataFrame(centroids, columns = data.columns)

    return centroids

centroids = initialize_centroids(3, data)
centroids
xy
052.8333889.953092
115.16810729.151304
252.62257157.644971

Ahora que ya tenemos nuestros centroides, debemos aprender a calcular el error.

Calcular la raíz de la suma de errores al cuadrado

Seguramente, hayas oído que el algoritmo kMeans consiste en medir la distancia. En realidad, no es así, el algoritmo kMeans busca minimizar la raíz de la suma de errores al cuadrado. Este indicador se puede calcular de la siguiente manera:

$$ error = \sqrt{(x_{2} – x_{1})^2 + (y_{2} – y_{1})^2} $$

Curiosamente, esta fórmula coincide exactamente con la distancia Euclídea:

Fórmula de la distancia euclídea

Es por eso que muchas veces se confunde, aunque ahora ya sabes que no es así. Y es que, si el algoritmo kMeans se basara en distancias, en un principio, podría usar otro tipo de medida de distancia (como ocurre en el algoritmo kNN), pero no es así.

En cualquier caso, podemos crear una función que calcule la raíz de la suma de errores al cuadrado:

def calculate_error(a,b):
    '''
    Given two Numpy Arrays, calculates the root of the sum of squared errores.
    '''
    error = np.square(np.sum((a-b)**2))

    return error    

Como ya tenemos los centroides, los datos y la forma de calcular el error, podemos comprobar que nuestro error funciona correctamente. Para ello simplemente debemos obtener los errores de los 3 centroides a una observación y compararlo visualmente:

errors = np.array([])
for centroid in range(centroids.shape[0]):
    error = calculate_error(centroids.iloc[centroid, :2], data.iloc[0,:2])
    errors = np.append(errors, error)

errors
array([ 9239109.47028511,    76100.30291143, 15797406.01662303])

Como vemos, según los datos, el segundo centroide (índice 1) es el que más cerca está de nuestros datos. Vamos a comprobarlo visualmente:

plt.scatter(data.iloc[1:,0], data.iloc[1:,1],  marker = 'o', alpha = 0.2)
plt.scatter(centroids.iloc[:,0], centroids.iloc[:,1],  marker = 'o', c = 'r')
plt.scatter(data.iloc[0,0], data.iloc[0,1],  marker = 'o', c = 'g')
for i in range(centroids.shape[0]):
    plt.text(centroids.iloc[i,0]+1, centroids.iloc[i,1]+1, s = centroids.index[i], c = 'r')
Error de los centrodes a la primera observacion kmeans

Efectivamente, tras realiza la comprobación visual vemos como, efectivamente, el segundo centroide (índice 1) es el que más cerca está del resto de las observaciones. Así pues, parece que nuestra función de error funciona correctamente.

Así pues, ahora que ya sabemos cómo encontrar el centroide más cercano, sigamos programando la función kmeans en Python. Siguiendo los pasos anteriores, para cada una de las observaciones deberemos encontrar el centroide que más cerca esté.

Asignar un centroide a cada una de las observaciones

Asignar a un centroide a cada obervación es sencillo. Simplemente debemos encontrar la posición del valor mínimo en en la lista de errores. Esto lo podemos conseguir muy sencillo combinado las funciones amin y where.

np.where(errors == np.amin(errors))[0].tolist()[0]
1

Así pues, debemos aplicar este mismo proceso a todas las observaciones. Como será algo recurrente, lo mejor será definirlo como una función.

def assign_centroid(data, centroids):
    '''
    Receives a dataframe of data and centroids and returns a list assigning each observation a centroid.
    data: a dataframe with all data that will be used.
    centroids: a dataframe with the centroids. For assignment the index will be used.
    '''

    n_observations = data.shape[0]
    centroid_assign = []
    centroid_errors = []
    k = centroids.shape[0]


    for observation in range(n_observations):

        # Calculate the errror
        errors = np.array([])
        for centroid in range(k):
            error = calculate_error(centroids.iloc[centroid, :2], data.iloc[observation,:2])
            errors = np.append(errors, error)

        # Calculate closest centroid & error 
        closest_centroid =  np.where(errors == np.amin(errors))[0].tolist()[0]
        centroid_error = np.amin(errors)

        # Assign values to lists
        centroid_assign.append(closest_centroid)
        centroid_errors.append(centroid_error)

    return (centroid_assign,centroid_errors)

data['centroid'], data['error'] = assign_centroid(data.iloc[:,:2] ,centroids)
data[['centroid', 'error']].head()
centroiderror
0176100.302911
113810.746835
2118034.697838
3171229.148068
4136703.174251

Ahora que ya tenemos la asignación realizada, podemos comprobar visualmente cómo ha quedado esta asignación:

colors = {0:'red', 1:'blue', 2:'green'}

plt.scatter(data.iloc[:,0], data.iloc[:,1],  marker = 'o', c = data['centroid'].apply(lambda x: colors[x]), alpha = 0.5)
plt.scatter(centroids.iloc[:,0], centroids.iloc[:,1],  marker = 'o', s=300, 
           c = centroids.index.map(lambda x: colors[x]))
Resultado de la primera clusterizacion kmeans

Como vemos, se han asignado los puntos más cercanos para cada uno de los centroides. Ahora que ya sabemos cómo asignar un centroide a cada observación, sigamos programando nuestro algoritmo kmeans en Python.

Calcular la suma de errores total

Calcular la suma de errores total es algo muy sencillo. Como ya hemos guardado el error de la operación en una columna, simplemente hacer la suma de los errores nos dará la uma de errores total:

data['error'].sum()
7089622.406081449

Esta suma deberemos compararla con el resultado de la iteración anterior. En nuestro caso, se trata de la primera iteración por lo que siempre vamos a seguir.

Asimismo, comentar que generalmente la coincidencia con el error anterior debe ser exacta. Esto significaría que nuestros centroides no se han movido, puesto que han llegado a un punto que minimiza el error.

En cualquier caso, esto es algo que veremos cuando lo montemos todo junto. Por el momento, seguimos programando nuestro algoritmo kmeans en Python. ¡Veamos como recalcular centroides!

Recalcular la posición de los centroides

Si el punto anterior no se ha cumplido, es decir, si el error total ha disminuido, deberemos recalcular la posición de los centroides para repetir el proceso. Para recalcular los centroides simplemente debemos calcular la posición media del centroide como una media de sus variables.

Como en el dataframe data contamos con la información de la observación y los centroides que se ha asignado a cada observación, es algo que podemos hacer de forma muy sencilla con este dataframe:

data_columns = ['x','y']

centroids = data.groupby('centroid').agg('mean').loc[:,data_columns].reset_index(drop = True)
centroids
xy
026.8310004.002000
110.90225619.928974
248.89880050.526050

De hecho, podríamos volver a visualizar los datos y veremos como los centroides han cambiado su posición, situándose en el centro de los datos:

plt.scatter(data.iloc[:,0], data.iloc[:,1],  marker = 'o', c = data['centroid'].apply(lambda x: colors[x]), alpha = 0.5)
plt.scatter(centroids.iloc[:,0], centroids.iloc[:,1],  marker = 'o', s=300, 
           c = centroids.index.map(lambda x: colors[x]))
Recalcular los centroides

Ahora que tenemos los centroides recalculados, deberíamos repetir el proceso anterior, hasta que el error no disminuya más. Así pues, veámos cómo poner todo junto y terminar de crear nuestro algoritmo kmeans en Python.

Uniéndolo todo para programar nuestro algoritmo kmeans en Python

Por último, simplemente debemos juntar todo el proceso anterior dentro de un bucle while, de tal forma que creemos nuestro algoritmo kmeans en Python. ¡Vamos a ello!

def knn(data, k):
    '''
    Given a dataset and number of clusters, it clusterizes the data. 
    data: a DataFrame with all information necessary
    k: number of clusters to create
    '''

    # Initialize centroids and error
    centroids = initialize_centroids(k, data)
    error = []
    compr = True
    i = 0

    while(compr):
        # Obtain centroids and error
        data['centroid'], iter_error = assign_centroid(data,centroids)
        error.append(sum(iter_error))
        # Recalculate centroids
        centroids = data.groupby('centroid').agg('mean').reset_index(drop = True)

        # Check if the error has decreased
        if(len(error)<2):
            compr = True
        else:
            if(round(error[i],3) !=  round(error[i-1],3)):
                compr = True
            else:
                compr = False
        i = i + 1 

    data['centroid'], iter_error = assign_centroid(data,centroids)
    centroids = data.groupby('centroid').agg('mean').reset_index(drop = True)
    return (data['centroid'], iter_error, centroids)

Ahora podemos aplicar esta función a nuestros datos. Para ello, primero deberé pasar los datos quitando las variables centroid, y error que he creado previamente.

data['centroid'], _, centroids =  knn(data.drop(['centroid','error'], axis = 1),3)
data['centroid'].head()                                   
0    0
1    0
2    0
3    0
4    0
Name: centroid, dtype: int64

Por último, podemos comprobar cómo ha terminado a nivel visual este clustering:

plt.scatter(data.iloc[:,0], data.iloc[:,1],  marker = 'o', c = data['centroid'].apply(lambda x: colors[x]), alpha = 0.5)
plt.scatter(centroids.iloc[:,0], centroids.iloc[:,1],  marker = 'o', s=300, 
           c = centroids.index.map(lambda x: colors[x]))
Resultado de la clusterización inicial

Como vemos el algoritmo ha clusterizado los datos, aunque no parece que haya obtenido el resultado deseado… Veámos qué ha pasado y cómo podemos obtener mejores resultado de nuestro algoritmo kmeans programado en Python!

Cómo mejorar los resultados del algoritmo kMeans programado en Python

Conseguir una mejor clasificación de los datos

Cuando el algoritmo kMeans devuelve un resultado significa que ha llegado a un punto que miniza el error: ha llegado a un mínimo. Sin embargo, ese punto no tiene por qué ser el mínimo global, es decir, la mejor respuesta posible, sino que puede ser un mínimo local. Esto se puede ver claramente en la siguiente imagen:

Diferencia entre mínimo local y mínimo global

Esto se debe a la inicialización aleatoria de los centroides y el azar. Puede darse el caso de que los centroides se hayan inicializado de tal forma que los datos no terminen bien clasificados, resultando en una mala clusterización.

Por suerte, resolver el problema de la inicialización aleatoria de los centroides es muy simple: correr el algoritmo kmeans varias veces. En cada una de estas ocasiones podemos medir el error total, de tal forma que, en los casos en los que el error local sea el mínimo habremos llegado a la mejor solución posible.

Lógicamente esto tiene una contrapartida y es que esto hace que ejecutar el algoritmo kmeans sea algo más lento de lo que en realidad podría ser.

En cualquier caso, vamos a ver cómo podemos solucionarlo en nuestro caso:

num_trials = 10

classifications = []
errors = []
centroids = []

for i in range(num_trials):

    np.random.seed(i)

    iter_class, iter_error, iter_centroid = knn(data.drop(['centroid','error'], axis = 1),4)

    classifications.append(iter_class)
    errors.append(sum(iter_error))
    centroids.append(iter_centroid)

errors
[175816.14665013814,
 175816.14665013814,
 175816.14665013814,
 2997534.4064878933,
 2997534.4064878933,
 2997534.4064878933,
 175816.14665013814,
 175816.14665013814,
 2958942.2373577864,
 175816.14665013814]

Si te fijas, aunque varias pruebas han dado el mismo resultado (175816), otras han dado un resultado subóptimo (2997534, 2958942), es decir, que no han clasificado bien los datos.

En cambio, si nosotros hubiéramos corrido el algoritmo solo una vez, podríamos haber tenido la mala suerte de que, debido a la inicialización aleatoria de los centroides, el resultado no fuera el óptimo.

Sin embargo, si corres el algoritmo varias veces, puedes encontrar cuál de las iteraciones minimiza el error. Esa será la mejor clasificación que habrá conseguido el algoritmo kmeans que acabo de programar.

Así pues, vamos a visualizarlo:

errors = np.array(errors)
best_ind = np.where(errors == errors.min())[0].tolist()[0]

data['centroid'] = classifications[best_ind]

plt.scatter(data.iloc[:,0], data.iloc[:,1],  marker = 'o', c = data['centroid'].apply(lambda x: colors[x]), alpha = 0.5)
plt.scatter(centroids[best_ind].iloc[:,0], centroids[best_ind].iloc[:,1],  marker = 'o', s=300, c = centroids[best_ind].index.map(lambda x: colors[x]))
Clasificacion con error minimo 2

Vaya… algo raro ha pasado. Y es que, aunque efectivamente el error sea menor, parece que ha «desaparecido» un cluster… Esto da pie a otra cuestión importante que debemos tener en cuenta al utilizar el algoritmo kmeans: el desvanecimiento de clusters.

Desvanecimiento de clusters

Si el número de grupos que pedimos al algoritmo es muy diferente a lo que el algoritmo «encuentra» de verdad, puede darse el caso de que un cluster no tenga ningún punto cercano y, por tanto, desaparezca.

Esto obviamente no debemos permitirlo: si el usuario quiere un cluster con $k=3$, tendremos que devolver un resultado con $k=3·. Sin embargo, el desvanecimiento del cluster puede esconder un problema por detrás: que el número de clusters no esté bien elegido.

Veamos un ejemplo de lo que comento:

_, _, centroids =  knn(data.drop(['centroid','error'], axis = 1),5)
centroids
xy
019.89418.44635
14.800926.51980
20.612834.71070
348.898850.52605

Como vemos, hemos pedido que el algoritmo clusterice en 5 clusters, pero solo ha creado 4… Esto no es algo que queramos en nuestro algoritmo kmeans.

Así pues, deberemos modificar nuestro algoritmo para que:

  1. En caso de que un centroice se «desvanezca» volver a inicializarlo.
  2. Mandar un warning en caso de que un centroide se desvanezca.
import warnings

def knn(data, k):
    '''
    Given a dataset and number of clusters, it clusterizes the data. 
    data: a DataFrame with all information necessary
    k: number of clusters to create
    '''

    # Initialize centroids
    centroids = initialize_centroids(k, data)
    error = []
    compr = True
    i = 0

    while(compr):
        # Obtain centroids and error
        data['centroid'], iter_error = assign_centroid(data,centroids)
        error = np.append(error, sum(iter_error))
        # Recalculate centroids
        centroids = data.groupby('centroid').agg('mean').reset_index(drop = True)

        # Re initialize centroids
        if(centroids.shape[0] < k):
            warnings.warn("Cluster devanished! Consider reducing the number of k")
            #raise Warning("Vanished centroid. Consider changing the number of clusters.")
            number_centroids_reinitialize = k - centroids.shape[0] 
            reinitialized_centroids = initialize_centroids(number_centroids_reinitialize, data.drop(['centroid'], axis = 1))

            # Find the index of the centroids that  are missing
            ind_missing = np.isin(np.array(range(k)), centroids.index)
            reinitialized_centroids.index = np.array(range(k))[ind_missing == False]

            # Include the new centroids
            centroids = centroids.append(reinitialized_centroids)

        # Check if the error has decreased
        if(len(error)<2):
            compr = True
        else:
            if(round(error[i],3) !=  round(error[i-1],3)):
                compr = True
            else:
                compr = False
        i = i + 1 


    #data['centroid'], iter_error = assign_centroid(data,centroids)
    #centroids = data.groupby('centroid').agg('mean').reset_index(drop = True)

    return (data['centroid'], error[-1], centroids)

Ahora podemos comprobar cómo es la clasificación tras haber realizado esta modificación:

_, _, centroids = knn(data.drop(['centroid','error'], axis = 1),5)
centroids
<ipython-input-19-68fc52047743>:25: UserWarning: Cluster devanished! Consider reducing the number of k
 warnings.warn("Cluster devanished! Consider reducing the number of k")
xy
054.33840046.538400
12.70685030.615250
219.8941008.446350
348.80450056.169875
445.12114346.924286

Como vemos, en este caso la función nos ha devuelto un warning debido a que se ha desvanecido un cluster, pero la función ha seguido ejecutándose.

Ahora ya sabemos una forma de mejorar el resultado del algoritmo kmeans en Python y uno de los problemas típicos que surge al programarla (y que muchos paquetes ni siquiera incluyen).

Sin embargo, hay otra cuestión muy importante que puede ayudarnos a mejorar el resultado de nuestros clusterings y que es muy sencillo: el escalado de los datos.

Escalado de los datos

Como habíamos visto antes, el algoritmo kmeans se basa en minimizar la raíz de la suma de errores al cuadrado. En este proceso es importante que tengamos en cuenta las escalas de los datos. Al fin y al cabo, si una variable tiene una escala mucho mayor a la otra, influirá mucho más en el error, lo cual puede llevar a clasificaciones incorrectas.

Veamos un ejemplo para comprobar lo que comento:

a = np.array([180,2])
b = np.array([220,2])
c = np.array([230,6])

plt.xlim([0,300])
plt.ylim([0,8])
plt.scatter(a[0], a[1],  marker = 'o', c = 'g', s=300)
plt.scatter(b[0], b[1],  marker = 'o', c = 'b', s=300)
plt.scatter(c[0], c[1],  marker = 'o', c = 'r', s=300)
Importancia de normalizar los datos en algoritmo kmeans

Si viéramos este caso en el que tenemos dos clusters (punto rojo y verde) y una observación (punto azul), visualmente vemos como el punto verde está tiene menos error al punto azul (está más cerca) que el punto rojo.

Sin embargo, si calcumos los errores, veríamos lo siguiente:

print('Error de verde a azul: '+ str(calculate_error(a,b)))
print('Error de rojo a azul: '+ str(calculate_error(c,b))) 
Error de verde a azul: 2560000
Error de rojo a azul: 13456

Como vemos el error del verde al azul es mucho mayor que del rojo al azul… Por tanto nuestro algoritmo asignaría el punto azul al cluster rojo, aunque visualmente vemos que no debería ser así. Esto es debido a las escalas: como el eje X tiene una escala mucho mayor, el error es más grande que en el eje y.

Para solucionarlo, simplemente debemos normalizar los datos, es decir, hacer que todos los datos sigan la misma escala. Esto se puede conseguir aplicando la siguiente fórmula:

\(\)  $$ x_{norm}= \frac{x – x_{min}}{ x_{max}- x_{min}} $$

De esta forma conseguiremos que los datos tengan un rango de 0 a 1, de tal modo que todos los datos sigan la misma escala. Lo comprobamos en el caso anterior:

def normalize(x):
    return (x-x.min())/(x.max()-x.min())

temp_data = pd.DataFrame([a,b,c], columns = ['x','y'])
temp_data = temp_data.apply(normalize)

print('Error de verde a azul (datos normalizados): '+ str(calculate_error(temp_data.loc[0],temp_data.loc[1])))
print('Error de rojo a azul (datos normalizados): '+ str(calculate_error(temp_data.loc[2],temp_data.loc[1])))
Error de verde a azul (datos normalizados): 0.4096000000000002
Error de rojo a azul (datos normalizados): 1.0816000000000001

Como ves, tras normalizar los datos, la situación ha cambiado: el cluster que menos error tiene respecto al punto azul es el cluster verde, que es justamente lo que nosotros vemos visualmente.

Así pues, antes de aplicar el algoritmo kmeans (y el resto de algoritmos de clustering) es fundamental normalizar primero nuestros datos. Es algo muy sencillo, y el impacto puede ser muy grande.

Dicho esto hay una última cuestión que es importante para usar bien nuestro algoritmo kmeans en Python: saber cómo elegir el número de clusters.

Cómo elegir el número de clusters con el algoritmo kmeans en Python

Hasta ahora el número de clusters que he elegido ha sido completamente subjetivo. Sin embargo, cuando nos enfrentamos a un problema real (como la segmentación de usuarios, por ejemplo), no sabemos cuántos clusters pueden existir. Así pues, ¿cómo elegimos el número de clusters?

Elegir el número de clusters a asignar es muy sencillo. Para ello hay que entender dos cuestiones:

  • Si eliges un número de clusters muy bajo, el error de los clusters a las observaciones será alto, puesto que estarán lejos.
  • Si eliges un número de clusters muy muy alto, el error será muy bajo, hasta convertirse en 0.

Es decir, que a medida que vamos aumentando el número de clusters, el error total que genera nuestro algoritmo va a ir disminuyendo. Sin embargo, esta disminución no va a darse en el mismo modo: pasar de 1 cluster a dos disminuirá más el error que pasar de $n-1$ a $n$.

Así pues, si graficamos el error total para distinta cantidad de clusters habrá un punto en el que, el error seguirá disminuyendo, pero de una forma mucho menos exagerada. Este será el número de clusters idóneo que deberíamos elegir.

Esta forma de elegir el número de clusters se conoce como el método del codo, ya que estamos buscando el codo de la gráfica.

Vemos cómo funciona con nuestro ejemplo:

total_errors = []
n = 10

for i in range(n):
    _,it_error, _ = knn(data.drop(['centroid','error'], axis = 1),i+1) 
    total_errors.append(it_error)

plt.figure(figsize=(10,5))
plt.plot(range(1,11), total_errors )
Regla del Codo para elegir la k en algoritmo kmeans Python

Como vemos, la regla del codo nos dice que con nuestros datos deberíamos elegir entre 2 y 3 clusters (yo me quedaría con 3), que justamente el número de grupo de datos que he creado al principio del todo.

Como ves, elegír el número de clusters es muy muy sencillo, aunque, eso sí, depende del número de datos que tengamos puede llevar tiempo.

Conclusión

Como ves, el algoritmo kmeans es un algoritmo muy sencillo de entender. Sin embargo, tiene varias cuestiones (como el desvanecimiento de clusters) que, si no se programa desde cero, es muy fácil que se pasen por alto.

Como siempre, espero que te haya resultado interesante. Si es así, puedes suscribirte para estar al día de los nuevos post que publico. En cualquier caso, ¡nos vemos en el siguiente!