K-means dibuja esferas y asigna todos los puntos a algún cluster; el jerárquico fusiona hasta donde le digas. Ninguno de los dos sabe decir "este punto no pertenece a ningún grupo" — y para MercaFresh esa frase vale dinero, porque un pedido que no se parece a ningún patrón conocido puede ser un error de datos, un abuso de promociones o un fraude. DBSCAN (Density-Based Spatial Clustering of Applications with Noise) agrupa por densidad: un cluster es una zona donde los puntos se apiñan, tenga la forma que tenga, y lo que queda en zonas despobladas se etiqueta como ruido. En esta lección aprenderás sus tres tipos de puntos (core, border, noise), sus dos parámetros (eps y min_samples) y cómo elegirlos con el gráfico k-distance, verás con make_moons un caso donde K-means fracasa y DBSCAN acierta, y lo aplicarás a la detección de pedidos anómalos en MercaFresh, conectándolo con los detectores estadísticos de los módulos anteriores.

Contenido

  1. Clustering por densidad: la idea
  2. Puntos core, border y noise
  3. Los parámetros eps y min_samples
  4. Cómo elegir eps: el gráfico k-distance
  5. Donde K-means falla: formas no esféricas (make_moons)
  6. Detección de anomalías en pedidos MercaFresh
  7. Comparativa: K-means vs. jerárquico vs. DBSCAN
  8. Limitaciones de DBSCAN

Clustering por densidad: la idea

Los dos algoritmos anteriores definían un cluster por proximidad a un centro (K-means) o por orden de fusión (jerárquico). DBSCAN lo define por densidad local: un cluster es un conjunto de puntos conectados entre sí a través de zonas densas. La intuición es cartográfica: si los clientes fueran casas, los clusters serían los pueblos — no importa si el pueblo es redondo, alargado o con forma de herradura; importa que las casas estén juntas. Y las casas aisladas en mitad del monte no pertenecen a ningún pueblo: son ruido.

De esta definición salen gratis las tres propiedades que distinguen a DBSCAN:

  • Clusters de cualquier forma (sigue la densidad, no la distancia a un centro).
  • No hay que fijar K: encuentra tantos clusters como zonas densas existan.
  • Concepto nativo de ruido: los puntos de zonas despobladas reciben la etiqueta especial −1.

Puntos core, border y noise

DBSCAN clasifica cada punto según cuánta compañía tiene en su vecindario de radio eps:

Tipo Definición Papel
Core (núcleo) Tiene al menos min_samples puntos (él incluido) a distancia ≤ eps El esqueleto del cluster: los clusters crecen conectando cores vecinos
Border (frontera) No es core, pero está a ≤ eps de algún core Pertenece al cluster de ese core, sin poder expandirlo
Noise (ruido) Ni core ni border Etiqueta −1: no pertenece a ningún cluster
flowchart LR
    subgraph Cluster["Zona densa = cluster"]
        C1["● core"] --- C2["● core"] --- C3["● core"]
        C3 --- B1["◐ border<br/>(cerca de un core,<br/>pero con pocos vecinos)"]
    end
    N1["○ noise<br/>(aislado: a más de eps<br/>de cualquier core)"]
    Cluster -.-> |"> eps"| N1

El algoritmo funciona así: toma un punto no visitado; si es core, abre un cluster y lo expande recursivamente con todos los puntos alcanzables a través de cores encadenados (cada core anexiona su vecindario, y los cores del vecindario anexionan el suyo); los border se suman al cluster que los alcanza; cuando nada más es alcanzable, el cluster se cierra y se busca el siguiente core libre. Lo que nunca es alcanzado, queda como noise. Nota el parentesco con el single linkage de 05-02 —también encadenaba vecinos próximos—, pero con un filtro de densidad que impide que un puente de dos o tres puntos sueltos una dos pueblos: para tender puente hacen falta cores, y ser core exige compañía.

Los parámetros eps y min_samples

