Els dos models anteriors comparteixen una limitació de fons: la seva frontera de decisió és un hiperplà — un tall recte a l'espai de features. Però el churn de MercaFresh no sempre es deixa tallar en línia recta: un client abandona si fa molt que no compra i a més la seva tendència es desploma, o si és de pla bàsic i la seva despesa cau, i aquestes regles amb "i" i "o" dibuixen fronteres esglaonades. Els arbres de decisió ataquen el problema com ho faria un analista humà: encadenant preguntes. En aquesta lliçó veuràs com un algorisme decideix què preguntar i en quin ordre (impuresa de Gini i entropia, amb els comptes fets a mà), entrenaràs i dibuixaràs un arbre sobre el churn de MercaFresh, i entendràs per què aquest model tan interpretable és també un memoritzador compulsiu si no se li posa fre.

Contingut

  1. La intuïció: classificar preguntant
  2. Com es tria cada divisió: impuresa de Gini
  3. Entropia i guany d'informació
  4. Arbres de regressió (breu)
  5. Implementació amb scikit-learn: churn de MercaFresh
  6. Interpretabilitat: regles llegibles i feature importance
  7. Hiperparàmetres clau i la tendència al sobreajustament
  8. Avantatges i limitacions

La intuïció: classificar preguntant

Un arbre de decisió és una cascada de preguntes binàries sobre les features. Cada client entra per l'arrel, respon preguntes i descendeix fins a una fulla, que emet la predicció:

flowchart TD
    A{"recencia_dies <= 45?"} -- "Si" --> B{"tendencia >= 0.8?"}
    A -- "No" --> C{"comandes_per_mes <= 1.5?"}
    B -- "Si" --> D["Fulla: ES QUEDA<br/>(230 clients, 96% fidels)"]
    B -- "No" --> E["Fulla: RISC<br/>(45 clients, 60% churn)"]
    C -- "Si" --> F["Fulla: CHURN<br/>(180 clients, 91% churn)"]
    C -- "No" --> G{"pla = basic?"}
    G -- "Si" --> H["Fulla: CHURN<br/>(90 clients, 74% churn)"]
    G -- "No" --> I["Fulla: RISC<br/>(55 clients, 52% churn)"]

Observa tres coses:

  • Cada camí arrel→fulla és una regla de negoci llegible: "si el client fa més de 45 dies que no compra i fa ≤1.5 comandes/mes, prediu churn (el 91% dels històrics d'aquesta fulla van abandonar)".
  • Les fulles guarden proporcions, no només etiquetes: l'arbre també dona probabilitats (la fracció de churn a la fulla).
  • La frontera resultant és esglaonada: cada pregunta talla l'espai amb un pla perpendicular a un eix; la combinació forma regions rectangulars. És un model genuïnament no lineal sense necessitat de transformar features.

La pregunta del milió: donat el dataset, quina pregunta cal posar a l'arrel? recencia <= 45 o despesa <= 20? I per què 45 i no 60? L'algorisme necessita un criteri numèric per comparar preguntes candidates.

Com es tria cada divisió: impuresa de Gini

La idea: una bona pregunta separa els clients en grups tan purs com sigui possible — grups on gairebé tots són churn o gairebé tots són fidels. La impuresa de Gini mesura com de barrejat està un grup:

$$Gini = 1 - \sum_{k} p_k^2$$

on $p_k$ és la proporció de cada classe al grup. Per a dues classes:

Composició del grup Càlcul Gini Lectura
100% churn $1 - 1^2 - 0^2$ 0.0 Pur: perfecte
90% / 10% $1 - 0.81 - 0.01$ 0.18 Gairebé pur
50% / 50% $1 - 0.25 - 0.25$ 0.50 Màxima barreja: inútil

Càlcul a mà complet. Node amb 10 clients de MercaFresh: 4 churn, 6 fidels. Gini inicial: $1 - 0.4^2 - 0.6^2 = 1 - 0.16 - 0.36 = 0.48$. Candidata: recencia_dies <= 60.

Clients Churn Fidels Gini del grup
recència ≤ 60 (esquerra) 6 1 5 $1 - (1/6)^2 - (5/6)^2 = 0.278$
recència > 60 (dreta) 4 3 1 $1 - (3/4)^2 - (1/4)^2 = 0.375$

Gini després de dividir = mitjana ponderada per mida: $\frac{6}{10} \cdot 0.278 + \frac{4}{10} \cdot 0.375 = 0.167 + 0.150 = 0.317$.

La divisió redueix la impuresa de 0.48 a 0.317: un guany de 0.163. L'algorisme repeteix aquest càlcul per a cada feature i cada punt de tall possible, tria la divisió de màxim guany, i recorre sobre cada grup fill fins que els nodes siguin purs o s'arribi a un límit. Aquest procediment voraç (triar sempre el millor localment, sense mirar enrere) s'anomena CART i és el que implementa scikit-learn.

