El mòdul anterior va acabar amb una observació incòmoda: els set algorismes supervisats exigien una columna y, i MercaFresh no té cap columna que digui a quin segment pertany cada client — els segments són precisament allò que vol descobrir. Aquesta lliçó obre l'aprenentatge no supervisat amb el seu algorisme emblema: K-means. Aprendràs què és el clustering i en què es diferencia radicalment de classificar, com funciona K-means pas a pas (fins al punt de poder executar-lo a mà), com decidir quants grups buscar amb la inèrcia i el mètode del colze, com mesurar la qualitat amb el silhouette score, i com aplicar-lo amb scikit-learn a les features RFM de MercaFresh per obtenir segments de clients amb lectura de negoci. És la lliçó que converteix "volem segmentar els nostres clients" en un procediment concret.

Contingut

  1. Clustering: agrupar sense etiquetes
  2. L'algorisme K-means pas a pas
  3. Exemple numèric a mà
  4. Quants clústers? Inèrcia i mètode del colze
  5. Silhouette score: mesurar la qualitat de la partició
  6. k-means++ i n_init: el problema de la inicialització
  7. Segmentant clients MercaFresh amb scikit-learn
  8. Interpretació: perfils per centroide
  9. Limitacions de K-means

Clustering: agrupar sense etiquetes

A 01-03 vam distingir supervisat de no supervisat; ara la diferència esdevé operativa. Compara els dos problemes de MercaFresh:

Classificació (04-02) Clustering (aquesta lliçó)
Pregunta Aquest client abandonarà? Quins grups de clients existeixen?
Etiqueta y Sí: churn (0/1), observada a l'històric No existeix: ningú no sap els grups per endavant
"Resposta correcta" Sí: podem comparar predicció amb realitat No: no hi ha cap veritat contra la qual corregir
El model aprèn Una frontera features → etiqueta Estructura interna de les features
Avaluació Encerts sobre etiquetes conegudes Mesures internes de cohesió/separació

Dues conseqüències pràctiques del fet que no existeixi "resposta correcta":

  • No hi ha cap error a minimitzar contra etiquetes. L'algorisme optimitza un criteri intern: que els punts d'un mateix grup estiguin a prop entre si (cohesió) i lluny dels altres grups (separació).
  • La validació final és humana. Un clustering és útil si els grups que descobreix signifiquen alguna cosa per al negoci ("clients VIP", "adormits", "nous"). Dues particions diferents poden ser totes dues defensables; en classificació això no passa.

El clustering respon a preguntes de descobriment: segments de clients, famílies de productes que es compren junts, patrons de comportament que ningú no havia anomenat encara.

L'algorisme K-means pas a pas

