Último proyecto, y volvemos a casa. Después de viviendas, imágenes, sentimientos y fraude, cerramos el círculo donde empezó todo: MercaFresh. El encargo es el clásico del aprendizaje no supervisado: sin etiqueta alguna, descubrir qué segmentos naturales de clientes existen, ponerles nombre de negocio, proponer una acción para cada uno y dejar el sistema listo para producción. Practicarás de punta a punta el módulo 5 completo — K-means, jerárquico, DBSCAN, PCA y t-SNE — sobre un dataset sintético pero realista de ~2.000 clientes generado en el propio código, construyendo las features RFM y los ratios que aprendiste en 03-06 "Ingeniería de características". Y como es el proyecto que cierra el módulo, terminaremos con una recapitulación de los cinco proyectos y el puente hacia el módulo 10.

Contenido

  1. Definición del problema: ¿qué es "éxito" sin etiquetas?
  2. Generación del dataset de clientes
  3. De pedidos a features: RFM y ratios
  4. EDA y escalado
  5. K-means: elegir k con codo y silhouette
  6. Contraste: clustering jerárquico y DBSCAN
  7. Visualización: PCA y t-SNE
  8. Perfilado: de centroides a nombres de negocio
  9. Acciones por segmento y cómo medirlas
  10. De los segmentos a producción
  11. Recapitulación del módulo 9

Definición del problema: ¿qué es "éxito" sin etiquetas?

En los proyectos 1–4 había una verdad contra la que medirse. Aquí no: nadie sabe cuántos segmentos "de verdad" hay. El éxito se define distinto, como aprendiste en el módulo 5:

  • Validez interna: clusters compactos y separados (silhouette, 05-01).
  • Estabilidad: los segmentos no deben cambiar radicalmente con otra semilla u otra muestra.
  • Accionabilidad: cada segmento debe ser describible en una frase y asociable a una acción de marketing distinta. Un clustering estadísticamente perfecto pero inexplicable a negocio es un fracaso.

Objetivo concreto: entre 3 y 8 segmentos (menos no discrimina, más no se gestiona), perfilados y con plan de acción.

Generación del dataset de clientes

Simulamos el histórico de pedidos de ~2.000 clientes con arquetipos de comportamiento mezclados y ruido — así luego podremos juzgar si los algoritmos los redescubren. En la realidad, esta tabla saldría de una consulta SQL al histórico de pedidos:

import numpy as np
import pandas as pd

rng = np.random.default_rng(42)
n = 2000

# Arquetipos latentes (proporciones): VIP, habitual, ocasional, dormido, nuevo
arquetipo = rng.choice(5, size=n, p=[0.08, 0.32, 0.30, 0.20, 0.10])

# Parámetros por arquetipo: (recencia_media_dias, pedidos_año, ticket_medio)
params = {
    0: (7,  90, 85),    # VIP: compra semanal, cesta grande
    1: (12, 45, 45),    # habitual: quincenal, cesta media
    2: (35, 12, 30),    # ocasional
    3: (160, 4, 38),    # dormido: hace meses que no compra
    4: (10, 3, 25),     # nuevo: reciente pero poca historia
}

filas = []
for a in arquetipo:
    r_mu, f_mu, m_mu = params[a]
    recencia = max(1, rng.gamma(2, r_mu / 2))
    pedidos = max(1, rng.poisson(f_mu))
    ticket = max(8, rng.normal(m_mu, m_mu * 0.3))
    frescos = np.clip(rng.normal([0.55, 0.45, 0.30, 0.35, 0.40][a], 0.12), 0, 1)
    antiguedad = (rng.uniform(15, 90) if a == 4
                  else rng.uniform(180, 1400))
    filas.append([recencia, pedidos, ticket, frescos, antiguedad])

clientes = pd.DataFrame(
    filas, columns=["recencia_dias", "pedidos_12m", "ticket_medio_eur",
                    "pct_frescos", "antiguedad_dias"]
)
print(clientes.describe().round(1))

Nota honesta: conocemos los arquetipos porque los hemos sembrado, pero no se los daremos a ningún algoritmo — están aquí solo para que tú, al final, puedas comparar lo descubierto con lo sembrado. El clustering trabajará a ciegas, como en la realidad.

De pedidos a features: RFM y ratios

Sobre las columnas base construimos el vector de segmentación, reeditando el RFM de 03-06:

X = pd.DataFrame({
    "recencia": clientes["recencia_dias"],               # R
    "frecuencia": clientes["pedidos_12m"],               # F
    "monetario": clientes["pedidos_12m"] * clientes["ticket_medio_eur"],  # M anual
    "ticket_medio": clientes["ticket_medio_eur"],
    "pct_frescos": clientes["pct_frescos"],
    "pedidos_por_mes_activo": clientes["pedidos_12m"]
                              / (clientes["antiguedad_dias"] / 30).clip(lower=1),
})

