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
- Clustering por densidad: la idea
- Puntos core, border y noise
- Los parámetros eps y min_samples
- Cómo elegir eps: el gráfico k-distance
- Donde K-means falla: formas no esféricas (
make_moons) - Detección de anomalías en pedidos MercaFresh
- Comparativa: K-means vs. jerárquico vs. DBSCAN
- 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:
- 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). - Ordena esas distancias de menor a mayor y dibújalas.
- 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_initlo 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
epsglobal 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.
epses una distancia: con features en unidades dispares, el radio solo "ve" la variable grande.StandardScalerprimero, 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
- ¿Qué es el Machine Learning?
- Historia y evolución del Machine Learning
- Tipos de Machine Learning
- Aplicaciones del Machine Learning
- El flujo de trabajo de un proyecto de Machine Learning
Módulo 2: Fundamentos de Estadística y Probabilidad
- Conceptos básicos de estadística
- Distribuciones de probabilidad
- Correlación y covarianza
- Inferencia estadística
- Teorema de Bayes
Módulo 3: Preprocesamiento de Datos
- Limpieza de datos
- Manejo de datos faltantes
- Transformación de datos
- Codificación de variables categóricas
- Normalización y estandarización
- Ingeniería de características
Módulo 4: Algoritmos de Machine Learning Supervisado
- Regresión lineal
- Regresión logística
- Árboles de decisión
- Máquinas de soporte vectorial (SVM)
- K-Vecinos más cercanos (K-NN)
- Naive Bayes
- Redes neuronales
Módulo 5: Algoritmos de Machine Learning No Supervisado
- Clustering: K-means
- Clustering jerárquico
- Análisis de componentes principales (PCA)
- Análisis de agrupamiento DBSCAN
- Visualización de datos con t-SNE y UMAP
Módulo 6: Evaluación y Validación de Modelos
- División de datos: entrenamiento, validación y prueba
- Métricas de evaluación
- Validación cruzada
- Curva ROC y AUC
- Overfitting y underfitting
Módulo 7: Técnicas Avanzadas y Optimización
- Regularización: Ridge, Lasso y Elastic Net
- Ensemble Learning
- Gradient Boosting
- Redes neuronales profundas (Deep Learning)
- Optimización de hiperparámetros
Módulo 8: Implementación y Despliegue de Modelos
- Frameworks y bibliotecas populares
- Implementación de modelos en producción
- Mantenimiento y monitoreo de modelos
- Consideraciones éticas y de privacidad
Módulo 9: Proyectos Prácticos
- Proyecto 1: Predicción de precios de viviendas
- Proyecto 2: Clasificación de imágenes
- Proyecto 3: Análisis de sentimientos en redes sociales
- Proyecto 4: Detección de fraudes
- Proyecto 5: Segmentación de clientes
