AulaML Python · C++
Python intermedio · 38 min · Python para Machine Learning

K-Means y aprendizaje no supervisado

Agrupar datos sin etiquetas: centroides, distancia y el método del codo

Sin etiquetas no hay respuestas que copiar, pero sí estructura que descubrir. Aquí ves cómo K-Means alterna asignaciones y centroides, cómo elegir el número de grupos con la inercia y por qué escalar las variables cambia por completo el resultado.

Lección 4 de 7 del módulo
Al terminar esta lección podrás
  • Explicar el ciclo asignar-recalcular de K-Means y cuándo termina
  • Calcular distancias y asignar puntos a centroides a mano
  • Elegir `k` con el método del codo y comprobarlo con la silueta
  • Escalar variables antes de agrupar y justificar por qué
  • Reconocer las limitaciones de K-Means y cuándo probar otro método

Antes necesitas: Fundamentos de Machine Learning · NumPy: vectores, matrices y vectorización · Estadística y análisis exploratorio

En esta lección
  1. ¿Qué es el clustering?
  2. ¿Para qué sirve?
  3. ¿Cómo funciona por dentro?
  4. Sintaxis: KMeans en scikit-learn
  5. Ejemplo básico: la distancia decide
  6. Visualización: puntos y centroides
  7. Traza: cómo se asigna un punto
  8. Ejemplo intermedio: una iteración completa
  9. Cómo elegir k: el codo
  10. Escalado: el paso que cambia el resultado
  11. Limitaciones y otros métodos
  12. Caso práctico: segmentar clientes
  13. Errores comunes
  14. Buenas prácticas
  15. Ejercicios
  16. Soluciones explicadas
  17. Resumen
  18. Evaluación

Tienes las compras de cientos de clientes y ninguna etiqueta que diga «este es del segmento A». No hay respuestas que copiar, pero sí una pregunta razonable: ¿qué grupos existen por sí solos? Eso es el aprendizaje no supervisado.

K-Means es el método más usado para responderla, y también el más malinterpretado. Al terminar esta lección sabrás qué garantiza —y qué no— y podrás mirar un gráfico de grupos sin creer que los colores son la verdad.

¿Qué es el clustering?

El clustering o agrupamiento consiste en repartir ejemplos en grupos de modo que los de un mismo grupo se parezcan entre sí más que a los de otros. No hay etiqueta correcta: el resultado es una hipótesis de estructura, no un hecho. Dos ejecuciones con distinto k o distinta escala producen agrupaciones distintas, y ninguna es «la verdadera».

K-Means es un algoritmo de clustering particional: eliges de antemano cuántos grupos quieres (k) y él reparte todos los puntos entre esos grupos, sin dejar ninguno fuera. Cada grupo queda representado por un centroide, el punto promedio de sus miembros.

¿Para qué sirve?

  • Segmentar clientes: agrupar por frecuencia de compra y gasto para diseñar campañas distintas.
  • Compresión de color: reducir una imagen a 16 colores agrupando los píxeles parecidos.
  • Detección de patrones: encontrar zonas de una ciudad con comportamiento similar de consumo.
  • Preparar datos: crear una columna de segmento que después alimente un modelo supervisado.

¿Cómo funciona por dentro?

El algoritmo alterna dos pasos hasta que nada cambia. Primero asigna cada punto al centroide más cercano, normalmente con distancia euclidiana. Después recalcula cada centroide como la media de los puntos asignados. Si los centroides se mueven, vuelve a asignar; si no, termina.

La inicialización importa: dos puntos de partida distintos pueden terminar en agrupaciones distintas. Por eso se usa k-means++, que elige centroides iniciales separados entre sí, y se repite el proceso varias veces (n_init) quedándose con la solución de menor inercia. La inercia es la suma de distancias al cuadrado de cada punto a su centroide: mide qué tan compactos son los grupos.

Sintaxis: KMeans en scikit-learn

SHELL Instalación
python -m pip install scikit-learn
PYTHON esquema.py
from sklearn.cluster import KMeans