Entropia i guany d'informació

El criteri alternatiu clàssic ve de la teoria de la informació. L'entropia mesura la incertesa d'un grup:

$$H = -\sum_{k} p_k \log_2 p_k$$

Amb el mateix node d'abans (4 churn, 6 fidels): $H = -0.4 \log_2 0.4 - 0.6 \log_2 0.6 = 0.529 + 0.442 = 0.971$ bits — gairebé la incertesa màxima (1 bit, la d'un 50/50). Un grup pur té entropia 0: no hi ha res a endevinar. El guany d'informació d'una divisió és la reducció d'entropia, calculada amb la mateixa mitjana ponderada que vam fer amb Gini.

Gini o entropia? A la pràctica donen arbres gairebé idèntics:

Criteri Rang (2 classes) Cost de càlcul Ús
Gini 0 – 0.5 Menor (sense logaritmes) Per defecte a sklearn
Entropia 0 – 1 bit Una mica més gran criterion="entropy"

Queda't amb la idea comuna: dividir és comprar puresa, i l'arbre sempre compra allà on el guany és màxim.

Arbres de regressió (breu)

El mateix mecanisme prediu números: per a la despesa mensual de 04-01, cada fulla prediu la mitjana de l'objectiu entre els seus clients, i la "impuresa" a reduir és la variància (el MSE dins del node). La pregunta comandes_per_mes <= 3.2 és bona si separa els clients en dos grups de despeses internament semblants. DecisionTreeRegressor ho implementa amb la mateixa interfície. La predicció resultant és una funció esglaonada — constant dins de cada regió rectangular — cosa que fa aquests arbres mals extrapoladors però bons capturadors de salts i llindars.

Implementació amb scikit-learn: churn de MercaFresh

Reutilitzem el dataset de churn de la lliçó anterior (el df de 04-02). Un detall alliberador que reprèn la taula de 03-05: els arbres comparen cada feature amb si mateixa (recencia <= 45 no canvia si la recència està en dies o escalada), així que no necessiten escalat ni desasimetrització. N'hi ha prou d'imputar nuls i codificar categòriques:

from sklearn.model_selection import train_test_split
from sklearn.pipeline import Pipeline
from sklearn.compose import ColumnTransformer
from sklearn.impute import SimpleImputer
from sklearn.preprocessing import OneHotEncoder, OrdinalEncoder
from sklearn.tree import DecisionTreeClassifier, plot_tree, export_text
import matplotlib.pyplot as plt

num_cols = ["edat", "satisfaccio", "recencia_dies", "comandes_per_mes",
            "despesa_mitjana_comanda", "ratio_inactivitat", "tendencia"]

# Preprocessador minim per a arbres: sense escalar, sense Yeo-Johnson (03-05)
prep_arbre = ColumnTransformer([
    ("num", SimpleImputer(strategy="median", add_indicator=True), num_cols),
    ("cat", OneHotEncoder(sparse_output=False, handle_unknown="ignore"), ["ciutat"]),
    ("ord", OrdinalEncoder(categories=[["basic", "estandard", "premium"]]), ["pla"]),
])

X_entrenament, X_prova, y_entrenament, y_prova = train_test_split(
    X, y, test_size=0.2, stratify=y, random_state=42)

arbre = Pipeline([
    ("prep", prep_arbre),
    ("model", DecisionTreeClassifier(max_depth=3, min_samples_leaf=20,
                                     random_state=42)),
])
arbre.fit(X_entrenament, y_entrenament)
print(f"Accuracy en prova: {arbre.score(X_prova, y_prova):.2%}")

# Dibuixar l'arbre entrenat
noms = arbre.named_steps["prep"].get_feature_names_out()
plt.figure(figsize=(16, 8))
plot_tree(arbre.named_steps["model"], feature_names=noms,
          class_names=["fidel", "churn"], filled=True, rounded=True)
plt.show()

Lectura del codi:

  • max_depth=3: com a molt 3 preguntes encadenades — un arbre que cap en una diapositiva. De seguida veurem per què limitar la profunditat no és opcional.
  • min_samples_leaf=20: cap fulla no pot quedar-se amb menys de 20 clients; evita regles basades en anècdotes.
  • plot_tree pinta cada node amb la seva pregunta, el seu Gini, quantes mostres conté i el seu repartiment de classes — la teoria dels apartats 2 i 3, dibuixada. Colors més intensos = nodes més purs.
  • random_state=42: els empats entre divisions igual de bones es trenquen a l'atzar; fixar la llavor fa l'arbre reproduïble.