Toda la conducta de DBSCAN cuelga de dos parámetros:

  • eps: el radio del vecindario. Es la definición operativa de "cerca". Demasiado pequeño → casi nadie reúne vecinos, casi todo es ruido. Demasiado grande → los vecindarios se solapan entre grupos distintos y todo colapsa en un mega-cluster.
  • min_samples: cuántos vecinos hacen falta para ser core. Es la definición de "denso". Valores mayores exigen clusters más macizos y expulsan más puntos al ruido. Regla práctica habitual: min_samples ≈ 2 × nº de dimensiones, y nunca menos de 4 salvo datasets minúsculos.

Y una advertencia ya familiar: eps es una distancia euclídea, así que las features deben estar escaladas (03-05). Un eps=0.5 significa cosas radicalmente distintas en euros y en desviaciones estándar.

Cómo elegir eps: el gráfico k-distance

min_samples se fija con la regla práctica; para eps existe un método gráfico estándar, el k-distance plot:

  1. Para cada punto, calcula la distancia a su k-ésimo vecino más cercano, con k = min_samples (herramienta conocida: NearestNeighbors, la maquinaria de 04-05).
  2. Ordena esas distancias de menor a mayor y dibújalas.
  3. La curva sube suave mientras recorre puntos de zonas densas (su k-ésimo vecino está cerca) y se dispara al llegar a los puntos aislados. El codo de la curva es el candidato a eps: la frontera entre "distancia normal a tus vecinos" y "estás solo".
import numpy as np
import matplotlib.pyplot as plt
from sklearn.neighbors import NearestNeighbors

k = 5                                   # = min_samples previsto
nn = NearestNeighbors(n_neighbors=k).fit(X_esc)
dist, _ = nn.kneighbors(X_esc)          # distancias a los k vecinos más próximos
d_k = np.sort(dist[:, -1])              # distancia al k-ésimo, ordenada

plt.plot(d_k)
plt.xlabel("Puntos ordenados")
plt.ylabel(f"Distancia al vecino {k}")
plt.title("Gráfico k-distance: el codo sugiere eps")
plt.show()

Si la curva se mantiene por debajo de ~0,6 y se dispara a partir de ahí, eps=0.6 es un buen punto de partida — que afinaremos mirando cuántos clusters y cuánto ruido produce. Es el tercer "codo" del módulo (inercia en 05-01, dendrograma en 05-02): buscar el salto brusco de una curva es un patrón recurrente para separar señal de ruido.

Donde K-means falla: formas no esféricas (make_moons)

La demostración clásica usa make_moons, un dataset sintético con dos medialunas entrelazadas — dos grupos evidentes para el ojo humano, pero no esféricos:

from sklearn.datasets import make_moons
from sklearn.cluster import KMeans, DBSCAN
from sklearn.preprocessing import StandardScaler

X, _ = make_moons(n_samples=400, noise=0.07, random_state=42)
X_esc = StandardScaler().fit_transform(X)

km_lab = KMeans(n_clusters=2, random_state=42).fit_predict(X_esc)
db_lab = DBSCAN(eps=0.3, min_samples=5).fit_predict(X_esc)

fig, axes = plt.subplots(1, 2, figsize=(11, 4))
axes[0].scatter(X_esc[:, 0], X_esc[:, 1], c=km_lab, s=12)
axes[0].set_title("K-means: parte cada luna por la mitad")
axes[1].scatter(X_esc[:, 0], X_esc[:, 1], c=db_lab, s=12)
axes[1].set_title("DBSCAN: recupera las dos lunas")
plt.show()

El resultado es elocuente:

  • K-means traza una frontera recta entre sus dos centroides y parte cada luna por la mitad, mezclando trozos de ambas en cada cluster. No es un fallo de ajuste: es su geometría — todo punto va al centroide más cercano, y los centroides de dos lunas entrelazadas caen donde caen. Ningún n_init lo arregla.
  • DBSCAN recorre cada luna encadenando cores vecinos: la densidad es continua a lo largo de la medialuna y se corta entre una y otra. Recupera las dos formas exactas y marca como −1 los puntos sueltos del ruido.

Este ejemplo cierra la advertencia de 05-01 ("clusters esféricos"): cuando la estructura no es convexa, no se necesita un K-means mejor ajustado sino un algoritmo con otra definición de cluster.

Detección de anomalías en pedidos MercaFresh