K-means busca dividir $n$ punts en $K$ grups, cadascun representat pel seu centroide (el punt mitjà del grup). La idea completa cap en un cicle de dos passos que es repeteix fins que res no canvia:

  1. Inicialització: triar $K$ (el fixa l'analista!) i col·locar $K$ centroides inicials.
  2. Assignació: cada punt s'assigna al centroide més proper (distància euclidiana, la mateixa de 04-05).
  3. Actualització: cada centroide es recalcula com la mitjana dels punts que té assignats (d'aquí ve means).
  4. Convergència: si cap assignació no ha canviat (o els centroides amb prou feines es mouen), aturar-se; si no, tornar al pas 2.
flowchart TD
    A["Triar K i situar<br/>K centroides inicials"] --> B["ASSIGNACIO:<br/>cada punt va al<br/>centroide mes proper"]
    B --> C["ACTUALITZACIO:<br/>cada centroide = mitjana<br/>dels seus punts assignats"]
    C --> D{"Ha canviat<br/>alguna assignacio?"}
    D -- "Si" --> B
    D -- "No" --> E["Convergencia:<br/>clusters finals"]

Fixa't en el paral·lelisme amb el descens de gradient de 04-01: també aquí hi ha un bucle que millora una funció objectiu a cada volta. La funció que K-means redueix a cada iteració és la inèrcia:

$$\text{Inèrcia} = \sum_{i=1}^{n} \lVert x_i - c_{k(i)} \rVert^2$$

és a dir, la suma de distàncies al quadrat de cada punt al seu centroide. Cada assignació i cada actualització la redueixen o la deixen igual, de manera que l'algorisme sempre convergeix — encara que, com veurem, pot convergir a un òptim local.

I l'aclariment promès a 04-05: K-NN i K-means no comparteixen res llevat de la lletra K. K-NN és supervisat (vota entre veïns etiquetats) i la seva K són veïns consultats; K-means és no supervisat i la seva K són grups buscats. Només comparteixen l'ús de la distància euclidiana — i per això tots dos exigeixen escalar (03-05).

Exemple numèric a mà

Executem K-means sobre 6 clients de MercaFresh usant una sola feature, recencia_dies, amb $K=2$:

Clients (recencia en dies):  3, 5, 8, 40, 45, 52

Inicialització (deliberadament dolenta, per veure que l'algorisme se'n recupera): centroides $c_1 = 3$ i $c_2 = 8$.

Iteració 1 — assignació: cada punt va al centroide més proper.

Punt Dist. a $c_1=3$ Dist. a $c_2=8$ Clúster
3 0 5 1
5 2 3 1
8 5 0 2
40 37 32 2
45 42 37 2
52 49 44 2

Iteració 1 — actualització: $c_1 = \text{mitjana}(3,5) = 4$; $c_2 = \text{mitjana}(8,40,45,52) = 36{,}25$.

Iteració 2 — assignació: el punt 8 ara dista 4 de $c_1$ i 28,25 de $c_2$ → canvia al clúster 1. Els altres no canvien.

Iteració 2 — actualització: $c_1 = \text{mitjana}(3,5,8) = 5{,}33$; $c_2 = \text{mitjana}(40,45,52) = 45{,}67$.

Iteració 3 — assignació: ningú no canvia. Convergència.

Resultat: {3, 5, 8} (clients recents) i {40, 45, 52} (clients freds). Encara que la inicialització va posar tots dos centroides dins del mateix grup natural, el cicle assignar-actualitzar els va separar en dues iteracions. Amb dues features el procediment és idèntic, només que les distàncies són euclidianes en 2D — bon moment per repassar el càlcul de 04-05.

Quants clústers? Inèrcia i mètode del colze

K-means no descobreix K: l'exigeix com a entrada. Com triar-lo? La inèrcia per si sola no serveix d'àrbitre, perquè sempre baixa en augmentar K (amb $K=n$ cada punt és el seu propi centroide i inèrcia = 0). El que sí que és informatiu és com baixa:

  • Mentre K sigui menor que el nombre de grups reals, afegir un clúster parteix un grup genuí i la inèrcia cau molt.
  • Quan K supera el nombre de grups reals, els clústers nous només trossegen grups ja compactes i el guany és marginal.

El mètode del colze consisteix a dibuixar la inèrcia enfront de K i buscar el punt on la corba passa de caure en picat a caure suaument — el "colze".

import matplotlib.pyplot as plt
from sklearn.cluster import KMeans

inercies = []
rang_k = range(1, 11)
for k in rang_k:
    km = KMeans(n_clusters=k, random_state=42)
    km.fit(X_esc)                      # X_esc: features RFM ja escalades
    inercies.append(km.inertia_)       # atribut amb la inercia final

plt.plot(rang_k, inercies, "o-")
plt.xlabel("Nombre de clústers (K)")
plt.ylabel("Inèrcia")
plt.title("Mètode del colze — clients MercaFresh")
plt.show()

Què fa el codi: entrena un K-means per cada K d'1 a 10 i guarda inertia_, que scikit-learn calcula després de convergir. En les dades de clients de MercaFresh la corba sol doblegar-se al voltant de K=4: d'1 a 4 cada clúster nou redueix molt la inèrcia; a partir de 4, gairebé gens. El colze no sempre és nítid — és una guia, no un oracle; la decisió final combina el colze, el silhouette (apartat següent) i la interpretabilitat de negoci.

Silhouette score: mesurar la qualitat de la partició

Sense etiquetes no hi ha accuracy possible, però sí que podem mesurar si la partició és geomètricament bona. El silhouette score avalua, per a cada punt $i$:

  • $a(i)$: distància mitjana d'$i$ als punts del seu propi clúster (cohesió: com més petita, millor).
  • $b(i)$: distància mitjana d'$i$ als punts del clúster aliè més proper (separació: com més gran, millor).

$$s(i) = \frac{b(i) - a(i)}{\max(a(i), b(i))}$$

La interpretació és directa:

$s(i)$ Lectura
A prop de +1 El punt està molt més a prop dels seus que del clúster veí: ben assignat
A prop de 0 Està a la frontera entre dos clústers
Negatiu Està més a prop d'un altre clúster que del seu: probablement mal assignat

L'score global és la mitjana de tots els $s(i)$. Com a regla orientativa: per sobre de 0,5 l'estructura és clara; entre 0,25 i 0,5, raonable; per sota, feble o inexistent. En dades reals de clients (que no formen illes perfectes) valors de 0,3-0,5 són habituals i perfectament aprofitables.

from sklearn.metrics import silhouette_score

for k in range(2, 8):                          # silhouette exigeix K >= 2
    etiquetes = KMeans(n_clusters=k, random_state=42).fit_predict(X_esc)
    print(f"K={k} | silhouette = {silhouette_score(X_esc, etiquetes):.3f}")

Usat juntament amb el colze, el silhouette sol desempatar: si el colze dubta entre 3 i 4, guanya la K amb millor silhouette (o la que produeixi segments més accionables per a negoci).

k-means++ i n_init: el problema de la inicialització

K-means convergeix sempre, però no necessàriament al millor resultat: centroides inicials desafortunats (per exemple, dos dins el mateix grup natural i cap en un altre) poden deixar l'algorisme atrapat en un òptim local amb inèrcia pitjor. Dues defenses estàndard, totes dues activades per defecte a scikit-learn:

  • k-means++ (init="k-means++"): en lloc de centroides purament a l'atzar, tria el primer a l'atzar i cada següent amb probabilitat proporcional al quadrat de la distància als ja triats — és a dir, tendeix a repartir-los lluny els uns dels altres. Inicis repartits, convergència més ràpida i millor.
  • n_init: executa l'algorisme complet diverses vegades amb inicialitzacions diferents i es queda amb l'execució de menor inèrcia. En versions modernes de scikit-learn el valor per defecte és n_init="auto".

Amb totes dues activades, K-means és molt estable a la pràctica; tot i així, fixa random_state perquè els teus resultats siguin reproduïbles.

Segmentant clients MercaFresh amb scikit-learn

Toca l'exemple protagonista. Partim de la taula rfm construïda a 03-06 — recencia_dies, comandes_per_mes, despesa_mitjana_comanda, més la ratio_inactivitat — i de la lliçó 03-05 sabem que un algorisme de distàncies exigeix StandardScaler: sense ell, despesa_mitjana_comanda (desenes d'euros) aixafaria comandes_per_mes (unitats) per pur accident d'unitats.

import pandas as pd
from sklearn.preprocessing import StandardScaler
from sklearn.cluster import KMeans
from sklearn.metrics import silhouette_score

features = ["recencia_dies", "comandes_per_mes",
            "despesa_mitjana_comanda", "ratio_inactivitat"]
X = rfm[features]

# 1. Escalar: imprescindible per a un algorisme de distancies (03-05)
scaler = StandardScaler()
X_esc = scaler.fit_transform(X)

# 2. Entrenar amb la K triada per colze + silhouette
km = KMeans(n_clusters=4, init="k-means++", n_init="auto", random_state=42)
rfm["segment"] = km.fit_predict(X_esc)

print(silhouette_score(X_esc, rfm["segment"]).round(3))   # p. ex. 0.41
print(rfm["segment"].value_counts())

Detalls del codi, línia a línia:

  • fit_predict entrena i retorna directament l'etiqueta de clúster (0 a 3) de cada client. Compte: són etiquetes inventades per l'algorisme, sense significat propi ni ordre — el "segment 2" d'avui pot dir-se "0" si reentrenes amb una altra llavor. El significat l'hi posarem nosaltres en interpretar.
  • Nota una diferència clau amb el mòdul 4: aquí no hi ha train_test_split, perquè no hi ha etiquetes a predir sobre dades noves; usem tots els clients per descobrir-ne l'estructura. (L'avaluació de models supervisats, amb les seves particions, arriba al mòdul 6.)
  • Escalem amb fit_transform sobre tot el dataset per la mateixa raó: sense conjunt de prova, no hi ha fuita a evitar.

