K-Means Clustering: algoritmo, aplicaciones y desventajas

¿Te suena haber escuchado más de una vez algo sobre el K-Means clustering? Normal. Se trata de uno de los algoritmos de Machine Learning no supervisados más utilizados por los Data Scientists y que además es muy fácil tanto de usar como de interpretar.

En este post voy a hacer que entiendas qué es el clustering y que sepas aplicar correctamente el K-Means.

Clustering

El clustering es de las herramientas más utilizadas durante el EDA para ver segmentaciones de los datos y tener una intuición de cómo están estructurados los datos.

Esta técnica no supervisada se basa en identificar grupos en los datos de tal manera que todos los datos del grupo (clúster) son datos con características similares mientras que los datos de los otros grupos son diferentes.

Algoritmo K-Means

El objetivo de K-Means es claro: agrupar observaciones similares para descubrir patrones que a simple vista se desconocen. Para conseguirlo, el algoritmo busca un número fijo (k) de clústers en el dataset.

Este número k es un hiperparámetro del algoritmo. Representa el número de centroides (centro del clúster) que queremos encontrar en el dataset. Para saber qué k tenemos que coger utilizamos la regla del codo que explico durante el caso práctico.

El algoritmo intenta minimizar la distancia entre las observaciones que pertenecen a un clúster y su centroide. Es decir, el objetivo del K-Means es minimizar la suma de las distancias entre los puntos y el centroide al que pertenecen.

Funcionamiento paso a paso de K-Means

Suponiendo que tenemos los datos de la imagen de abajo, los pasos de ejecución del algoritmo son los siguientes:

  1. Elección del número de clústers k

    El primer paso siempre es elegir en cuantas agrupaciones queremos segmentar los datos.

  2. Inicializar las coordenadas de los centroides

    Los centroides se inicializan en coordenadas aleatorias. Suponiendo que tenemos k=2, iniciamos dos centroides, uno rojo y otro verde, en puntos aleatorios de los datos.
    Inicializaci?n de centroides del K-Means clustering

  3. Asignamos cada punto a un clúster

    Se calcula la distancia de cada punto a cada centroide, y se agrupa con aquel centroide más próximo.
    Agrupaci?n de datos a los centroides de K-Means clustering

  4. Se recalculan los centroides de los clústers

    Una vez tenemos todos los puntos asignados a un clúster, se recalculan los centroides de manera que vuelven a ser los centros de cada clúster.
    Recalculamos centroides del K-Means Clustering

  5. Se repiten los pasos 3 y 4 hasta que se llega al criterio de parada.

    El proceso de asignar cada punto a un clúster y calcular los centros se repite hasta que se cumple el criterio de parada estipulado.
    Clusters finales K-Means Clustering

Criterio de parada para K-Means Clustering

Existen tres criterios para terminar el algoritmo:

  1. Los centroides dejan de cambiar. Es decir, después de múltiples iteraciones, los centroides de cada clúster no cambian. Por lo que se asume que el algoritmo ha convergido.
  2. Los puntos dejan de cambiar de clúster. Parecido al anterior, pero esta vez no nos fijamos en los centroides, si no en los puntos que pertenecen a cada clúster. Cuando se observa que no hay un intercambio de clústers se asume que el modelo está entrenado.
  3. Límite de iteraciones. Podemos fijar un número máximo de iteraciones que queremos que nuestro algoritmo ejecute antes de pararlo. Cuando llega a ese número, se asume que el modelo no va a mejorar drásticamente y se para el entrenamiento.

Caso Práctico en Python

Para demostrar el potencial del algoritmo, vamos a realizar segmentación de clientes de un centro comercial utilizando K-Means. Los datos que vamos a utilizar los podemos encontrar en Kaggle.

Lo primero de todo, como siempre, es importar las librerias que vamos a necesitar para el ejemplo:

# Importamos las librerias necesarias
import pandas as pd 
import matplotlib.pyplot as plt 
import seaborn as sns 
import plotly as py
import plotly.io as pio
import plotly.express as px
from sklearn.cluster import KMeans
from sklearn import preprocessing
from yellowbrick.cluster import KElbowVisualizer
import warnings
warnings.filterwarnings("ignore")
py.offline.init_notebook_mode(connected = True)
pio.renderers.default='browser'

Vamos a ver qué tenemos…

df = pd.read_csv('/content/Mall_Customers.csv')
print('Dimensiones del df:', df.shape)
#____________________________________________________________
# Vamos a sacar informacion sobre los tipos y nulos del df
df.drop_duplicates(inplace = True)
tab_info=pd.DataFrame(df.dtypes).T.rename(index={0:'tipo de la columna'})
tab_info=tab_info.append(pd.DataFrame(df.isnull().sum()).T.rename(index={0:'campos nulos (cant)'}))
tab_info=tab_info.append(pd.DataFrame(df.isnull().sum()/df.shape[0]*100).T.
                         rename(index={0:'campos nulos (%)'}))
display(tab_info)
#__________________
#
display(df[:5])