El último ratio es ingeniería con intención: un cliente con 3 pedidos y 20 años de antigüedad no es lo mismo que uno con 3 pedidos y 3 semanas — el ratio de intensidad los separa, distinguiendo nuevos prometedores de ocasionales crónicos.

EDA y escalado

import matplotlib.pyplot as plt

X.hist(bins=40, figsize=(12, 6)); plt.tight_layout(); plt.show()
print(X.corr().round(2))

Verás asimetrías fuertes en recencia, frecuencia y monetario (colas a la derecha, el patrón de 03-03 "Transformación de datos") — les aplicamos log para que los clusters no los dominen unos pocos extremos — y después estandarizamos, porque K-means mide distancias euclídeas y sin escalar mandaría la variable de mayor magnitud (monetario, en cientos de euros), como aprendiste en 03-05 y sufriste en 05-01:

from sklearn.preprocessing import StandardScaler

X_t = X.copy()
for col in ["recencia", "frecuencia", "monetario"]:
    X_t[col] = np.log1p(X_t[col])

esc = StandardScaler()
X_esc = esc.fit_transform(X_t)

K-means: elegir k con codo y silhouette

El protocolo de 05-01 "Clustering: K-means", completo:

from sklearn.cluster import KMeans
from sklearn.metrics import silhouette_score

inercias, siluetas = [], []
ks = range(2, 11)
for k in ks:
    km = KMeans(n_clusters=k, n_init=10, random_state=42).fit(X_esc)
    inercias.append(km.inertia_)
    siluetas.append(silhouette_score(X_esc, km.labels_))

fig, ejes = plt.subplots(1, 2, figsize=(12, 4))
ejes[0].plot(ks, inercias, "o-"); ejes[0].set_title("Codo (inercia)")
ejes[1].plot(ks, siluetas, "o-"); ejes[1].set_title("Silhouette medio")
for e in ejes: e.set_xlabel("k")
plt.show()

Con estos datos, el codo se dobla hacia k = 4–5 y la silhouette suele hacer máximo local en k = 5 (≈ 0,30–0,40; en datos reales de clientes, valores así son normales — los segmentos humanos no son esferas nítidas). Elegimos k = 5, y anotamos el argumento: coincide codo, silhouette razonable y un número gestionable para marketing. La decisión de k siempre es mitad métrica, mitad negocio.

km = KMeans(n_clusters=5, n_init=10, random_state=42).fit(X_esc)
clientes["segmento"] = km.labels_
print(clientes["segmento"].value_counts().sort_index())

Contraste: clustering jerárquico y DBSCAN

Un solo algoritmo es una sola opinión. El jerárquico de 05-02 aporta el dendrograma — la foto de cómo se agrupan los clientes a todas las escalas:

from scipy.cluster.hierarchy import linkage, dendrogram, fcluster

enlace = linkage(X_esc, method="ward")
plt.figure(figsize=(11, 4))
dendrogram(enlace, truncate_mode="lastp", p=30, no_labels=True)
plt.title("Dendrograma (Ward, truncado)"); plt.ylabel("Distancia")
plt.show()

jer = fcluster(enlace, t=5, criterion="maxclust")
print(pd.crosstab(clientes["segmento"], jer))

Si el dendrograma muestra 4–5 ramas gruesas y la tabla cruzada revela que los clusters jerárquicos coinciden en gran medida con los de K-means, tenemos convergencia de evidencia: la estructura es real, no un artefacto del algoritmo.

DBSCAN (05-04) responde otra pregunta: ¿hay clientes que no pertenecen a ningún grupo?

from sklearn.cluster import DBSCAN

db = DBSCAN(eps=0.9, min_samples=10).fit(X_esc)
print(pd.Series(db.labels_).value_counts().head())
print("Clientes-ruido:", (db.labels_ == -1).sum())

Los etiquetados como −1 son clientes-ruido: combinaciones raras (ticket enorme con frecuencia mínima, por ejemplo) que no encajan en ningún segmento. K-means los habría forzado dentro del centroide más cercano, contaminando su perfil. En la práctica conviene revisarlos aparte: algunos son errores de datos (03-01), otros son oportunidades singulares — cuentas de empresa, por ejemplo. Ajusta eps con criterio: demasiado pequeño y todo es ruido; demasiado grande y todo es un único cluster.

Visualización: PCA y t-SNE

Seis dimensiones no se dibujan; las proyectamos con las dos lentes del módulo 5 — la lineal y global (PCA, 05-03) y la no lineal centrada en vecindarios (t-SNE, 05-05):

from sklearn.decomposition import PCA
from sklearn.manifold import TSNE