Interpretació: perfils per centroide

Un clustering sense interpretació és un número del 0 al 3 enganxat a cada client: inútil. L'eina clau són els centroides desescalats: com que cada centroide és la mitjana del seu grup, descriure'l en unitats originals retrata el client típic del segment.

# Centroides en unitats originals (desfem l'escalat)
centroides = pd.DataFrame(scaler.inverse_transform(km.cluster_centers_),
                          columns=features).round(2)
centroides["n_clients"] = rfm["segment"].value_counts().sort_index().values
print(centroides)

km.cluster_centers_ està en l'espai escalat (mitjanes 0, desviacions 1); inverse_transform el retorna a dies, comandes i euros. Un resultat típic a MercaFresh:

Segment recencia_dies comandes_per_mes despesa_mitjana_comanda ratio_inactivitat n_clients Lectura de negoci
0 9 4,1 78 € 0,05 180 VIP: compren molt, sovint i fa quatre dies
1 15 2,2 34 € 0,10 410 Habituals: la base sana del negoci
2 95 0,6 41 € 0,62 150 Adormits: valuosos però refredant-se — risc de churn
3 21 1,1 22 € 0,28 260 Ocasionals: compra esporàdica i tiquet baix

Aquesta taula és el lliurable real de la segmentació: l'equip de màrqueting pot dissenyar una acció per fila (programa de fidelitat per als VIP, campanya de reactivació per als adormits...). Observa el cercle que es tanca: el segment 2 coincideix amb el perfil que la regressió logística de 04-02 assenyalava com a churn probable — el no supervisat descobreix el grup que el supervisat predeia, sense haver vist ni una sola etiqueta.