Como hemos dicho antes, K-Means encuentra clústers basándose en la distancia, por lo que es importante que no tengamos variables categóricas. En nuestro caso, Gender es una de ellas. Vamos a pasarla a binaria en el que ‘Hombre’ vale 0 y ‘Mujer’ 1. Vamos a ver la distribución de las demás variables también:

df['Gender'] = pd.get_dummies(df['Gender']).values[:,0]
plt.style.use('fivethirtyeight')
plt.figure(1 , figsize = (15 , 6))
n = 0 
for x in ['Age' , 'Annual Income (k$)' , 'Spending Score (1-100)']:
    n += 1
    plt.subplot(1 , 3 , n)
    plt.subplots_adjust(hspace =0.5 , wspace = 0.5)
    sns.distplot(df[x] , bins = 20)
    plt.title('Distplot of {}'.format(x))
Distribuci?n de las variables para KMeans

Tenemos CustomerID, que es una variable identificadora. Como esta variable no va a aportarnos nada a la hora de clusterizar nos la cargamos. El siguiente paso será normalizar las variables para que todas estén en la misma escala y no afecte a la hora de calcular la distancia entre puntos:

X = df.copy()
X.drop(labels=['CustomerID'], axis=1, inplace=True)
X1 = preprocessing.normalize(X)

Empezamos con el algoritmo

Elección de k con la regla del codo

Ahora viene la chicha del post. Vamos a elegir el número de clústers idóneos para nuestro problema. Para ello, vamos a realizar varias ejecuciones con una k diferente (desde 1 clúster hasta 12) y representaremos en un gráfico la distancia media de cada punto hasta su centroide y el tiempo de entrenamiento necesario.

La idea es que a medida que vamos aumentando la cantidad de centroides, la distancia media de los puntos al centroide irá disminuyendo cada vez menos. La norma mnemotécnica es la norma del codo puesto que la gráfica es una curva y el número de clústers óptimo será el ‘codo’ del brazo.

La librería yellowbrick tiene un método rápido para visualizar esto.

model = KMeans()
visualizer = KElbowVisualizer(model, k=(1,12))
visualizer.fit(X1)        # Entrenamos con los datos
visualizer.show()        # Renderizamos la imagen
Norma del codo Kmeans

En nuestro caso, cualquier número de cl?sters entre 4 y 6 nos valdría. Este es un punto donde entra el criterio experto para tomar la decisión de cual coger. Yo me voy a fiar de yellowbrick y me quedo con k=4.

Ahora ya solo nos queda entrenar K-Means con k=4 y ver los resultados.

algorithm = KMeans(n_clusters = 5 ,init='k-means++', n_init = 10 ,max_iter=300, 
                        tol=0.0001,  random_state= 111  , algorithm='elkan')
algorithm.fit(X1)
labels = algorithm.labels_
centroids = algorithm.cluster_centers_
df['label'] =  labels

Visualización

Para la visualización yo he optado por usar plotly. Se trata de una librería de visualización que permite crear gráficos interactivos. Podeis utilizar también bokeh, como os enseño en este post.

fig = px.scatter_3d(df, x='Age', y='Spending Score (1-100)', z='Annual Income (k$)',
              color='label')
fig.show()
fig.write_html("/content/file.html")

En la imagen anterior podemos ver claramente los 4 clústers, aunque vemos que los clústers 2 y 4 son parecidos en el centro de la figura, vamos a realizar un PCA para poder mostrar las 3 componentes principales a ver si es verdad que son diferentes. (Por cierto, aquí tienes explicado el PCA).

from sklearn.decomposition import PCA
pca = PCA(n_components=3)
# Entrenamos el PCA con nuestros datos, y lo aplicamos a los datos
principalComponents = pca.fit_transform(X1)
principalDf = pd.DataFrame(data = principalComponents
             , columns = ['component 1', 'component 2', 
                          'component 3'])
principalDf['label'] = df['label']
fig = px.scatter_3d(principalDf, x='component 1', y='component 2', z='component 3',
              color='label')

fig.show()

Ahora podemos ver como los clústers están sin mezclar, por lo que, como hemos dicho al principio del post, el K-Means ayuda a descubrir patrones ocultos en los datos.

Desventajas del K-Means

Ya hemos visto la potencia que tiene este algoritmo. Por lo sencillo que es de aplicar y la valiosa información sobre nuestros datos que nos aporta. Como no es oro todo lo que reluce, tengo que comentaros también las desventajas que ofrece:

  • Tenemos que elegir k nosotros mismos. Es muy posible que nosotros cometamos un error, o que sea imposible escoger una k óptima.
  • Es sensible a outliers. Los casos extremos hacen que el clúster se vea afectado. Aunque esto puede ser algo positivo a la hora de detectar anomalías.
  • Es un algoritmo que sufre de la maldición de la dimensionalidad.

Conclusión

El clústering es una técnica muy popular para problemas sin etiqueta y también para tareas de EDA. El K-Means es el rey de esta técnica por su sencillez tanto de entender como de aplicar. Se basa en agrupar los datos según la distancia entre ellos. Aunque como todo en esta vida tiene peros.

¿Quieres aprender ML desde lo básico? Te dejo 2 post para que te inicies en este mundo.