Al final de la lliçó anterior ens va quedar una matriu totalment numèrica però amb escales dispars: columnes one-hot que valen 0 o 1 convivint amb despeses de fins a 15.400 €. Per a molts algorismes, aquesta disparitat és un problema seriós: una columna amb nombres grans domina els càlculs i les altres es tornen invisibles, no perquè importin menys, sinó per pur accident d'unitats. Normalitzar i estandarditzar significa posar totes les variables en escales comparables. En aquesta lliçó entendràs per què l'escala importa (i per a quins algorismes), dominaràs els tres escaladors fonamentals de scikit-learn —MinMaxScaler, StandardScaler i RobustScaler—, en veuràs l'efecte abans i després, i aprendràs la regla d'or que evita fuites: ajustar l'escalador només amb dades d'entrenament.

Contingut

  1. Per què l'escala importa
  2. Quins algorismes ho necessiten i quins no
  3. Min-Max scaling: portar-ho tot a [0, 1]
  4. Estandardització z-score: StandardScaler
  5. RobustScaler: escalar amb outliers a casa
  6. Comparació visual abans i després
  7. Quin escalador fer servir: taula de decisió
  8. Fit a train, transform a test

Per què l'escala importa

Prenem dos clients de MercaFresh descrits per dues variables: despesa total en euros i antiguitat en anys.

  • Client A: 1.200 € de despesa, 1 any d'antiguitat.
  • Client B: 1.150 € de despesa, 9 anys d'antiguitat.

Per a un algorisme basat en distàncies (com el K-NN que veurem a 04-05), la "diferència" entre tots dos és:

import numpy as np

A = np.array([1200, 1])
B = np.array([1150, 9])
print(np.sqrt(((A - B) ** 2).sum()))   # 50.64

La distància surt 50,64... dels quals 50 vénen de la despesa i només 0,64 de l'antiguitat. Els 8 anys de diferència —enormes en termes de fidelitat del client— queden aixafats per 50 € de diferència de despesa, que és xavalla. L'antiguitat es podria eliminar del dataset i el resultat gairebé no canviaria: la unitat de mesura, no la importància real, està decidint quina variable mana.

El mateix problema apareix, amb una altra mecànica, en els algorismes que aprenen per gradient (regressió logística 04-02, SVM 04-04, xarxes neuronals 04-07): quan les variables tenen escales molt diferents, la superfície d'error es torna una vall allargada i estreta, i el procés d'optimització fa ziga-zagues lentament en lloc de baixar recte. Escalar arrodoneix la vall i accelera —i estabilitza— l'entrenament. Els detalls de cada algorisme pertanyen al mòdul 4; aquí n'hi ha prou amb el missatge: distàncies i gradients exigeixen escales comparables.

Quins algorismes ho necessiten i quins no

Família Sensible a l'escala? Per què
K-NN (04-05), K-means (05-01) Sí, crític Calculen distàncies entre files
SVM (04-04) Sí, crític Distàncies a l'hiperplà + optimització
Regressió lineal/logística amb gradient (04-01, 04-02) Convergència del gradient; comparabilitat de coeficients
Xarxes neuronals (04-07) Gradients estables
PCA (05-03) Sí, crític La variància gran domina les components
Arbres de decisió (04-03), Random Forest, Gradient Boosting (07-02/03) No Només fan talls tipus "despesa > 500?"; el tall és el mateix en qualsevol escala
Naive Bayes (04-06) En general no Treballa amb probabilitats per variable

Aquesta taula t'acompanyarà tot el curs: abans d'entrenar qualsevol model, pregunta't a quina fila cau. I un recordatori de la lliçó 03-02: el KNNImputer també mesura distàncies, així que també agraeix dades escalades.

Min-Max scaling: portar-ho tot a [0, 1]

La normalització Min-Max transforma cada variable perquè el seu mínim sigui 0 i el seu màxim 1:

x_escalat = (x - min) / (max - min)
import pandas as pd
from sklearn.preprocessing import MinMaxScaler

clients = pd.DataFrame({
    "despesa_total": [1250.5, 890.0, 2100.75, 310.2, 15400.0, 670.4, 980.1, 45.9],
    "antiguitat_anys": [3, 1, 6, 2, 9, 4, 1, 5],
    "num_comandes": [18, 12, 25, 4, 210, 9, 14, 6],
})

mms = MinMaxScaler()
escalat = pd.DataFrame(mms.fit_transform(clients), columns=clients.columns)
print(escalat.round(3))
print(mms.data_min_, mms.data_max_)   # allo apres al fit