Limitacions de K-means

K-means és ràpid, simple i escalable, però paga aquestes virtuts amb supòsits forts:

  • Clústers esfèrics i de mida similar. En assignar per distància al centroide, K-means dibuixa fronteres que reparteixen l'espai en regions convexes: dos grups allargats, niats o amb forma de lluna els partirà malament per molt evidents que siguin a la vista (ho veurem gràficament a 05-04, on DBSCAN els resol).
  • K fixada a priori. El colze i el silhouette ajuden, però la decisió és de l'analista; amb estructura ambigua, dues K diferents poden ser defensables. El clustering jeràrquic (05-02) esquiva parcialment el problema mostrant totes les escales d'agrupament alhora.
  • Sensibilitat a outliers. El centroide és una mitjana, i ja vam veure a 02-01 què fa un valor extrem amb una mitjana: un client amb despesa altíssima arrossega el seu centroide o fins i tot segresta un clúster per a ell sol. Mitigacions: tractar els outliers abans (03-01) o usar la transformació log (03-03).
  • Sensibilitat a l'escala. Sense StandardScaler, la feature de major magnitud domina la distància i el clustering resultant és un artefacte de les unitats (03-05). No és opcional: és requisit.
  • Tot punt acaba en un clúster. K-means no té concepte de "soroll": la comanda més anòmala del món serà assignada a algun centroide. Per detectar anomalies necessitarem DBSCAN (05-04).

Errors Comuns i Consells

  • Oblidar-se d'escalar. L'error número u. Si els teus segments semblen ordenats únicament per despesa, gairebé segur que vas agrupar euros contra unitats sense escalar. Revisa sempre que has aplicat StandardScaler abans de fit.
  • Prendre el número de clúster com a categoria amb significat. L'etiqueta 0/1/2/3 és arbitrària i pot canviar entre execucions. Anomena els segments pel seu perfil (taula de centroides), mai pel seu número.
  • Buscar el colze en dades sense estructura. Si els clients formen un núvol continu sense grups, el colze serà una corba suau sense doblec i el silhouette rondarà el 0. Això també és un resultat: significa que no hi ha segments naturals amb aquestes features — prova altres features abans de forçar una K.
  • Interpretar centroides en l'espai escalat. Un centroide amb despesa = 1.8 no significa 1,80 €: són desviacions estàndard. Usa inverse_transform abans de presentar perfils a negoci.
  • Consell: executa el clustering dues o tres vegades amb diferents random_state (mantenint n_init). Si els segments canvien molt, l'estructura és feble i convé desconfiar de qualsevol interpretació fina.

Exercicis

Exercici 1. Continua a mà l'exemple de la lliçó amb els mateixos 6 punts (3, 5, 8, 40, 45, 52) però amb la inicialització $c_1 = 40$, $c_2 = 45$ i $K=2$. Itera fins a convergir. Arribes a la mateixa partició final que a la lliçó? Què et diu això sobre els òptims locals?