Ahora el uso estrella para MercaFresh. Cambiamos de tabla: en lugar de clientes, pedidos individuales, con features como importe, número de artículos y hora del día. La pregunta ya no es "¿qué grupos hay?" sino "¿qué pedidos no encajan en ningún grupo?" — y ahí la etiqueta −1 de DBSCAN pasa de subproducto a protagonista.

import pandas as pd
from sklearn.preprocessing import StandardScaler
from sklearn.cluster import DBSCAN

features = ["importe", "num_articulos", "hora_pedido"]
X = pedidos[features]
X_esc = StandardScaler().fit_transform(X)

db = DBSCAN(eps=0.6, min_samples=6)          # eps del k-distance; min_samples ~ 2×3 dims
pedidos["cluster"] = db.fit_predict(X_esc)

anomalos = pedidos[pedidos["cluster"] == -1]
print(f"Pedidos anómalos: {len(anomalos)} de {len(pedidos)} "
      f"({len(anomalos)/len(pedidos):.1%})")
print(anomalos[features].head())

Qué encuentra cada etiqueta:

  • Los clusters (0, 1, 2...) son los patrones normales de compra: la compra semanal grande de mediodía, la compra rápida nocturna de pocos artículos, etc. Nadie los pidió: emergen de la densidad.
  • Los −1 son pedidos sin patrón: un importe de 900 € a las 4 de la madrugada con 3 artículos; 70 unidades del mismo producto en promoción. Candidatos a revisión manual, no culpables automáticos: la anomalía estadística es una alerta, no un veredicto.

Este detector es el tercero de una serie que el curso ha ido construyendo, y conviene verlos como complementarios:

Detector Base Detecta Limitación
Z-score (02-02) Distancia a la media en desviaciones, por variable Valores extremos en una variable Ciego a combinaciones: 30 € a las 4 a.m. es normal en cada variable por separado
Bayes / fraude (02-05) Probabilidades condicionadas a patrones conocidos Lo que se parece al fraude ya visto Necesita ejemplos previos del patrón fraudulento
DBSCAN (esta lección) Densidad multivariante, sin etiquetas Combinaciones raras nunca vistas No dice por qué es raro; sensible a eps

En un sistema real de MercaFresh convivirían los tres: el z-score como primer filtro barato por variable, DBSCAN cazando combinaciones inéditas y las señales bayesianas puntuando los patrones de fraude documentados. (El proyecto 09-04 monta un detector de fraude completo; aquí nos quedamos con el papel de DBSCAN.)

Comparativa: K-means vs. jerárquico vs. DBSCAN

Con los tres algoritmos de clustering del módulo sobre la mesa, la tabla de decisión:

Criterio K-means (05-01) Jerárquico (05-02) DBSCAN (05-04)
Forma de clusters Esférica/convexa Según enlace (Ward ≈ esférica; single, cadenas) Arbitraria (sigue la densidad)
¿K a priori? Sí No (se corta el dendrograma) No (emerge de eps/min_samples)
Manejo de ruido No: todo punto a un cluster No (aunque single lo insinúa) Sí: etiqueta −1 nativa
Parámetros clave K enlace + altura de corte eps + min_samples
Determinista No (mitigado con n_init) Sí Sí (salvo empates en borders)
Coste Bajo: escala a millones $O(n^2)$–$O(n^3)$: miles Medio: ~$O(n \log n)$ con índices espaciales
Requiere escalar Sí Sí Sí
Ideal para Segmentos compactos a gran escala Explorar estructura, jerarquías Formas irregulares, anomalías

La elección no es un concurso: en MercaFresh acabamos de usar K-means/jerárquico para segmentar clientes (grupos compactos e interpretables) y DBSCAN para vigilar pedidos (formas libres y ruido). Cada pregunta eligió su algoritmo.

