Sin duda k-means es uno de los algoritmos de aprendizaje automático no supervisado más popular. El objetivo de k-means es simple: agrupa puntos de datos similares con el objetivo de descubrir patrones subyacentes. Para lograr este objetivo, k-means busca un número fijo (k) de agrupamientos (clústers) en el conjunto de datos .
1. Funcionamiento básico de k-means.
Aunque estoy casi seguro que si has llegado hasta aquí ya conoces cómo funciona k-means (y lo que buscas es un ejemplo de uso) permíteme que te haga una muy breve descripción de su funcionamiento.
En k-means se define de inicio un número k, que se refiere al número de centroides en los que se dividirá el conjunto de datos. Cada centroide sería la ubicación que marca el centro de cada agrupación.
A cada punto se asigna uno de los grupos mediante la reducción de la suma de cuadrados en el grupo. Dicho de otra forma, el algoritmo k-means identifica k número de centroides, y luego asigna cada punto de los datos al grupo más cercano, mientras mantiene los centroides lo más pequeños posible.
Una vez tenemos cada punto asociado a un clúster, podemos etiquetarlo en el dataframe original asociándolo a dicho grupo y “catalogando” por tanto nuestros datos.
2. Datos de inicio: valores de las acciones de Samsung.
Vamos a aplicar el algoritmo sobre un conjunto de datos de las acciones de Samsung en bolsa para determinar cómo se agrupan usando k-means. Para ello nos descargamos de la web de Yahoo Finanzas el histórico con los valores desde el 1 de enero de 2008 de las acciones de Samsung Electronics Co., Ltd.
De los datos descargados, buscaremos patrones de agrupamiento entre dos señales elegidas, en este caso seleccionamos la señal CLOSE (precio de cierre diario) y la señal VOLUME (volumen de contrataciones diario). Contaremos con una dataframe con los datos diarios desde el 1 de enero de 2008 hasta el 28 de junio de 2019, lo que nos da un total de 2849 registros (que serán algo menos cuando limpiemos los valores nulos).
NOTA: podemos aplicar k-means sobre todas las señales/variables necesarias, pero para poder visualizar los agrupamientos k-means en una gráfica 2-D aplicaremos el algoritmo exclusivamente sobre la dos señales anteriormente detalladas.
3. Carga de librerías y del dataframe.
#%% Carga de librerías. import pandas as pd from sklearn import preprocessing from sklearn.cluster import KMeans import matplotlib.pyplot as plt #%% Carga del dataframe. df = pd.read_excel("Samsung.xlsx")
Las librerías usadas serán Pandas para almacenar y manipular el dataframe, Preprocessing para normalizar los datos antes de aplicar al algoritmo, KMeans para realizar el clustering, y http://exponentis.es/coomeva-eps-citas-por-internet para hacer las representaciones gráficas.
El dataframe cargado queda de la siguiente forma:
4. Preprocesado de los datos.
En primer lugar, eliminamos los http://exponentis.es/mujeres-solteras-mobifriends-manabiy resetamos el índice. El objetivo de dicho reseteo es que el índice que numera las filas no se reajusta cuando eliminamos filas vacías, y pueden presentarse problemas de dimensionalidad al extraer columnas para posteriormente agregarlas:
#%% Se eliminan filas que tengan valor NaN. df = df.dropna() df = df.reset_index(drop=True)
En nuestro ejemplo el dataset “limpio” es de 2784 registros, es decir, que se han eliminado 66 registros NA.
El siguiente paso es extraer la columna DATE (fecha) que no usaremos en el algoritmo k-means. Antes de extraerla la guardaremos en la variable dates, ya que volveremos a insertarla en nuestro dataframe tras aplicar el algoritmo:
#%% Eliminamos columna de fecha que no usaremos en el algoritmo. dates = df['Date'] # Guardamos la columna Date. df = df.drop('Date', 1) # Borramos la columna del dataframe.
5. Normalización de los datos.
Como ocurre con cualquier algoritmo de Machine Learning que utilice funciones de distancia, los datos deben ser normalizados antes de aplicarles el algoritmo. En este caso utilizaremos la conocer hombres mayores de 50 que normaliza todos los datos entre [0, 1].
#%% Se normalizan los datos con MinMax() min_max_scaler = preprocessing.MinMaxScaler() df_escalado = min_max_scaler.fit_transform(df) df_escalado = pd.DataFrame(df_escalado) # Hay que convertir a DF el resultado. df_escalado = df_escalado.rename(columns = {0: 'Close', 1: 'Volume'})
Tras aplicar el escalado, nuestro nuevo dataframe normalizado se llama df_ escalado y tiene todos sus valores normalizados entre [0,1]:
6. Representación gráfica de los datos.
Ahora podemos echar un vistazo a la representación gráfica de nuestros datos. En el eje x representaremos el precio de cierre (CLOSE) y en el eje y el volumen (VOLUME):
#%% Representación gráfica de los datos. x = df_escalado['Close'].values y = df_escalado['Volume'].values plt.xlabel('Close price') plt.ylabel('Volume') plt.title('Samsung stocks CLOSE vs. VOLUME') plt.plot(x,y,'o',markersize=1)
A priori, la nube de puntos parece indicar que, a mayor volumen menor es el precio de cierre estabilizándose conforme el precio del CLOSE aumenta.
Hay que indicar que hemos representado gráficamente los datos normalizados, pero que, si hubiésemos representado los datos sin normalizar la gráfica sería exactamente igual, con la única diferencia que la escala de los ejes sería diferente.
7. Aplicación de k-means.
El primer paso antes de aplicar k-means es decidir qué valor de k (número de clústeres) queremos usar. Una forma de elegir este valor k es por criterio propio: si conocemos bien la distribución de nuestros datos y queremos “forzar” un número determinado de clústeres simplemente lo elegimos.
La otra opción es realizar una gráfica elbow o de codo para determinar el número óptimo de clústeres. Hacemos una iteración de k-means variando el valor de k, de forma que representamos en el eje x dicho valor de k y en el eje y la suma de los errores cuadráticos (SSE). De esta forma podemos elegir el valor de k dónde se produce el “codo” de la curva:
#%% Curva elbow para determinar valor óptimo de k. nc = range(1, 30) # El número de iteraciones que queremos hacer. kmeans = [KMeans(n_clusters=i) for i in nc] score = [kmeans[i].fit(df_escalado).score(df_escalado) for i in range(len(kmeans))] score plt.xlabel('Número de clústeres (k)') plt.ylabel('Suma de los errores cuadráticos') plt.plot(nc,score)
La curva elbow nos muestra que un valor de k = 5 puede ser apropiado, aunque se podría probar con valores entre 5 y 10 y comparar resultados. No hay una solución, un valor de k, más correcto que otro, ya que el objetivo de una clusterización con k-means es obtener información útil nuestros datos, por lo que nuestra interpretación a posteriori de los clústeres creados marcará la calidad de nuestra solución escogida.
Así que ya podemos aplicar el algoritmo de k-means:
#%% Aplicación de k-means con k = 5. kmeans = KMeans(n_clusters=5).fit(df_escalado) centroids = kmeans.cluster_centers_ print(centroids)
El algoritmo muestra por pantalla las coordenadas de los 5 centroides:
8. Etiquetado de datos.
Ya hemos ejecutado k-means y obtenido los centroides. Ahora podemos asignar cada registro de nuestro dataset a uno de los clústers:
#%% Etiquetamos nuestro dataframe. labels = kmeans.predict(df_escalado) df['label'] = labels
Hemos añadido la columna “label” a nuestro dataframe original sin normalizar, por lo que ahora, cada registro está asignado a un único clúster. Le añadimos también la columna “Date” que extrajimos al inicio para saber a qué fecha corresponde cada registro:
#%% Añadimos la columna de fecha df.insert(0, 'Date', dates)
El dataframe etiquetado queda así:
9. Representación gráfica de los clústeres k-means.
Una vez con los datos etiquetados, podemos visualizar gráficamente en dos dimensiones el clustering realizado por k-means, ya que hemos usado sólo dos variables.
#%% Plot k-means clustering. colores=['red','green','blue','yellow','fuchsia'] asignar=[] for row in labels: asignar.append(colores[row]) plt.scatter(x, y, c=asignar, s=1) plt.scatter(centroids[:, 0], centroids[:, 1], marker='*', c='black', s=20) # Marco centroides. plt.xlabel('Close price') plt.ylabel('Volume') plt.title('Samsung stocks k-means clustering') plt.show()
Hemos creado una lista de 5 colores, una para cada clúster y se ha marcado cada centroide con un punto estrellado de color negro:
La interpretación de los grupos creados por k-means es una tarea que debe realizar el especialista de los datos. En este caso, y de forma simplificada, podríamos describir cada grupo de la siguiente forma:
- Clúster azul: grupo de bajo volumen y precio de cierre alto. Los dos puntos de la parte superior podrían considerarse outliers.
- Clúster rojo: grupo de volumen bajo y precio de cierre medio.
- Clúster fucsia: grupo de volumen medio y precio de cierre medio.
- Clúster verde: grupo de volumen medio y precio de cierre bajo.
- Clúster amarillo: grupo de volumen alto y precio de cierre bajo.
10. Clasificación de nuevas muestras.
Por último, queda por determinar la forma de clasificar nuevas muestras. Es decir, que dados nuevos datos de entrada, determinar a qué clúster pertenecen.
Supongamos que nuestros nuevos datos a categorizar son los siguientes:
- CLOSE: 46.850
- VOLUME: 7.196.370
Introducimos estos nuevos datos como un dataframe de una única fila:
close = 46850 volume = 7196370 nuevo_dato = pd.DataFrame([[close,volume]]) # Nueva muestra nuevo_dato = nuevo_dato.rename(columns = {0: 'Close', 1: 'Volume'})
No podemos introducir como tal estos valores en el algoritmo k-means ya que no están normalizados. Así que en primer lugar hay que normalizar, y para ello debemos agregarlos al conjunto de datos original.
Añadimos por tanto esta nueva fila de datos a nuestro dataframe de inicio y lo guardamos con el nombre df_n para no sobrescribir el original:
df_n = df.append(nuevo_dato)
Nuestro nuevo dataframe df_n tiene aun las columnas “date” y “label” del datafame original, así que las eliminamos y resetamos el índice:
df_n = df_n.drop('Date', 1) df_n = df_n.drop('label', 1) df_n = df_n.reset_index(drop=True)
Ahora procedemos a normalizar el Dataframe completo como hizo anteriormente:
min_max_scaler = preprocessing.MinMaxScaler() df_escalado = min_max_scaler.fit_transform(df_n) df_escalado = pd.DataFrame(df_escalado) # Hay que convertir a DF el resultado. df_escalado = df_escalado.rename(columns = {0: 'Close', 1: 'Volume'})
Ya tenemos nuestros nuevos datos (última final del dataframe) normalizados:
Por tanto, los valores normalizados son:
- CLOSE: 0,789142
- VOLUME: 0,110929
Podemos introducir estos nuevos datos ya normalizados a mano o extraerlos en forma de vector numpy:
close_n = df_escalado['Close'][2784] volume_n = df_escalado['Volume'][2784] import numpy as np X_new = np.array([[close_n, volume_n]]) # Nueva muestra
Por último, introducimos el array X_new en k-means:
new_labels = kmeans.predict(X_new) print(new_labels)
El resultado es el clúster 2, que en nuestro caso es el AZUL, es decir, grupo de bajo volumen y precio de cierre alto.
11. Representación gráfica de la nueva muestra.
Podemos representar gráficamente el nuevo punto y verificar que, efectivamente, corresponde con el clúster AZUL:
#%% Plot del nuevo dato clusterizado. colores=['red','green','blue','yellow','fuchsia'] asignar=[] for row in labels: asignar.append(colores[row]) fig, ax = plt.subplots() x_n = close_n y_n = volume_n plt.plot(x_n,y_n, '*', color = 'lime', markersize = 20) plt.scatter(x, y, c=asignar, s=1) plt.xlabel('Close price') plt.ylabel('Volume') plt.title('Samsung stocks k-means clustering') plt.show()
hola, tu ejemplo es genial, me sirvió de mucho para ir armando algo que necesito clasificar en el trabajo.
pero tengo problemas (debido a mi escaso conocimiento) al querer graficar.
te comento: mi tabla tiene 7 atributos (prestamos, calificación, morosidad, depósitos, etc, etc) y quiero graficarlos, pero no sé cómo realizarlo.
hasta aquí llegué bien:
0 1 2 3 4 5 6 7 label
0 1.000000 0.0 0.0 1.0 0.0 0.012232 0.039595 0.001515 4
1 1.000000 0.0 0.0 1.0 0.0 0.027677 0.025791 0.004117 4
2 1.000000 0.0 0.0 0.0 0.0 0.034001 0.103239 0.081781 5
3 0.833333 0.0 0.0 0.5 0.0 0.051012 0.043279 0.005598 1
4 1.000000 0.0 0.0 0.0 0.0 0.028064 0.016616 0.000984 5
en tu ejemplo usas solamente dos variables: ¿cómo hago con 7? desde ya muchas gracias
Hola Damian, gracias por comentar en el blog.
No se pueden graficar 7 dimensiones para visualizar los clústeres creados. Se pueden graficar 2 (como en mi ejemplo) o 3 haciendo una gráfica 3D. En estos casos lo que puedes hacer es crear varias gráficas cogiendo tus variables 2 a 2 (por ejemplo la 1 con la 2, la 3 con la 4….). También puedes visualizarlas todas con una matriz de dispersión usando pairplot() de la librería Seabron. Aquí tienes un ejemplo:
https://relopezbriega.github.io/blog/2016/09/18/visualizaciones-de-datos-con-python/
Otra opción es hacer clustering con un Mapa Auto-Organizado. En mi blog tiene un ejemplo, pero está en R, no en Python. El mapa auto-organizado “convierte” a 2D todas tus variables (lo convierte en un mapa) y ahí puedes visualizar el clustering perfectamente. Aquí tienes un ejemplo de cómo resultaría:
https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRA3QOI6-oaKNvlnIkTzcwmoH8h9bK6RSia1Qw4W1PQogvWS5-h
Un saludo!
Realmente valoro mucho tu ayuda.
Te comento: empecé a practicar de a poco: sumé una sola variable, con lo cual las cosas quedaron así:
datos=pd.read_csv(“base2.csv”,”;”)
df=pd.DataFrame(datos)
x=df[‘prest’]
y=df[‘depos’]
z=df[‘irreg’]
X=np.array(list(zip(x,y,z))).reshape(len(x),3)
a estas alturas ya tengo en X las 3 variables cargadas.
realizo la curva del codo, el entrenamiento, obteniendo las coordenadas con esas tres variables.
coordenada: [ 457634 1148293 3662] etiqueta: 5
coordenada: [ 457296 1188164 912] etiqueta: 5
coordenada: [453123 396820 5927] etiqueta: 0
coordenada: [452762 302196 7905] etiqueta: 0
coordenada: [451945 292007 890] etiqueta: 0
todo sigue perfecto.
con este código que sigue, puedo graficar cada coordenada:
for i in range(len(X)):
print(“coordenada: “,X[i],” etiqueta: “,labels[i])
plt.plot(X[i][0],X[i][1],colors[labels[i]],markersize=10)
queda de 10, un lujo.
el problema se me está presentando cuando quiero colocar en ese gráfico los centroides. No manejo muy bien este asunto (por eso te solicito consejo) y lo hago a través de este código (el cual no funciona):
plt.scatter(centroids[:,0],centroids[:,1],marker=”x”,s=150,linewidths=5,zorder=10)
estuve practicando bastante y pude hacer que funcione todo, menos colocar los centroides en la gráfica. Se visualizan perfectamente los agrupamientos (6 en mi caso), pero a esos centroides no los puedo incorporar. Como te dije, creo que el problema está con scatter, tengo que usar otro método y no sé cual.
buscando info encontré que “scatter” solamente soporta dos variables ¿qué otro método existe para realizar dicha tarea?
Un abrazo y muchas gracias
Hola Damian,
No tengo mucho tiempo de analizar tu problema, pero a simple vista parece que tus centroides tienen 3 coordenadas con lo cual es imposible representarlos en un plano. Busca hacer una representación 3D de tus datos con matplotlib o modifica las dimensiones de tu problema.
Te recomiendo que hagas consultas en https://stackoverflow.com/ donde la comunidad suele contestar este tipo de preguntas.
Gracias y un saludo.
hola! veo que solo realizas una iteración. esto es debido a kmeans encuentra los centroides optimos o se puede iterar actualizando la ubicación de los centroides??
Gracias pro tu ayuda
Hola Yulieth,
El propio algoritmo kmeans, partiendo de puntos aleatorios (centroides), itera ajustando los grupos que se van ajustando hasta que la asignación no cambia más moviendo los puntos, maximizando la distancia entre los distintos grupos y minimizando la distancia intragrupos.
https://scikit-learn.org/stable/modules/generated/sklearn.cluster.KMeans.html
Un saludo.