Interpretació directa: 0 és "el client que menys", 1 "el que més", 0,5 "a mig camí". Avantatges i inconvenients:

  • A favor: rang acotat i predictible ([0, 1]), interpretació intuïtiva, encaixa on s'exigeix un rang fix (per exemple, algunes xarxes neuronals o imatges amb píxels 0-255).
  • En contra: extremadament sensible als outliers. Mira la columna despesa_total: el client de 15.400 € s'endú l'1, i tots els altres queden comprimits entre 0,00 i 0,13. L'outlier ha segrestat l'escala i la resta de clients són gairebé indistingibles entre si.
  • A més, una dada futura més gran que el màxim d'entrenament sortirà > 1: el rang [0, 1] només està garantit per a les dades amb què es va fer fit.

Estandardització z-score: StandardScaler

L'estandardització transforma cada variable restant-ne la mitjana i dividint per la seva desviació estàndard:

z = (x - mitjana) / desviacio

Et sona? És exactament el z-score de la lliçó 02-02: cada valor passa a expressar-se en "nombre de desviacions estàndard respecte a la mitjana". Un client amb z = +2 en despesa està dues desviacions per sobre de la despesa mitjana; un amb z = 0 és exactament la mitjana. El que allà fèiem servir com a detector de rareses, aquí es converteix en un idioma comú per a totes les variables: després d'estandarditzar, totes tenen mitjana 0 i desviació 1, i "estar 2 desviacions per sobre" significa el mateix en euros que en anys.

from sklearn.preprocessing import StandardScaler

ss = StandardScaler()
estandarditzat = pd.DataFrame(ss.fit_transform(clients), columns=clients.columns)
print(estandarditzat.round(2))
print(estandarditzat.mean().round(2))   # ~0 en totes les columnes
print(estandarditzat.std().round(2))    # ~1 en totes les columnes
print(ss.mean_, ss.scale_)              # mitjana i desviacio apreses al fit

Propietats:

  • No acota el rang: els valors típics cauen entre -3 i +3 (la regla 68-95-99.7 de 02-02), però un outlier pot sortir a z = 6 sense problema.
  • No canvia la forma de la distribució: si la despesa era asimètrica, la despesa estandarditzada continua sent asimètrica. Escalar no és transformar (això va ser 03-03); són passos complementaris: primer log1p corregeix la forma, després StandardScaler ajusta l'escala.
  • És l'escalador per defecte a la pràctica: prou robust per a la majoria de casos i el que assumeixen molts algorismes.
  • Continua fent servir mitjana i desviació, així que els outliers el pertorben (menys que a Min-Max, però ho fan): amb el client de 15.400 € a dins, la mitjana i la desviació de la despesa estan inflades.

RobustScaler: escalar amb outliers a casa

Quan els outliers són part legítima del dataset —i a MercaFresh ho són: els clients-restaurant existeixen i no els volem eliminar—, convé escalar amb estadístics que els ignorin. RobustScaler fa servir la mediana i l'IQR (el rang interquartílic de 02-01) en lloc de la mitjana i la desviació:

x_escalat = (x - mediana) / IQR
from sklearn.preprocessing import RobustScaler

rs = RobustScaler()
robust = pd.DataFrame(rs.fit_transform(clients), columns=clients.columns)
print(robust.round(2))
print(rs.center_, rs.scale_)   # mediana i IQR apresos al fit

Com que mediana i IQR gairebé no es mouen encara que hi hagi valors extrems (ho vas veure a 02-01: són estadístics robustos), el gruix dels clients queda ben repartit i l'outlier simplement surt amb un valor gran, sense comprimir els altres. És la tria natural quan la neteja de 03-01 va concloure "aquests outliers són reals i es queden".

Comparació visual abans i després

Res no convenç més que veure-ho. Comparem els tres escaladors sobre la despesa, amb el seu outlier inclòs:

import matplotlib.pyplot as plt

despesa = clients[["despesa_total"]]
versions = {
    "Original (EUR)": despesa.values.ravel(),
    "MinMaxScaler": MinMaxScaler().fit_transform(despesa).ravel(),
    "StandardScaler": StandardScaler().fit_transform(despesa).ravel(),
    "RobustScaler": RobustScaler().fit_transform(despesa).ravel(),
}

fig, axes = plt.subplots(1, 4, figsize=(14, 3))
for ax, (titol, valors) in zip(axes, versions.items()):
    ax.boxplot(valors, vert=True)
    ax.set_title(titol, fontsize=10)