puntos = [[1, 1], [2, 1], [1, 2], [6, 5], [7, 6], [6, 6]]

modelo = KMeans(n_clusters=2, n_init=10, random_state=0)
etiquetas = modelo.fit_predict(puntos)

print(etiquetas)
print(modelo.cluster_centers_.round(2))
print(round(modelo.inertia_, 2))

fit_predict entrena y devuelve la etiqueta de cada punto en una sola llamada. cluster_centers_ guarda las coordenadas de los centroides y inertia_ la suma de errores al cuadrado. Las etiquetas son números arbitrarios: el grupo 0 no es «mejor» que el 1, y su numeración puede cambiar entre ejecuciones.

Ejemplo básico: la distancia decide

Con dos centroides en (1, 1) y (6, 5), un punto en (2, 1) pertenece al que esté más cerca. La distancia euclidiana se calcula con el teorema de Pitágoras:

PYTHON distancias.py
from math import dist

centroides = [(1, 1), (6, 5)]
punto = (2, 1)

for i, c in enumerate(centroides):
    print(f"centroide {i}: {dist(punto, c):.2f}")

La salida es centroide 0: 1.00 y centroide 1: 5.66. La diferencia es enorme, así que la asignación no admite dudas. Cuando dos distancias están muy cerca, el punto cae en una frontera y puede cambiar de grupo con un pequeño movimiento de los centroides.

Visualización: puntos y centroides

Seis puntos y dos centroides iniciales. Recorre los pasos para ver cómo los centroides se desplazan hacia el promedio de su grupo:

Mapa de datos ·Puntos y centroides en dos dimensiones
8 × 2 rango [1, 7] Las seis primeras filas son los datos. Las dos últimas son los centroides, que arrancan sobre dos puntos cualesquiera.
x
y
P1
1
1
P2
2
1
P3
1
2
P4
6
5
P5
7
6
P6
6
6
C1
1
1
C2
6
5

Recorre los pasos para ver cómo cambian las celdas.

Los dos grupos están claramente separados, así que cualquier inicialización razonable llega al mismo resultado. En datos reales los grupos se tocan, y ahí la inicialización y la escala sí cambian la respuesta.

Traza: cómo se asigna un punto

La asignación es una comparación de distancias. Observa el cálculo interno para el mismo punto (2, 1):

Ejecución visual ·Asignación de un punto a su centroide
1 / 6
1 from math import dist
2
3 punto = (2, 1)
4 centroides = [(1, 1), (6, 5)]
5
6 distancias = [dist(punto, c) for c in centroides]
7 elegido = distancias.index(min(distancias))
8
9 print("distancias:", [round(d, 2) for d in distancias])
10 print("cluster:", elegido)
El punto que hay que clasificar tiene coordenadas (2, 1).
Variables
punto = (2, 1)
Salida
—

Avanza paso a paso: observa la línea resaltada, las variables y la salida.

Ejemplo intermedio: una iteración completa

Este código hace exactamente lo que dibuja la matriz, sin scikit-learn: asigna los seis puntos y recalcula los dos centroides.

PYTHON una_iteracion.py
from math import dist

puntos = [(1, 1), (2, 1), (1, 2), (6, 5), (7, 6), (6, 6)]
centroides = [(1, 1), (6, 5)]

grupos = [[], []]
for p in puntos:
    i = 0 if dist(p, centroides[0]) <= dist(p, centroides[1]) else 1
    grupos[i].append(p)

nuevos = []
for g in grupos:
    x = sum(p[0] for p in g) / len(g)
    y = sum(p[1] for p in g) / len(g)
    nuevos.append((round(x, 2), round(y, 2)))

print("tamaños:", [len(g) for g in grupos])
print("centroides:", nuevos)

La salida es tamaños: [3, 3] y centroides: [(1.33, 1.33), (6.33, 5.67)]. Con esos centroides nuevos ningún punto cambia de grupo, así que el algoritmo se detiene en la iteración siguiente. n_init controla cuántas veces se repite todo el proceso con inicializaciones distintas.