pca = PCA(n_components=2)
X_pca = pca.fit_transform(X_esc)
X_tsne = TSNE(n_components=2, perplexity=30,
              random_state=42).fit_transform(X_esc)

fig, ejes = plt.subplots(1, 2, figsize=(13, 5))
for eje, datos, titulo in [(ejes[0], X_pca, "PCA"), (ejes[1], X_tsne, "t-SNE")]:
    sc = eje.scatter(datos[:, 0], datos[:, 1], c=clientes["segmento"],
                     cmap="tab10", s=8, alpha=0.6)
    eje.set_title(titulo)
plt.colorbar(sc, label="Segmento"); plt.show()
print("Varianza explicada PCA:", pca.explained_variance_ratio_.round(2))

Recuerda las reglas de lectura de 05-05: en PCA las distancias globales significan algo (y pca.components_ te dice qué combinación de features es cada eje); en t-SNE solo la pertenencia a grupos es interpretable — ni tamaños ni distancias entre islas. Si los colores de K-means aparecen como regiones coherentes en ambas proyecciones, el clustering gana otra confirmación visual.

Perfilado: de centroides a nombres de negocio

La fase que convierte el análisis en producto — tabla de medias por segmento en unidades originales, no escaladas:

perfil = clientes.groupby("segmento").agg(
    n=("segmento", "size"),
    recencia=("recencia_dias", "mean"),
    pedidos=("pedidos_12m", "mean"),
    ticket=("ticket_medio_eur", "mean"),
    pct_frescos=("pct_frescos", "mean"),
    antiguedad=("antiguedad_dias", "mean"),
).round(1)
perfil["gasto_anual"] = (perfil["pedidos"] * perfil["ticket"]).round(0)
print(perfil.to_string())

Con estas cifras se bautiza cada segmento. Un resultado típico (tus números variarán; los nombres los pones mirando tu tabla, no esta):

Segmento n Recencia Pedidos/año Ticket Gasto anual Nombre de negocio
0 ≈ 160 8 días 88 84 € ≈ 7.400 € VIP
1 ≈ 640 13 días 44 46 € ≈ 2.000 € Habituales
2 ≈ 600 36 días 12 31 € ≈ 370 € Ocasionales
3 ≈ 400 165 días 4 37 € ≈ 150 € Dormidos
4 ≈ 200 11 días 3 26 € ≈ 80 € Nuevos

Y el momento de la verdad de nuestro experimento: cruza clientes["segmento"] con los arquetipos sembrados (pd.crosstab(clientes["segmento"], arquetipo)). Verás una correspondencia fuerte pero imperfecta — fronteras borrosas entre habituales y ocasionales, nuevos repartidos —, que es exactamente lo que pasa con segmentos reales: el clustering recupera la estructura, no la partición exacta.

Acciones por segmento y cómo medirlas

Un segmento sin acción es un gráfico bonito. La tabla que marketing espera:

Segmento Acción propuesta Métrica de éxito
VIP Programa fidelidad premium, envío gratis Retención a 12 meses
Habituales Suscripción de cesta recurrente Conversión a suscripción
Ocasionales Campañas en sus categorías más compradas Frecuencia de pedido
Dormidos Email de reactivación con cupón Tasa de reactivación a 30 días
Nuevos Onboarding: 3 primeras compras guiadas Supervivencia al 4º pedido

¿Y cómo sabremos si la acción funciona? Con el test A/B de 02-04 "Inferencia estadística": dentro de cada segmento, un grupo aleatorio recibe la acción y otro no, y se contrasta la diferencia en la métrica de éxito con su test de hipótesis. Sin grupo de control, cualquier mejora es atribuible a la estacionalidad o al azar — el clustering propone, la inferencia dispone.

De los segmentos a producción

Cerramos con el oficio de 08-02 "Implementación de modelos en producción". El pipeline transformaciones + escalado + K-means se serializa y se ejecuta como scoring batch: cada noche (o cada semana — los segmentos se mueven despacio), se recalculan las features de cada cliente y se le asigna segmento con predict, escribiendo el resultado donde el CRM lo lea:

import joblib
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import FunctionTransformer

def log_rfm(X):
    X = X.copy()
    for col in ["recencia", "frecuencia", "monetario"]:
        X[col] = np.log1p(X[col])
    return X

pipeline_seg = Pipeline([
    ("log", FunctionTransformer(log_rfm)),
    ("esc", StandardScaler()),
    ("km", KMeans(n_clusters=5, n_init=10, random_state=42)),
]).fit(X)

joblib.dump(pipeline_seg, "segmentador_mercafresh.joblib")

# En el job nocturno:
modelo = joblib.load("segmentador_mercafresh.joblib")
clientes["segmento"] = modelo.predict(X)