plt.tight_layout()
plt.show()

El que mostren els quatre boxplots (els de 02-01, treballant de nou):

  • Original: caixa diminuta a baix, outlier llunyíssim a dalt. Escala en milers.
  • MinMaxScaler: la mateixa silueta exacta, però comprimida en [0, 1]: la caixa sencera ocupa un 13 % del rang. L'outlier mana.
  • StandardScaler: la mateixa silueta, centrada en 0; l'outlier surt a z ≈ 2,6 i la resta queda apinyada en una franja estreta per sota de la mitjana.
  • RobustScaler: la caixa central queda ben desplegada al voltant de 0 (de -0,5 a 0,5 aproximadament, perquè l'IQR passa a valer 1) i l'outlier se'n va lluny sense molestar ningú.

Conclusió visual: escalar mai no canvia la forma de la distribució, només la seva regla de mesurar; el que distingeix els escaladors és quin tram de les dades fan servir com a referència per a aquesta regla.

Quin escalador fer servir: taula de decisió

Situació Escalador recomanat Motiu
Cas general, sense outliers greus StandardScaler Estàndard de facto; l'assumeixen molts algorismes
Outliers legítims presents RobustScaler Mediana i IQR no es deixen arrossegar
Es necessita rang acotat [0, 1] MinMaxScaler L'únic que ho garanteix (a train)
Variable ja binària (one-hot) Cap Ja és a {0, 1}; escalar-la només resta interpretabilitat
Model d'arbres (04-03, 07-02/03) Cap de necessari Els talls són invariants a l'escala
Distribució molt asimètrica Transformar primer (03-03), escalar després Escalar no corregeix la forma

I la connexió amb el flux que anem muntant: l'escalador és un pas més del Pipeline de la branca numèrica del ColumnTransformer — imputació → transformació → escalat, en aquest ordre, mentre les columnes one-hot passen de llarg.

from sklearn.pipeline import Pipeline
from sklearn.impute import SimpleImputer

branca_numerica = Pipeline(steps=[
    ("imputar", SimpleImputer(strategy="median")),
    ("escalar", RobustScaler()),
])

Fit a train, transform a test

La regla que tanca la lliçó és la mateixa que va aparèixer a 03-02 amb la imputació, ara aplicada a l'escalat — i amb els escaladors és on més es peca. Tot escalador aprèn paràmetres a fit: mínims i màxims, mitjanes i desviacions, medianes i IQRs. Si aquests paràmetres es calculen amb el dataset complet abans de dividir en entrenament i prova (la divisió que formalitzarem a 06-01), les dades de prova hauran deixat la seva empremta en la mitjana o el màxim: fuita d'informació. L'avaluació sortirà una mica millor del que mereix, i cap error ni avís no t'alertarà.

El protocol correcte:

from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler

# 1) Dividir PRIMER (els detalls, a 06-01)
X_train, X_test = train_test_split(clients, test_size=0.25, random_state=42)

# 2) Ajustar l'escalador NOMES amb entrenament
scaler = StandardScaler()
scaler.fit(X_train)

# 3) Transformar tots dos conjunts amb ALLO APRES A TRAIN
X_train_esc = scaler.transform(X_train)
X_test_esc = scaler.transform(X_test)

# Drecera equivalent per a train: scaler.fit_transform(X_train)

Conseqüència que sorprèn la primera vegada: el conjunt de prova escalat no tindrà mitjana exactament 0 ni desviació exactament 1 — s'ha mesurat amb la regla de train, no amb la seva pròpia. I així ha de ser: en producció, cada client nou arribarà d'un en un i s'escalarà amb els paràmetres congelats de l'entrenament. fit_transform a train, transform a seques a test i en producció: grava-t'ho com a reflex. Si a més tot viu dins d'un Pipeline, el reflex ve de sèrie: el pipeline només fa fit quan tu li ho demanes sobre train.

Errors Comuns i Consells

  • Fer fit_transform sobre el conjunt de prova. És l'error de fuita més comú en principiants: a test (i en producció) només transform. Si el teu test escalat té mitjana 0 perfecta, sospita.
  • Escalar amb MinMaxScaler un dataset amb outliers. La resta de valors queda comprimida en un racó del [0, 1]. Amb outliers legítims, RobustScaler.
  • Esperar que escalar arregli l'asimetria. Escalar és una operació lineal: la forma de l'histograma no canvia. Per a la forma, les transformacions de 03-03.
  • Escalar les columnes one-hot. Un 0/1 estandarditzat es converteix en valors com -0,58/1,72: es perd la llegibilitat sense guanyar res de rellevant. Deixa les binàries fora de la branca d'escalat al ColumnTransformer.
  • Escalar la variable objectiu d'un problema de classificació. El churn (0/1) és l'etiqueta, no una característica: no s'escala.
  • Oblidar guardar l'escalador. Els paràmetres apresos (mean_, scale_...) són part del model: en producció necessitaràs el mateix objecte per escalar cada client nou (ho reprendrem al mòdul 8).
  • Consell: imprimeix el describe() del resultat escalat. Mitjanes ~0 i desviacions ~1 (o rangs [0, 1]) a train confirmen que tot ha funcionat; valors absurds delaten columnes que no s'havien d'escalar.

Exercicis

Exercici 1

Sense executar codi: els temps de lliurament de MercaFresh són [38, 42, 44, 47, 52, 55, 61, 190] minuts (el 190 va ser una comanda amb la furgoneta avariada). Quin escalador triaries i per què? Què li passaria a la resta de valors amb MinMaxScaler?

Exercici 2

Estandarditza a mà (sense scikit-learn, només NumPy) la variable antiguitat = [3, 1, 6, 2, 9, 4, 1, 5] i comprova que el resultat té mitjana 0 i desviació 1. Després verifica que StandardScaler dona el mateix resultat.

Exercici 3

Aquest codi té un error de fuita d'informació. Troba'l i corregeix-lo:

scaler = StandardScaler()
dades_esc = scaler.fit_transform(dades)
X_train, X_test = train_test_split(dades_esc, test_size=0.3)

Solucions

Exercici 1

RobustScaler. El 190 és un outlier amb causa coneguda (avaria) però present a les dades; mitjana i desviació (StandardScaler) quedarien inflades per ell. Amb MinMaxScaler seria pitjor: 190 s'enduria l'1 i els set valors normals quedarien comprimits aproximadament entre 0,00 i 0,15, gairebé indistingibles entre si. RobustScaler, en fer servir la mediana (49,5) i l'IQR, desplega bé els valors normals i deixa que el 190 surti lluny sense distorsionar els altres. (Alternativa legítima de 03-01: tractar el 190 com a error puntual i corregir-lo abans d'escalar.)