Cómo elegir k: el codo

K-Means necesita que le digas k. Una forma de buscarlo es entrenar con varios valores y mirar la inercia: siempre baja al aumentar k, porque más centroides siempre ajustan mejor. Lo útil es encontrar el punto donde la caída deja de ser grande. Para estos seis puntos, la mejor inercia de cada k es:

kInerciaLectura
168.33todos los puntos en un solo grupo
22.67caída enorme: aparecen los dos grupos reales
31.83mejora pequeña, un grupo se parte en dos
41.00cada subgrupo se divide otra vez

El codo está en k = 2: pasar de 68.33 a 2.67 es una mejora de otra magnitud, mientras que las siguientes apenas restan una unidad. La silueta confirma la lectura con otra pregunta: compara la distancia media dentro del grupo con la del grupo vecino. Va de −1 a 1, y con estos puntos da 0.83, un valor alto.

PYTHON kmeans_sklearn.py
from sklearn.cluster import KMeans
from sklearn.metrics import silhouette_score

puntos = [[1, 1], [2, 1], [1, 2], [6, 5], [7, 6], [6, 6]]

modelo = KMeans(n_clusters=2, init=[[1.0, 1.0], [6.0, 5.0]], n_init=1, random_state=0)
modelo.fit(puntos)

print("etiquetas:", modelo.labels_)
print("centroides:", modelo.cluster_centers_.round(2))
print("inercia:", round(modelo.inertia_, 2))
print("silueta:", round(silhouette_score(puntos, modelo.labels_), 2))

La salida es etiquetas: [0 0 0 1 1 1], los centroides [[1.33 1.33] [6.33 5.67]], inercia: 2.67 y silueta: 0.83. Pasar init como lista fija los centroides de partida y hace el experimento reproducible; en un proyecto real deja init="k-means++" y usa n_init=10.

Escalado: el paso que cambia el resultado

La distancia euclidiana suma diferencias al cuadrado de todas las columnas, así que una variable con números grandes domina. Si comparas ingresos (0 a 100 000) con edad (18 a 80), la distancia la decide el ingreso: la edad es invisible. Escalar con StandardScaler pone cada columna en unidades de desviación típica y devuelve el voto a ambas.

Encadena el escalado y el modelo en un Pipeline para que el tratamiento sea idéntico en entrenamiento y en producción. Y hazlo antes de comparar inercias: los valores de la tabla solo son comparables entre sí si se calcularon con la misma escala.

Limitaciones y otros métodos

K-Means supone grupos aproximadamente esféricos, de tamaño parecido y separables por distancia al centro. Falla con formas alargadas, con densidades muy distintas y con ruido: un valor atípico arrastra su centroide. Además, siempre asigna todos los puntos, aunque algunos no pertenezcan a ningún grupo.

MétodoQué aportaCuándo probarlo
KMeansgrupos compactos y rápidosdatos numéricos con grupos redondos
DBSCANgrupos por densidad y puntos de ruidoformas irregulares y outliers
AgglomerativeClusteringjerarquía y dendrogramano sabes k y quieres explorar
GaussianMixturepertenencia probabilísticagrupos que se solapan
PCAreduce dimensiones antes de agruparmuchas columnas correlacionadas

Caso práctico: segmentar clientes

Imagina dos variables por cliente: visitas al mes y gasto mensual en cientos. Con los seis puntos de la lección, los grupos son «compra ocasional» y «compra frecuente». Un cliente nuevo con 5 visitas y 3 de gasto cae del lado del segundo grupo:

PYTHON cliente_nuevo.py
from math import dist

centroides = [(1.33, 1.33), (6.33, 5.67)]
nuevo = (5, 3)

for i, c in enumerate(centroides):
    print(i, round(dist(nuevo, c), 2))