Limitaciones de DBSCAN

  • Densidades variables. El talón de Aquiles: un único eps global no puede servir a la vez a un cluster muy denso y a otro difuso — el eps que preserva al segundo fusiona al primero con sus vecinos, y el eps del primero desintegra al segundo en ruido. (Existen variantes como HDBSCAN que jerarquizan la densidad; quedan fuera de este curso, pero conviene saber el nombre.)
  • Elección de eps delicada. El k-distance ayuda, pero décimas arriba o abajo cambian el número de clusters y el % de ruido. Analiza siempre la sensibilidad probando 2-3 valores alrededor del codo.
  • Alta dimensionalidad. DBSCAN es tan víctima de la maldición (05-03) como cualquier método de distancias: con muchas dimensiones las densidades se diluyen y "cerca" pierde sentido. PCA antes de DBSCAN es una combinación habitual.
  • Puntos border ambiguos. Un border alcanzable desde dos clusters se asigna al primero que lo visita: pequeñas variaciones de orden pueden mover puntos fronterizos (el ruido y los cores, en cambio, son estables).
  • Sin centroides. No hay un "pedido medio" por cluster que perfilar directamente; para interpretar un cluster de DBSCAN se calculan estadísticos descriptivos (02-01) de sus miembros.

Errores Comunes y Consejos

  • Ejecutar DBSCAN sin escalar. eps es una distancia: con features en unidades dispares, el radio solo "ve" la variable grande. StandardScaler primero, como en todo el módulo.
  • Tratar el −1 como un cluster más. Al perfilar resultados o calcular silhouette, el ruido no es un grupo: fíltralo (etiquetas != -1) antes de describir clusters, y repórtalo aparte como % de ruido.
  • Ajustar eps hasta que el ruido desaparezca. Si tu objetivo es detectar anomalías, ¡el ruido es el resultado! Un 1-5% de ruido suele ser razonable; 0% significa que agrandaste eps hasta tragarte las anomalías; 40% significa que lo empequeñeciste hasta desintegrar los clusters.
  • Copiar el eps de otro dataset. eps depende de la escala, la dimensión y la densidad de tus datos: recalcula el k-distance en cada problema, incluso entre versiones del mismo dataset.
  • Consejo: reporta siempre junto al clustering tres números — nº de clusters, % de ruido y tamaño del cluster mayor. Son el diagnóstico instantáneo de un eps mal elegido (1 cluster gigante = eps grande; ruido masivo = eps pequeño).

Ejercicios

Ejercicio 1. A mano, con eps=2 y min_samples=3, clasifica como core, border o noise los puntos 1D: [1, 2, 3, 10, 11, 30]. (Distancia = valor absoluto de la diferencia; recuerda que el propio punto cuenta como vecino.) ¿Cuántos clusters resultan y qué puntos quedan como ruido?

Ejercicio 2. Genera make_moons(n_samples=500, noise=0.1, random_state=0), escala, y ejecuta DBSCAN con min_samples=5 y tres valores de eps: 0,1, 0,3 y 1,0. Para cada uno imprime número de clusters (excluyendo −1) y % de ruido, y dibuja los tres resultados. Relaciona lo que ves con el codo del k-distance.

Ejercicio 3. Simula 300 pedidos normales de MercaFresh (importe ~ N(45, 15), num_articulos ~ N(18, 6)) y añade 5 pedidos anómalos (importe 400-600 con 2-4 artículos). Escala, elige eps con el k-distance y comprueba si DBSCAN marca los 5 como ruido. Compara con un z-score univariante (02-02) sobre importe: ¿los habría detectado también? ¿Y si el importe anómalo fuera 60 € con 2 artículos?

Soluciones

Ejercicio 1

Vecindarios de radio 2 (contándose a sí mismo): el 1 tiene {1,2,3} → 3 vecinos → core; el 2 tiene {1,2,3} → core; el 3 tiene {1,2,3} → core (el 10 queda a distancia 7). El 10 tiene {10,11} → 2 < 3 → no core, ¿border? No está a ≤2 de ningún core → noise; el 11, igual → noise; el 30 está solo → noise. Resultado: 1 cluster {1,2,3} y tres puntos de ruido {10,11,30}. Observa la sutileza: 10 y 11 están juntos, pero dos puntos no bastan para fundar un cluster con min_samples=3 — la densidad exige masa crítica, no solo cercanía. Con min_samples=2 habrían formado un segundo cluster.

Ejercicio 2