Exercici 2

import numpy as np
from sklearn.preprocessing import StandardScaler

x = np.array([3, 1, 6, 2, 9, 4, 1, 5], dtype=float)

z = (x - x.mean()) / x.std()        # std() de NumPy divideix per n, com StandardScaler
print(z.round(3))
print(z.mean().round(10), z.std().round(10))   # 0.0 i 1.0

ss = StandardScaler()
z_sk = ss.fit_transform(x.reshape(-1, 1)).ravel()
print(np.allclose(z, z_sk))          # True

Detall fi: StandardScaler fa servir la desviació poblacional (dividir per n), igual que np.std() per defecte; la mostral de pandas (Series.std(), que divideix per n-1) donaria valors lleugerament diferents.

Exercici 3

El fit_transform s'executa sobre totes les dades abans de dividir: la mitjana i la desviació apreses contenen informació de les files que després seran de test. Correcció:

X_train, X_test = train_test_split(dades, test_size=0.3, random_state=42)

scaler = StandardScaler()
X_train_esc = scaler.fit_transform(X_train)   # apren NOMES de train
X_test_esc = scaler.transform(X_test)         # aplica allo apres

Primer dividir, després ajustar només amb train, i transformar test amb els paràmetres de train.

Conclusió

Ja domines l'última peça purament mecànica del preprocessament: per què els algorismes de distàncies i gradients necessiten escales comparables, com MinMaxScaler acota a [0, 1] (però es rendeix davant dels outliers), com StandardScaler converteix cada variable als z-scores que vas conèixer a 02-02, com RobustScaler escala amb mediana i IQR quan els outliers són legítims, i la regla innegociable d'ajustar sempre l'escalador només amb dades d'entrenament. El dataset de churn de MercaFresh està net, complet, transformat, codificat i escalat.

Està, doncs, acabat? Tècnicament sí; competitivament no. Fins ara hem reparat i adaptat les columnes que ja existien. El salt de qualitat ve de crear columnes noves que condensin coneixement de negoci: quant fa que el client no compra, quant val, si la seva activitat creix o s'apaga. Aquesta és l'enginyeria de característiques, la lliçó que tanca el mòdul — i on el dataset de MercaFresh assolirà la seva forma definitiva.

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