Últim projecte, i tornem a casa. Després d'habitatges, imatges, sentiments i frau, tanquem el cercle allà on va començar tot: MercaFresh. L'encàrrec és el clàssic de l'aprenentatge no supervisat: sense cap etiqueta, descobrir quins segments naturals de clients existeixen, posar-los nom de negoci, proposar una acció per a cadascun i deixar el sistema a punt per a producció. Practicaràs de cap a cap el mòdul 5 complet — K-means, jeràrquic, DBSCAN, PCA i t-SNE — sobre un dataset sintètic però realista de ~2.000 clients generat al mateix codi, construint les features RFM i els ratios que vas aprendre a 03-06 "Enginyeria de característiques". I com que és el projecte que tanca el mòdul, acabarem amb una recapitulació dels cinc projectes i el pont cap al mòdul 10.

Contingut

  1. Definició del problema: què és "èxit" sense etiquetes?
  2. Generació del dataset de clients
  3. De comandes a features: RFM i ratios
  4. EDA i escalat
  5. K-means: triar k amb colze i silhouette
  6. Contrast: clustering jeràrquic i DBSCAN
  7. Visualització: PCA i t-SNE
  8. Perfilat: de centroides a noms de negoci
  9. Accions per segment i com mesurar-les
  10. Dels segments a producció
  11. Recapitulació del mòdul 9

Definició del problema: què és "èxit" sense etiquetes?

Als projectes 1–4 hi havia una veritat contra la qual mesurar-se. Aquí no: ningú no sap quants segments hi ha "de debò". L'èxit es defineix diferent, com vas aprendre al mòdul 5:

  • Validesa interna: clústers compactes i separats (silhouette, 05-01).
  • Estabilitat: els segments no han de canviar radicalment amb una altra llavor o una altra mostra.
  • Accionabilitat: cada segment ha de poder descriure's en una frase i associar-se a una acció de màrqueting diferent. Un clustering estadísticament perfecte però inexplicable a negoci és un fracàs.

Objectiu concret: entre 3 i 8 segments (menys no discrimina, més no es gestiona), perfilats i amb pla d'acció.

Generació del dataset de clients

Simulem l'històric de comandes de ~2.000 clients amb arquetips de comportament barrejats i soroll — així després podrem jutjar si els algorismes els redescobreixen. A la realitat, aquesta taula sortiria d'una consulta SQL a l'històric de comandes:

import numpy as np
import pandas as pd

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

# Arquetips latents (proporcions): VIP, habitual, ocasional, adormit, nou
arquetip = rng.choice(5, size=n, p=[0.08, 0.32, 0.30, 0.20, 0.10])

# Parametres per arquetip: (recencia_mitjana_dies, comandes_any, tiquet_mitja)
params = {
    0: (7,  90, 85),    # VIP: compra setmanal, cistell gran
    1: (12, 45, 45),    # habitual: quinzenal, cistell mitja
    2: (35, 12, 30),    # ocasional
    3: (160, 4, 38),    # adormit: fa mesos que no compra
    4: (10, 3, 25),     # nou: recent pero amb poca historia
}

files = []
for a in arquetip:
    r_mu, f_mu, m_mu = params[a]
    recencia = max(1, rng.gamma(2, r_mu / 2))
    comandes = max(1, rng.poisson(f_mu))
    tiquet = 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)
    antiguitat = (rng.uniform(15, 90) if a == 4
                  else rng.uniform(180, 1400))
    files.append([recencia, comandes, tiquet, frescos, antiguitat])

clients = pd.DataFrame(
    files, columns=["recencia_dies", "comandes_12m", "tiquet_mitja_eur",
                    "pct_frescos", "antiguitat_dies"]
)
print(clients.describe().round(1))

Nota honesta: coneixem els arquetips perquè els hem sembrat, però no els donarem a cap algorisme — són aquí només perquè tu, al final, puguis comparar allò descobert amb allò sembrat. El clustering treballarà a cegues, com a la realitat.

De comandes a features: RFM i ratios

Sobre les columnes base construïm el vector de segmentació, reeditant el RFM de 03-06:

X = pd.DataFrame({
    "recencia": clients["recencia_dies"],                # R
    "frequencia": clients["comandes_12m"],               # F
    "monetari": clients["comandes_12m"] * clients["tiquet_mitja_eur"],  # M anual
    "tiquet_mitja": clients["tiquet_mitja_eur"],
    "pct_frescos": clients["pct_frescos"],
    "comandes_per_mes_actiu": clients["comandes_12m"]
                              / (clients["antiguitat_dies"] / 30).clip(lower=1),
})

L'últim ratio és enginyeria amb intenció: un client amb 3 comandes i 20 anys d'antiguitat no és el mateix que un amb 3 comandes i 3 setmanes — el ratio d'intensitat els separa, distingint els nous prometedors dels ocasionals crònics.

EDA i escalat

import matplotlib.pyplot as plt

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

Hi veuràs asimetries fortes a recencia, frequencia i monetari (cues a la dreta, el patró de 03-03 "Transformació de dades") — els apliquem log perquè els clústers no els dominin uns pocs extrems — i després estandarditzem, perquè K-means mesura distàncies euclidianes i sense escalar manaria la variable de més magnitud (monetari, en centenars d'euros), com vas aprendre a 03-05 i vas patir a 05-01:

from sklearn.preprocessing import StandardScaler

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

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

K-means: triar k amb colze i silhouette

El protocol de 05-01 "Clustering: K-means", complet:

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

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

fig, eixos = plt.subplots(1, 2, figsize=(12, 4))
eixos[0].plot(ks, inercies, "o-"); eixos[0].set_title("Colze (inercia)")
eixos[1].plot(ks, siluetes, "o-"); eixos[1].set_title("Silhouette mitja")
for e in eixos: e.set_xlabel("k")
plt.show()

Amb aquestes dades, el colze es doblega cap a k = 4–5 i la silhouette sol fer un màxim local a k = 5 (≈ 0,30–0,40; en dades reals de clients, valors així són normals — els segments humans no són esferes nítides). Triem k = 5, i n'anotem l'argument: coincideix el colze, silhouette raonable i un nombre gestionable per a màrqueting. La decisió de k sempre és meitat mètrica, meitat negoci.

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

Contrast: clustering jeràrquic i DBSCAN

Un sol algorisme és una sola opinió. El jeràrquic de 05-02 aporta el dendrograma — la foto de com s'agrupen els clients a totes les escales:

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

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

jer = fcluster(enllac, t=5, criterion="maxclust")
print(pd.crosstab(clients["segment"], jer))

Si el dendrograma mostra 4–5 branques gruixudes i la taula creuada revela que els clústers jeràrquics coincideixen en gran mesura amb els de K-means, tenim convergència d'evidència: l'estructura és real, no un artefacte de l'algorisme.

DBSCAN (05-04) respon una altra pregunta: hi ha clients que no pertanyen a cap grup?

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("Clients-soroll:", (db.labels_ == -1).sum())

Els etiquetats com a −1 són clients-soroll: combinacions rares (tiquet enorme amb freqüència mínima, per exemple) que no encaixen en cap segment. K-means els hauria forçat dins del centroide més proper, contaminant-ne el perfil. A la pràctica convé revisar-los a part: alguns són errors de dades (03-01), d'altres són oportunitats singulars — comptes d'empresa, per exemple. Ajusta eps amb criteri: massa petit i tot és soroll; massa gran i tot és un únic clúster.

Visualització: PCA i t-SNE

Sis dimensions no es dibuixen; les projectem amb les dues lents del mòdul 5 — la lineal i global (PCA, 05-03) i la no lineal centrada en veïnatges (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, eixos = plt.subplots(1, 2, figsize=(13, 5))
for eix, dades, titol in [(eixos[0], X_pca, "PCA"), (eixos[1], X_tsne, "t-SNE")]:
    sc = eix.scatter(dades[:, 0], dades[:, 1], c=clients["segment"],
                     cmap="tab10", s=8, alpha=0.6)
    eix.set_title(titol)
plt.colorbar(sc, label="Segment"); plt.show()
print("Variancia explicada PCA:", pca.explained_variance_ratio_.round(2))

Recorda les regles de lectura de 05-05: en PCA les distàncies globals signifiquen alguna cosa (i pca.components_ et diu quina combinació de features és cada eix); en t-SNE només la pertinença als grups és interpretable — ni mides ni distàncies entre illes. Si els colors de K-means apareixen com a regions coherents en totes dues projeccions, el clustering guanya una altra confirmació visual.

Perfilat: de centroides a noms de negoci

La fase que converteix l'anàlisi en producte — taula de mitjanes per segment en unitats originals, no escalades:

perfil = clients.groupby("segment").agg(
    n=("segment", "size"),
    recencia=("recencia_dies", "mean"),
    comandes=("comandes_12m", "mean"),
    tiquet=("tiquet_mitja_eur", "mean"),
    pct_frescos=("pct_frescos", "mean"),
    antiguitat=("antiguitat_dies", "mean"),
).round(1)
perfil["despesa_anual"] = (perfil["comandes"] * perfil["tiquet"]).round(0)
print(perfil.to_string())

Amb aquestes xifres es bateja cada segment. Un resultat típic (els teus números variaran; els noms els poses mirant la teva taula, no aquesta):

Segment n Recència Comandes/any Tiquet Despesa anual Nom de negoci
0 ≈ 160 8 dies 88 84 € ≈ 7.400 € VIP
1 ≈ 640 13 dies 44 46 € ≈ 2.000 € Habituals
2 ≈ 600 36 dies 12 31 € ≈ 370 € Ocasionals
3 ≈ 400 165 dies 4 37 € ≈ 150 € Adormits
4 ≈ 200 11 dies 3 26 € ≈ 80 € Nous

I el moment de la veritat del nostre experiment: creua clients["segment"] amb els arquetips sembrats (pd.crosstab(clients["segment"], arquetip)). Hi veuràs una correspondència forta però imperfecta — fronteres borroses entre habituals i ocasionals, nous repartits —, que és exactament el que passa amb segments reals: el clustering recupera l'estructura, no la partició exacta.

Accions per segment i com mesurar-les

Un segment sense acció és un gràfic bonic. La taula que màrqueting espera:

Segment Acció proposada Mètrica d'èxit
VIP Programa de fidelitat prèmium, enviament gratis Retenció a 12 mesos
Habituals Subscripció de cistell recurrent Conversió a subscripció
Ocasionals Campanyes en les seves categories més comprades Freqüència de comanda
Adormits Email de reactivació amb cupó Taxa de reactivació a 30 dies
Nous Onboarding: 3 primeres compres guiades Supervivència a la 4a comanda

I com sabrem si l'acció funciona? Amb el test A/B de 02-04 "Inferència estadística": dins de cada segment, un grup aleatori rep l'acció i un altre no, i es contrasta la diferència en la mètrica d'èxit amb el seu test d'hipòtesi. Sense grup de control, qualsevol millora és atribuïble a l'estacionalitat o a l'atzar — el clustering proposa, la inferència disposa.

Dels segments a producció

Tanquem amb l'ofici de 08-02 "Implementació de models en producció". El pipeline transformacions + escalat + K-means se serialitza i s'executa com a scoring batch: cada nit (o cada setmana — els segments es mouen a poc a poc), es recalculen les features de cada client i se li assigna segment amb predict, escrivint el resultat allà on el CRM el llegeixi:

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

def log_rfm(X):
    X = X.copy()
    for col in ["recencia", "frequencia", "monetari"]:
        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 nocturn:
model = joblib.load("segmentador_mercafresh.joblib")
clients["segment"] = model.predict(X)

Dues vigilàncies del monitoratge de 08-03 també apliquen aquí: la mida de cada segment al llarg del temps (si els adormits creixen un 30 %, és una alarma de negoci, no del model) i la deriva dels centroides quan es reentrena — si els perfils canvien de significat, els noms i les campanyes s'han de revisar amb ells. I el recordatori de 08-04: segmentar per tractar diferent els clients exigeix revisar que cap segment no sigui un proxy d'un col·lectiu protegit.

Errors Comuns i Consells

  • Oblidar l'escalat (o el log). Sense estandarditzar, monetari dicta els clústers tot sol; sense log, els quatre clients extrems es converteixen en "segments" de quatre persones. És l'error número u en segmentació.
  • Triar k només per la mètrica. Una silhouette de 0,41 amb k = 2 pot ser pitjor negoci que 0,35 amb k = 5: dos segments no donen joc comercial. Mètrica i accionabilitat decideixen juntes.
  • Perfilar en unitats escalades. Un centroide de "−0,3 recències estàndard" no el pots explicar a màrqueting. El perfilat, sempre en unitats originals.
  • Tractar els segments com a veritats eternes. Són una foto: els clients migren entre segments i aquesta migració (quants habituals cauen a adormits cada mes?) sovint és més valuosa que la foto mateixa.
  • Llançar accions sense grup de control. Sense A/B no hi ha atribució; hauràs gastat el pressupost a confirmar els teus biaixos.

Reptes per ampliar

  1. Més features, millors segments. Afegeix al vector variables de comportament: percentatge de compres amb cupó, diversitat de categories, taxa de devolucions, canal (app/web). Pista: repeteix colze + silhouette des de zero — amb més dimensions el k òptim pot canviar — i vigila amb PCA quanta variància aporten de debò les columnes noves.
  2. Clustering de productes. Gira la matriu: en lloc de clients descrits per compres, productes descrits per qui els compra (o per coaparició en cistells). Pista: la matriu producte × client és enorme i dispersa — redueix primer amb el PCA de 05-03 (o TruncatedSVD, la seva variant per a matrius disperses) i agrupa després; els grups resultants alimenten recomanacions del tipus "qui compra això...".
  3. Panell de seguiment. Construeix amb matplotlib un informe mensual automàtic: mida de cada segment al llarg del temps, despesa mitjana per segment i matriu de migracions mes a mes. Pista: desa cada scoring batch amb la seva data; la migració és un pd.crosstab(segment_mes_anterior, segment_actual) — i converteix-lo en el monitoratge permanent que demanava 08-03.

Recapitulació del mòdul 9

Cinc projectes, cinc problemes diferents, un mateix mètode. La taula que resumeix el que has practicat:

Projecte Tipus de problema Dataset Tècniques protagonistes Lliçó central
1. Habitatges Regressió California Housing Pipeline, Ridge/Lasso, Random Forest, HistGradientBoosting, CV, residus L'EDA anticipa quin model guanyarà
2. Imatges Classificació multiclasse digits + Fashion-MNIST SVM, PCA, MLP vs. CNN, dropout, early stopping Comença simple; que les dades justifiquin cada capa
3. Sentiments Classificació de text Ressenyes (fictici) Neteja, TF-IDF, n-grames, MultinomialNB, interpretació de pesos La representació importa tant com l'algorisme
4. Frau Classificació desequilibrada Sintètic (make_classification) class_weight, remostreig, corba PR, AUC-PR, llindar per costos Separa el model (probabilitats) de la decisió (euros)
5. Segmentació No supervisat MercaFresh sintètic RFM, K-means, jeràrquic, DBSCAN, PCA/t-SNE, perfilat, A/B Sense etiquetes, l'èxit és estabilitat + accionabilitat

I transversal als cinc: definir el problema i la mètrica abans de tocar dades, apartar el test al principi, encapsular en Pipeline, comparar contra un baseline amb validació creuada honesta, analitzar els errors en lloc de només comptar-los, i traduir el resultat a l'idioma de negoci.

Conclusió

El cercle està tancat: MercaFresh va obrir el curs ensenyant-te cada peça per separat i el tanca veient-te ensamblar-les totes sense ajuda — n'has descobert els segments de clients des de zero, els has validat amb tres algorismes i dues projeccions, els has posat nom, acció i grup de control, i els has deixat servint-se en batch amb el seu monitoratge previst. Ja no ets la persona que va començar el mòdul 1: tens un mètode complet i cinc projectes que ho demostren, a punt per convertir-se en l'embrió del teu portafolis. El que queda no és aprendre més lliçons, sinó continuar aprenent pel teu compte: al mòdul 10 et deixem el mapa — llibres, cursos, comunitats i eines — perquè el final d'aquest curs sigui només el principi del teu camí en el machine learning.

Curs de Machine Learning

Mòdul 1: Introducció al Machine Learning

Mòdul 2: Fonaments d'Estadística i Probabilitat

Mòdul 3: Preprocessament de Dades

Mòdul 4: Algorismes de Machine Learning Supervisat

Mòdul 5: Algorismes de Machine Learning No Supervisat

Mòdul 6: Avaluació i Validació de Models

Mòdul 7: Tècniques Avançades i Optimització

Mòdul 8: Implementació i Desplegament de Models

Mòdul 9: Projectes Pràctics

Mòdul 10: Recursos Addicionals

© Copyright 2026. Tots els drets reservats