La salida es 0 4.03 y 1 2.98: el cliente se asigna al grupo 1. La decisión de negocio empieza justo aquí: ¿qué campaña recibe cada grupo, y qué pasa con quien queda cerca de la frontera? El algoritmo agrupa; la acción la defines tú.

Errores comunes

  • No escalar: una columna con números grandes decide todos los grupos.
  • Elegir k mirando solo la inercia: siempre baja al aumentar k.
  • Interpretar las etiquetas como categorías reales: el grupo 0 no existe fuera de tu modelo.
  • Ignorar la inicialización: con n_init=1 puedes quedarte en un mínimo local malo.
  • Agrupar variables correlacionadas: dos columnas que miden lo mismo cuentan doble en la distancia.

Buenas prácticas

  • Escala o normaliza antes de calcular distancias, y documenta qué transformación usaste.
  • Prueba varios k y guarda la curva de inercia y la silueta de cada uno.
  • Repite con varias semillas para comprobar que la solución es estable.
  • Describe cada grupo con la media de sus variables originales, no con los centroides escalados.
  • Valida el resultado con alguien del negocio: un grupo sin interpretación no se puede usar.

Ejercicios

Ejercicio 1

Asigna cuatro puntos

Con centroides en (1.5, 1.5) y (8.5, 8.5), asigna los puntos (1, 1), (2, 2), (8, 8) y (9, 9) calculando distancias. Imprime la etiqueta y la distancia al centroide elegido con dos decimales.
Pista

Reutiliza dist de math y guarda en una lista la distancia menor. El índice de esa distancia es la etiqueta.

Ejercicio 2

Calcula la inercia

Con los seis puntos de la lección y los centroides exactos (4/3, 4/3) y (19/3, 17/3), calcula la inercia sin usar scikit-learn: suma la distancia al cuadrado de cada punto a su centroide. Imprime el resultado redondeado a dos decimales.
Pista

Asigna cada punto según su coordenada: los tres primeros van al centroide 0. Recuerda que la inercia no lleva raíz.

Completar código Completa el clustering
from sklearn.cluster import KMeans

modelo = KMeans(n_clusters=, n_init=10, random_state=0)
modelo.fit(puntos)

print("etiquetas:", )
print("inercia:", round(modelo.inertia_, 2))

El número de grupos se pasa en `n_clusters`; las etiquetas quedan guardadas en un atributo con guion bajo final.

Soluciones explicadas

En el primer ejercicio, la simetría hace que todas las distancias mínimas coincidan en 0.71 (√0.5). Eso no significa que los grupos sean iguales: significa que los puntos están a la misma distancia de su centro. Si añadieras un punto en (5, 5), quedaría exactamente en la frontera y su asignación dependería del desempate.

En el segundo, la inercia es la suma de cuadrados, no la raíz. Por eso el valor es pequeño: cada punto está a poco más de medio unitario de su centroide. Con datos reales, la inercia carece de unidades interpretables; sirve para comparar soluciones sobre el mismo conjunto y la misma escala.

Resumen

  • El clustering busca estructura sin etiquetas; el resultado es una hipótesis, no una verdad.
  • K-Means alterna asignar puntos al centroide más cercano y recalcular la media del grupo.
  • La inercia mide compacidad y siempre baja al aumentar k.
  • El codo señala el k donde la mejora deja de compensar; la silueta lo confirma.
  • Sin escalar, la variable con números más grandes domina la distancia.
  • K-Means supone grupos esféricos y sensibles a los outliers; DBSCAN y los métodos jerárquicos cubren otros casos.

Evaluación

Evaluación 1

¿Qué hace K-Means en el paso de actualización?

Evaluación 2

La inercia baja de 68.33 a 2.67 al pasar de k = 1 a k = 2, y a 1.83 con k = 3. ¿Qué sugiere el codo?

Evaluación 3

Agrupas ingresos (0 a 100 000) y edad (18 a 80) sin escalar. ¿Qué ocurre?

Tus notas

¿Ya lo entendiste?

Márcala como completada para llevar tu progreso y desbloquear el siguiente paso de la ruta.