Interpretabilitat: regles llegibles i feature importance

Dues sortides de l'arbre valen or en una reunió de negoci. La primera, les regles en text pla:

print(export_text(arbre.named_steps["model"], feature_names=list(noms)))
|--- num__recencia_dies <= 52.50
|   |--- num__tendencia >  0.74
|   |   |--- class: fidel
|   ...

Qualsevol responsable de retenció ho entén sense saber què és un Gini. La segona, la importància de cada feature: quanta reducció total d'impuresa van aportar les divisions que la fan servir:

import pandas as pd
importancies = pd.Series(arbre.named_steps["model"].feature_importances_,
                         index=noms).sort_values(ascending=False)
print(importancies.head(5).round(3))

Si recencia_dies i tendencia dominen el rànquing, el model confirma la tesi de negoci de 03-06: el churn s'anuncia amb silenci i refredament. Cautela: les importàncies sumen 1 i es reparteixen entre features correlacionades de manera una mica arbitrària (mateix avís que amb els coeficients de 04-01/04-02), i aquest mètode tendeix a afavorir features amb molts valors diferents.

Hiperparàmetres clau i la tendència al sobreajustament

Aquí hi ha el costat fosc. Sense límits, l'algorisme divideix fins que cada fulla és pura — encara que per aconseguir-ho necessiti una fulla per client. Aquest arbre se sap l'entrenament de memòria: encerta el 100% en entrenament i s'ensorra en prova, perquè les seves darreres divisions no capturen patrons sinó soroll individual. És el fenomen del sobreajustament (overfitting), que diagnosticarem amb rigor a 06-05; els arbres en són l'exemple de manual.

sense_fre = Pipeline([("prep", prep_arbre),
                      ("model", DecisionTreeClassifier(random_state=42))])
sense_fre.fit(X_entrenament, y_entrenament)
print(f"Entrenament: {sense_fre.score(X_entrenament, y_entrenament):.2%}"
      f" | Prova: {sense_fre.score(X_prova, y_prova):.2%}")
# Tipic: Entrenament: 100.00% | Prova: forca pitjor que l'arbre podat

Els frens (hiperparàmetres de poda prèvia):

Hiperparàmetre Què limita Efecte d'estrènyer-lo
max_depth Preguntes encadenades màximes Arbre més simple i general, risc de quedar-se curt
min_samples_leaf Mida mínima de fulla Prohibeix regles anecdòtiques
min_samples_split Mida mínima per dividir un node Similar, actua abans
ccp_alpha Poda posterior per cost-complexitat Poda branques que aporten poc

Triar aquests valors sistemàticament és matèria de l'optimització d'hiperparàmetres (07-05). I un avançament que explica mitja indústria del ML: la millor cura del sobreajustament d'un arbre no és podar-lo amb més fúria, sinó fer la mitjana de molts arbres diferents — els ensembles tipus Random Forest (07-02) i el gradient boosting (07-03) neixen exactament d'aquí. En aquest curs, l'arbre individual és la peça; allà en veuràs l'edifici.

Avantatges i limitacions

Avantatges Limitacions
Interpretable: regles llegibles i dibuixables Sobreajusta amb facilitat si no es poda
No necessita escalat ni transformacions de forma (03-05) Inestable: petits canvis a les dades poden canviar tot l'arbre
Captura no-linealitat i interaccions sense enginyeria prèvia Fronteres només perpendiculars als eixos (les diagonals li costen esglaons)
Maneja features numèriques i ordinals amb naturalitat Extrapola malament en regressió (predicció esglaonada, plana fora del rang)
Ràpid en predicció Un arbre sol rarament és el model més precís disponible

Errors Comuns i Consells

  • Entrenar sense límits i presumir del 100% en entrenament. Aquest número no mesura aprenentatge sinó memòria. Compara sempre entrenament contra prova; una bretxa gran és l'alarma de sobreajustament (06-05).
  • Escalar les features "per si de cas" amb un Pipeline compartit. No trenca res, però destrossa la interpretabilitat: la regla recencia <= 0.83 (en unitats robustes) no l'entén ningú. Per a arbres, deixa les features en les seves unitats naturals.
  • Prendre l'arbre dibuixat com a veritat estable. Reentrenar amb un 5% més de dades pot reorganitzar branques senceres. Les importàncies solen ser més estables que l'estructura; per a les conclusions de negoci, basa't en elles.
  • Llegir feature_importances_ com a causalitat. És reducció d'impuresa, no efecte causal — la mateixa cautela de 02-03 que arrosseguem des dels coeficients.
  • Consell: comença sempre amb un arbre petit (max_depth=3) i dibuixa'l. Encara que el model final sigui un altre, aquest dibuix és la millor eina d'exploració i comunicació del projecte: et diu quines features tallen i per on.