Exercici 2. Amb la taula rfm de MercaFresh (o un DataFrame sintètic equivalent), calcula el colze i el silhouette per a K de 2 a 8 usant només dues features: recencia_dies i comandes_per_mes. Tria una K justificant-la amb tots dos criteris i dibuixa els clústers en un scatter amb plt.scatter(..., c=etiquetes).

Exercici 3. Afegeix a la taula rfm un client outlier amb despesa_mitjana_comanda = 5000 (la resta de features en valors mitjans) i reexecuta el K-means de la lliçó amb K=4, amb i sense StandardScaler. Compara les taules de centroides. Què li passa a l'outlier en cada cas i per què?

Solucions

Exercici 1

  • Iteració 1 — assignació: 3, 5, 8 i 40 van amb $c_1=40$ (estan més a prop de 40 que de 45); 45 i 52 amb $c_2=45$. Actualització: $c_1 = \text{mitjana}(3,5,8,40) = 14$; $c_2 = \text{mitjana}(45,52) = 48{,}5$.
  • Iteració 2 — assignació: 40 dista 26 de $c_1$ i 8,5 de $c_2$ → canvia al clúster 2. Actualització: $c_1 = 5{,}33$; $c_2 = 45{,}67$.
  • Iteració 3: ningú no canvia. Convergència en {3,5,8} i {40,45,52} — la mateixa partició que a la lliçó malgrat una inicialització oposada. En aquest dataset l'estructura és tan clara que tots dos inicis arriben al mateix òptim; amb dades ambigües no està garantit, i per això existeixen k-means++ i n_init.

Exercici 2

import numpy as np
from sklearn.metrics import silhouette_score

X2 = StandardScaler().fit_transform(rfm[["recencia_dies", "comandes_per_mes"]])
for k in range(2, 9):
    km = KMeans(n_clusters=k, random_state=42)
    lab = km.fit_predict(X2)
    print(f"K={k} | inercia={km.inertia_:8.1f} | sil={silhouette_score(X2, lab):.3f}")

km = KMeans(n_clusters=3, random_state=42)          # ajusta a la K triada
lab = km.fit_predict(X2)
plt.scatter(X2[:, 0], X2[:, 1], c=lab, s=15)
plt.scatter(*km.cluster_centers_.T, marker="X", s=200, c="red")
plt.xlabel("recencia (esc.)"); plt.ylabel("comandes/mes (esc.)")
plt.show()

Amb només aquestes dues features l'habitual és que colze i silhouette apuntin a K=3 (actius freqüents, actius esporàdics, adormits): en treure la despesa desapareix la distinció VIP/habitual. Lliçó implícita: els segments depenen de les features triades, no només de l'algorisme.

Exercici 3

Sense escalar, la columna de despesa domina la distància completament: l'outlier queda sol en un clúster propi (o arrossega brutalment un centroide) i els altres tres clústers es reparteixen per despesa, ignorant recència i freqüència. Amb StandardScaler el dany es redueix (l'outlier queda a unes quantes desviacions, no a tres ordres de magnitud), però continua sent el punt més influent del seu clúster i esbiaixa el seu centroide cap amunt — la mitjana no perdona (02-01). Tractament correcte: detectar i tractar l'outlier a la neteja (03-01) o esmorteir-lo amb log1p (03-03) abans de segmentar.

Conclusió

Has creuat la frontera del no supervisat: sense etiquetes ni "resposta correcta", K-means descobreix grups alternant dos passos —assignar cada punt al seu centroide més proper i recalcular centroides com a mitjanes— fins a convergir. Saps triar K combinant el colze (inèrcia) amb el silhouette, saps que k-means++ i n_init et protegeixen de males inicialitzacions, i has segmentat els clients de MercaFresh amb les seves features RFM escalades, convertint números de clúster en perfils amb nom i acció de negoci. També coneixes la lletra petita: clústers esfèrics, K a priori, al·lèrgia als outliers i a escales dispars.

Entre aquestes limitacions n'hi ha una d'especialment elegant de resoldre: i si no haguéssim de fixar K per endavant, sinó que poguéssim veure totes les maneres d'agrupar els clients —de 2 grups a 20— en un sol dibuix i tallar allà on més convingui? Aquest dibuix existeix, es diu dendrograma, i és el cor de la lliçó següent: el clustering jeràrquic.

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