X, _ = make_moons(n_samples=500, noise=0.1, random_state=0)
X_esc = StandardScaler().fit_transform(X)

for eps in [0.1, 0.3, 1.0]:
    lab = DBSCAN(eps=eps, min_samples=5).fit_predict(X_esc)
    n_clu = len(set(lab)) - (1 if -1 in lab else 0)
    ruido = (lab == -1).mean()
    print(f"eps={eps} | clusters={n_clu} | ruido={ruido:.1%}")

Patrón esperado: eps=0.1 fragmenta las lunas en muchos mini-clusters con mucho ruido (radio menor que la distancia típica entre vecinos); eps=0.3 da 2 clusters con poco ruido — está cerca del codo del k-distance, que para estos datos ronda 0,2-0,3; eps=1.0 fusiona todo en 1 cluster sin ruido (el radio salta el hueco entre lunas). La secuencia fragmentación → estructura correcta → colapso es el comportamiento canónico de eps.

Ejercicio 3

rng = np.random.default_rng(42)
normales = pd.DataFrame({"importe": rng.normal(45, 15, 300).clip(5),
                         "num_articulos": rng.normal(18, 6, 300).clip(1)})
raros = pd.DataFrame({"importe": [420, 480, 510, 555, 600],
                      "num_articulos": [3, 2, 4, 2, 3]})
pedidos = pd.concat([normales, raros], ignore_index=True)

X_esc = StandardScaler().fit_transform(pedidos)
lab = DBSCAN(eps=0.5, min_samples=5).fit_predict(X_esc)   # eps según tu k-distance
print(pedidos[lab == -1])

Los 5 anómalos aparecen como −1 (importes a más de 20 desviaciones del grueso: aislidísimos en el espacio escalado); puede caer también en ruido algún pedido normal extremo — revisar el % total. El z-score sobre importe también los cazaría (z ≈ +25). La diferencia surge con el caso final: 60 € y 2 artículos tiene z-scores individuales modestos (z ≈ 1 en importe, z ≈ −2,7 en artículos, ninguno escandaloso), pero la combinación "importe normal con solo 2 artículos" (¿dos artículos de 30 €? en un supermercado es atípico) vive en una zona despoblada del plano y DBSCAN puede señalarla. Esa es exactamente la ventaja multivariante sobre el z-score univariante que anunciaba la tabla de la lección.

Conclusión

DBSCAN completa tu trío de algoritmos de clustering con una definición nueva: cluster = zona densa, ruido = lo que queda fuera. Sabes clasificar puntos en core, border y noise, elegir min_samples por regla práctica y eps con el gráfico k-distance, has visto en las medialunas de make_moons por qué la geometría de K-means tiene límites que ningún ajuste arregla, y has puesto a DBSCAN a hacer el trabajo que mejor sabe en MercaFresh: señalar pedidos que no encajan en ningún patrón, complementando al z-score univariante de 02-02 y al enfoque bayesiano de 02-05. La tabla comparativa de los tres algoritmos es, junto con la de los siete supervisados de 04-07, tu segunda carta de navegación del curso.

Queda una pieza para cerrar el módulo. Hemos segmentado, jerarquizado, comprimido y detectado — pero para comunicar todo esto, una imagen vale más que mil tablas de centroides, y el mapa 2D de PCA (05-03) era honesto pero lineal y a veces borroso. La última lección del módulo presenta las dos técnicas modernas de visualización que despliegan estructura no lineal en dos dimensiones: t-SNE y UMAP, con sus poderes y sus trampas de interpretación.

Curso de Machine Learning

Módulo 1: Introducción al Machine Learning

Módulo 2: Fundamentos de Estadística y Probabilidad

Módulo 3: Preprocesamiento de Datos

Módulo 4: Algoritmos de Machine Learning Supervisado

Módulo 5: Algoritmos de Machine Learning No Supervisado

Módulo 6: Evaluación y Validación de Modelos

Módulo 7: Técnicas Avanzadas y Optimización

Módulo 8: Implementación y Despliegue de Modelos

Módulo 9: Proyectos Prácticos

Módulo 10: Recursos Adicionales

© Copyright 2026. Todos los derechos reservados