Dos vigilancias del monitoreo de 08-03 aplican también aquí: el tamaño de cada segmento en el tiempo (si los dormidos crecen un 30 %, es una alarma de negocio, no del modelo) y la deriva de los centroides cuando se reentrena — si los perfiles cambian de significado, los nombres y las campañas deben revisarse con ellos. Y el recordatorio de 08-04: segmentar para tratar distinto a los clientes exige revisar que ningún segmento sea un proxy de un colectivo protegido.

Errores Comunes y Consejos

  • Olvidar el escalado (o el log). Sin estandarizar, monetario dicta los clusters él solo; sin log, los cuatro clientes extremos se convierten en "segmentos" de cuatro personas. Es el error número uno en segmentación.
  • Elegir k solo por la métrica. Una silhouette de 0,41 con k = 2 puede ser peor negocio que 0,35 con k = 5: dos segmentos no dan juego comercial. Métrica y accionabilidad deciden juntas.
  • Perfilar en unidades escaladas. Un centroide de "−0,3 recencias estándar" no se lo puedes contar a marketing. El perfilado siempre en unidades originales.
  • Tratar los segmentos como verdades eternas. Son una foto: los clientes migran entre segmentos y esa migración (¿cuántos habituales caen a dormidos cada mes?) es a menudo más valiosa que la foto misma.
  • Lanzar acciones sin grupo de control. Sin A/B no hay atribución; habrás gastado el presupuesto en confirmar tus sesgos.

Retos para ampliar

  1. Más features, mejores segmentos. Añade al vector variables de comportamiento: porcentaje de compras con cupón, diversidad de categorías, tasa de devoluciones, canal (app/web). Pista: repite codo + silhouette desde cero — con más dimensiones el k óptimo puede cambiar — y vigila con PCA cuánta varianza aportan de verdad las nuevas columnas.
  2. Clustering de productos. Gira la matriz: en vez de clientes descritos por compras, productos descritos por quién los compra (o por co-aparición en cestas). Pista: la matriz producto × cliente es enorme y dispersa — reduce primero con el PCA de 05-03 (o TruncatedSVD, su variante para matrices dispersas) y agrupa después; los grupos resultantes alimentan recomendaciones del tipo "quienes compran esto...".
  3. Panel de seguimiento. Construye con matplotlib un informe mensual automático: tamaño de cada segmento en el tiempo, gasto medio por segmento y matriz de migraciones mes a mes. Pista: guarda cada scoring batch con su fecha; la migración es un pd.crosstab(segmento_mes_anterior, segmento_actual) — y conviértelo en el monitoreo permanente que pedía 08-03.

Recapitulación del módulo 9

Cinco proyectos, cinco problemas distintos, un mismo método. La tabla que resume lo que has practicado:

Proyecto Tipo de problema Dataset Técnicas protagonistas Lección central
1. Viviendas Regresión California Housing Pipeline, Ridge/Lasso, Random Forest, HistGradientBoosting, CV, residuos El EDA anticipa qué modelo ganará
2. Imágenes Clasificación multiclase digits + Fashion-MNIST SVM, PCA, MLP vs. CNN, dropout, early stopping Empieza simple; que los datos justifiquen cada capa
3. Sentimientos Clasificación de texto Reseñas (ficticio) Limpieza, TF-IDF, n-gramas, MultinomialNB, interpretación de pesos La representación importa tanto como el algoritmo
4. Fraude Clasificación desbalanceada Sintético (make_classification) class_weight, remuestreo, curva PR, AUC-PR, umbral por costes Separa el modelo (probabilidades) de la decisión (euros)
5. Segmentación No supervisado MercaFresh sintético RFM, K-means, jerárquico, DBSCAN, PCA/t-SNE, perfilado, A/B Sin etiquetas, el éxito es estabilidad + accionabilidad

Y transversal a los cinco: definir el problema y la métrica antes de tocar datos, apartar el test al principio, encapsular en Pipeline, comparar contra un baseline con validación cruzada honesta, analizar los errores en lugar de solo contarlos, y traducir el resultado al idioma de negocio.

Conclusión

El círculo está cerrado: MercaFresh abrió el curso enseñándote cada pieza por separado y lo cierra viéndote ensamblarlas todas sin ayuda — has descubierto sus segmentos de clientes desde cero, los has validado con tres algoritmos y dos proyecciones, les has puesto nombre, acción y grupo de control, y los has dejado sirviéndose en batch con su monitoreo previsto. Ya no eres la persona que empezó el módulo 1: tienes un método completo y cinco proyectos que lo demuestran, listos para convertirse en el embrión de tu portafolio. Lo que queda no es aprender más lecciones, sino seguir aprendiendo por tu cuenta: en el módulo 10 te dejamos el mapa — libros, cursos, comunidades y herramientas — para que el final de este curso sea solo el principio de tu camino en el machine learning.

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