Exercicis

Exercici 1. A mà: un node té 8 clients (4 churn, 4 fidels). La divisió A separa en (3 churn, 1 fidel) i (1 churn, 3 fidels); la divisió B separa en (4 churn, 2 fidels) i (0 churn, 2 fidels). Calcula el Gini ponderat després de cada divisió i decideix quina triaria l'algorisme.

Exercici 2. Entrena l'arbre del churn amb max_depth d'1 a 12 i dibuixa l'accuracy en entrenament i en prova davant de la profunditat. Descriu les tres zones de la corba i localitza la profunditat raonable.

Exercici 3. Extreu amb export_text la regla completa del camí que porta a la fulla amb més proporció de churn de l'arbre de max_depth=3, i tradueix-la a una frase que pogués aparèixer en un informe per a l'equip de retenció de MercaFresh.

Solucions

Exercici 1

Gini inicial: $1 - 0.5^2 - 0.5^2 = 0.5$.

  • Divisió A: cada fill té Gini $1 - (3/4)^2 - (1/4)^2 = 0.375$. Ponderat: $\frac{4}{8}(0.375) + \frac{4}{8}(0.375) = 0.375$.
  • Divisió B: fill esquerre $1 - (4/6)^2 - (2/6)^2 = 0.444$; fill dret $1 - 0 - 1 = 0$ (pur). Ponderat: $\frac{6}{8}(0.444) + \frac{2}{8}(0) = 0.333$.

Guanya B (0.333 < 0.375): encara que deixa un fill força barrejat, fabrica un node completament pur, i al criteri li compensa. Lliçó: l'algorisme valora la puresa ponderada total, no el repartiment equilibrat.

Exercici 2

import matplotlib.pyplot as plt

profunditats = range(1, 13)
acc_entrenament, acc_prova = [], []
for d in profunditats:
    m = Pipeline([("prep", prep_arbre),
                  ("model", DecisionTreeClassifier(max_depth=d, random_state=42))])
    m.fit(X_entrenament, y_entrenament)
    acc_entrenament.append(m.score(X_entrenament, y_entrenament))
    acc_prova.append(m.score(X_prova, y_prova))

plt.plot(profunditats, acc_entrenament, marker="o", label="entrenament")
plt.plot(profunditats, acc_prova, marker="s", label="prova")
plt.xlabel("max_depth"); plt.ylabel("accuracy"); plt.legend(); plt.show()

Tres zones: (1) profunditats 1-2, totes dues corbes baixes — l'arbre és massa simple per al patró (underfitting); (2) zona intermèdia (típicament 3-5), la prova assoleix el seu màxim; (3) a partir d'aquí l'entrenament continua pujant cap al 100% mentre la prova s'estanca o cau — l'arbre memoritza soroll (overfitting). La profunditat raonable és la del màxim en prova. Aquesta corba en U invertida és el retrat del compromís biaix-variància que formalitzarem a 06-05.

Exercici 3

print(export_text(arbre.named_steps["model"], feature_names=list(noms)))

Localitza la fulla amb class: churn i més puresa i encadena'n les condicions. Amb les dades simulades, un resultat típic: recencia_dies > 52.5 i tendencia <= 0.74 i comandes_per_mes <= 2.1. Traducció d'informe: "El segment de més risc són els clients amb més de 52 dies sense comprar l'activitat recent dels quals és menys de tres quartes parts de l'habitual i que fan dues comandes al mes o menys: històricament, la gran majoria d'aquests clients acaba abandonant. Recomanem prioritzar-los en la campanya de retenció." La regla és accionable precisament perquè les features (03-06) es van dissenyar amb lectura de negoci.

Conclusió

Has afegit el primer model no lineal a la teva caixa d'eines: l'arbre classifica encadenant preguntes, tria cadascuna comprant la màxima puresa (Gini o entropia — ja saps fer el compte a mà), es llegeix com a regles de negoci i ordena les features per importància. També n'has vist el taló d'Aquil·les: sense max_depth i min_samples_leaf, memoritza en lloc d'aprendre — un avançament del sobreajustament (06-05) i la motivació dels ensembles (07-02). I una comoditat nova: és el primer model del curs que no necessita escalat.

L'arbre dibuixa fronteres esglaonades, perpendiculars als eixos. La lliçó següent ataca la geometria des de l'angle oposat: en lloc de trossejar l'espai amb preguntes, buscar directament el millor tall possible — l'hiperplà que separa les classes amb la màxima distància de seguretat — i, quan cap tall recte no basti, projectar les dades a un espai on sí que n'hi hagi. Són les màquines de vectors de suport.

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