A la lliçó anterior vam deixar una branca del ColumnTransformer pendent: les columnes categòriques. La ciutat del client, la categoria del producte o el pla de subscripció de MercaFresh són text, i els models de Machine Learning només saben operar amb nombres: multipliquen, sumen distàncies, calculen gradients. "Barcelona" no es pot multiplicar. Codificar és traduir categories a nombres, i no és un tràmit mecànic: cada mètode de codificació explica al model una història diferent sobre les dades, i triar malament (per exemple, imposar un ordre que no existeix) introdueix patrons falsos que el model aprendrà obedientment. En aquesta lliçó veuràs els mètodes principals, les seves trampes i una guia clara de quan fer servir cadascun.

Contingut

  1. Per què els models necessiten nombres
  2. Nominal vs ordinal: la distinció que ho governa tot
  3. Label i ordinal encoding
  4. One-hot encoding
  5. El problema de l'alta cardinalitat
  6. Frequency i target encoding
  7. Taula comparativa i decisió
  8. Cas complet MercaFresh: ciutat, categoria i pla

Per què els models necessiten nombres

Internament, gairebé tots els algorismes del mòdul 4 redueixen la seva feina a aritmètica sobre matrius: la regressió calcula sumes ponderades (pes1 * x1 + pes2 * x2 + ...), K-NN mesura distàncies entre files, les xarxes neuronals multipliquen matrius. Tan bon punt una columna conté "Barcelona", aquesta maquinària s'atura: no existeix 0.7 * "Barcelona".

La temptació ingènua és assignar nombres a dit: Barcelona = 1, Madrid = 2, Valencia = 3. Funciona sintàcticament... i aquí hi ha el perill: el model es creurà els nombres. Interpretarà que Valencia és "el triple" que Barcelona, que Madrid és "entre" totes dues, que la mitjana de Barcelona i Valencia és Madrid. Hem inventat un ordre i unes distàncies que no existeixen en la realitat. Tota aquesta lliçó gira al voltant d'evitar aquest engany.

Nominal vs ordinal: la distinció que ho governa tot

Del mòdul 2 (lliçó 02-01) recordaràs la classificació de variables. Per codificar, la distinció crucial és aquesta:

Tipus Hi ha ordre real? Exemples MercaFresh Codificació natural
Nominal No: només etiquetes ciutat, categoria de producte, mètode de pagament One-hot
Ordinal Sí: ordre amb significat pla (bàsic < estàndard < premium), satisfacció (baixa < mitjana < alta) Ordinal (enters ordenats)

La pregunta de control: té sentit dir que una categoria és "més gran" que una altra? Premium > bàsic, sí; Madrid > Sevilla, no. Respondre aquesta pregunta abans de tocar el teclat evita el 90 % dels errors de codificació.

Label i ordinal encoding

Tots dos assignen un enter a cada categoria; la diferència és en qui controla l'ordre.

Ordinal encoding: quan l'ordre és real

Per al pla de subscripció de MercaFresh, l'ordre és informació: un client premium paga més i es comporta diferent. Aquí els enters ordenats són la codificació correcta, i hem de fixar l'ordre explícitament:

import pandas as pd
from sklearn.preprocessing import OrdinalEncoder

df = pd.DataFrame({
    "id_client": [101, 104, 105, 106, 107],
    "pla": ["basic", "premium", "estandard", "basic", "premium"],
})

# categories fixa l'ordre: basic=0 < estandard=1 < premium=2
enc = OrdinalEncoder(categories=[["basic", "estandard", "premium"]])
df["pla_cod"] = enc.fit_transform(df[["pla"]])
print(df)
#    id_client        pla  pla_cod
# 0        101      basic      0.0
# 1        104    premium      2.0
# 2        105  estandard      1.0
# ...

Sense el paràmetre categories, OrdinalEncoder ordena alfabèticament — que aquí per casualitat coincideix, però amb ["baix", "alt", "mitja"] produiria alt=0, baix=1, mitja=2: un desastre silenciós. Amb ordinals, dicta sempre l'ordre tu.

En pandas pur, l'equivalent és un mapatge explícit, igual de vàlid:

ordre = {"basic": 0, "estandard": 1, "premium": 2}
df["pla_cod"] = df["pla"].map(ordre)

Label encoding: gairebé només per a la variable objectiu

LabelEncoder de scikit-learn fa el mateix (categoria → enter) però està pensat per a la variable objectiu (per exemple, churn "si"/"no" → 1/0), no per a les característiques: treballa amb una sola columna i assigna l'ordre alfabètic sense que el puguis controlar.

from sklearn.preprocessing import LabelEncoder

le = LabelEncoder()
y = le.fit_transform(["no", "si", "no", "no", "si"])   # [0 1 0 0 1]
print(le.classes_)                                      # ['no' 'si']

El perill: ordre fals sobre nominals

Què passa si codifiquem la ciutat amb enters? Barcelona=0, Madrid=1, Sevilla=2, Valencia=3. Un model lineal aprendrà un únic coeficient per a "ciutat" i conclourà coses com "cada pas de ciutat augmenta el churn un 3 %" — una regla absurda que depèn de l'ordre alfabètic. Alguns models d'arbre (04-03) sobreviuen a això perquè només fan talls, però com a norma general: enters per a nominals = ordre fals = patrons inventats. Per als nominals, l'eina és la següent.

One-hot encoding

La idea: en lloc d'un nombre per categoria, una columna binària per categoria. Cada fila té un 1 a la seva columna i 0 a les altres — d'aquí el nom, "un de sol encès".

df2 = pd.DataFrame({
    "id_client": [101, 104, 105, 108, 110],
    "ciutat": ["Barcelona", "Barcelona", "Valencia", "Madrid", "Sevilla"],
})

# Amb pandas: rapid per explorar
dummies = pd.get_dummies(df2["ciutat"], prefix="ciutat", dtype=int)
print(pd.concat([df2, dummies], axis=1))
#    id_client     ciutat  ciutat_Barcelona  ciutat_Madrid  ciutat_Sevilla  ciutat_Valencia
# 0        101  Barcelona                 1              0               0                0
# 1        104  Barcelona                 1              0               0                0
# 2        105   Valencia                 0              0               0                1
# ...

Ja no hi ha ordre ni distàncies inventades: cada ciutat és una dimensió independent i el model aprèn un efecte propi per a cadascuna. És la codificació per defecte per a nominals de poques categories.

get_dummies vs OneHotEncoder

get_dummies és còmode, però té un defecte greu en un flux de ML: no memoritza les categories. Si el lot del mes que ve no porta cap client de Sevilla, generarà una columna menys i el model rebrà una matriu amb una altra forma. OneHotEncoder segueix el patró fit/transform que ja domines: aprèn les categories a fit i garanteix sempre les mateixes columnes.

from sklearn.preprocessing import OneHotEncoder

ohe = OneHotEncoder(sparse_output=False, handle_unknown="ignore")
X_ciutat = ohe.fit_transform(df2[["ciutat"]])
print(ohe.get_feature_names_out())
# ['ciutat_Barcelona' 'ciutat_Madrid' 'ciutat_Sevilla' 'ciutat_Valencia']
  • handle_unknown="ignore": si en producció apareix un client de Bilbao (categoria mai vista), el codifica com a tot zeros en lloc de llançar un error. Imprescindible al món real.
  • sparse_output=False retorna una matriu normal; amb moltes columnes convé deixar la sortida dispersa (sparse) per estalviar memòria.
  • Encaixa directament com la branca categòrica del ColumnTransformer de la lliçó anterior.

La trampa de la multicolinealitat, de passada

Si saps que un client no és de Barcelona, ni de Madrid, ni de Sevilla... ja saps que és de Valencia: l'última columna és redundant (sempre suma 1 amb les altres). Aquesta redundància, anomenada multicolinealitat, molesta alguns models lineals clàssics. El paràmetre drop="first" (o drop_first=True a get_dummies) elimina una columna per evitar-la. Queda't amb la idea i el nom del paràmetre; el perquè profund pertany a la regressió lineal (04-01) i a la regularització (07-01).

El problema de l'alta cardinalitat

One-hot escala malament. La columna ciutat amb 4 valors genera 4 columnes; però i el codi postal de lliurament de MercaFresh, amb 800 valors diferents? O l'id de producte amb 5.000?

  • 800 columnes noves, gairebé totes zeros: memòria i temps disparats.
  • Categories amb 2 o 3 exemples: el model no en pot aprendre res de fiable (matèria primera de l'overfitting, que formalitzarem a 06-05).

Estratègies davant de l'alta cardinalitat:

  1. Agrupar per coneixement de negoci: codis postals → província o zona de repartiment.
  2. Agrupar la cua rara: conservar les categories freqüents i fusionar la resta en "altres" (OneHotEncoder(min_frequency=...) ho fa sol).
  3. Canviar de codificació: frequency o target encoding, que generen una única columna sigui quina sigui la cardinalitat.
# Agrupar la cua: ciutats amb menys de 20 clients passen a "altres"
recompte = df_gran["ciutat"].value_counts()
rares = recompte[recompte < 20].index
df_gran["ciutat_agrupada"] = df_gran["ciutat"].replace(rares, "altres")

Frequency i target encoding

Dues tècniques que substitueixen la categoria per un sol nombre informatiu, pensades per a cardinalitat alta. Les presentem a nivell introductori: entén la idea i, sobretot, el risc.

Frequency encoding

Substitueix cada categoria per la seva freqüència (quantes vegades apareix, o la seva proporció):

freq = df_gran["ciutat"].value_counts(normalize=True)
df_gran["ciutat_freq"] = df_gran["ciutat"].map(freq)

Barcelona (40 % dels clients) es converteix en 0.40; un poble amb 2 clients, en 0.0002. És simple, no fa explotar la dimensionalitat i de vegades la freqüència en si és predictiva (les ciutats grans tenen millors temps de lliurament i menys churn). Limitació: dues categories amb la mateixa freqüència es tornen indistingibles.

Target encoding

Substitueix cada categoria per la mitjana de la variable objectiu en aquella categoria: cada ciutat es converteix en la seva pròpia taxa de churn.

# Idea conceptual (aixi NO s'ha de fer en un projecte real, mira el risc mes avall)
taxa_per_ciutat = df_gran.groupby("ciutat")["churn"].mean()
df_gran["ciutat_target"] = df_gran["ciutat"].map(taxa_per_ciutat)

És potentíssim —condensa exactament la relació categoria-objectiu en un nombre— i precisament per això és la tècnica amb més risc de fuita d'informació de tot el preprocessament: estem posant la resposta (el churn) dins d'una característica. Per a una ciutat amb 3 clients dels quals 2 van fer churn, la codificació 0.67 és pràcticament xivar al model l'etiqueta d'aquests clients. Símptomes i precaucions:

  • Avaluació d'entrenament sospitosament perfecta i rendiment pobre en dades noves.
  • Calcular les mitjanes només amb dades d'entrenament (el mateix principi de 03-02, aquí elevat a crític).
  • Fer servir suavitzat (barrejar la mitjana de la categoria amb la mitjana global, amb més pes a la global com menys dades tingui la categoria) i esquemes de validació creuada que veuràs a 06-03. El TargetEncoder de scikit-learn incorpora aquestes defenses.

Regla prudent per a aquest curs: one-hot com a opció per defecte; target encoding només quan la cardinalitat ho exigeixi i amb les seves salvaguardes posades.

Taula comparativa i decisió

Mètode Columnes generades Imposa ordre? Cardinalitat alta Risc principal Quan fer-lo servir
Ordinal encoding 1 Sí (el defineixes tu) Ordre mal definit Ordinals reals (pla, nivell)
Label encoding 1 Sí (alfabètic) Ordre fals Només la variable objectiu
One-hot 1 per categoria No Malament Explosió de columnes Nominals amb poques categories (per defecte)
Frequency 1 No Molt bé Col·lisions de freqüència Cardinalitat alta, solució ràpida
Target 1 No Molt bé Fuita d'informació Cardinalitat alta, amb suavitzat i cura
flowchart TD
    A{"La variable te<br/>ordre real?"} -->|Si| B["Ordinal encoding<br/>amb ordre explicit"]
    A -->|No| C{"Poques categories?<br/>(orientatiu: < 15)"}
    C -->|Si| D["One-hot encoding"]
    C -->|No| E{"Puc agrupar amb<br/>criteri de negoci?"}
    E -->|Si| F["Agrupar i one-hot"]
    E -->|No| G["Frequency o target encoding<br/>(amb salvaguardes)"]

Cas complet MercaFresh: ciutat, categoria i pla

Apliquem la decisió a les tres categòriques del dataset de churn:

import pandas as pd
from sklearn.compose import ColumnTransformer
from sklearn.preprocessing import OneHotEncoder, OrdinalEncoder

clients = pd.DataFrame({
    "id_client": [101, 104, 105, 106, 107, 108],
    "ciutat": ["Barcelona", "Barcelona", "Valencia", "Madrid", "Madrid", "Sevilla"],
    "categoria_favorita": ["frescos", "rebost", "frescos", "begudes", "frescos", "neteja"],
    "pla": ["basic", "premium", "estandard", "basic", "premium", "basic"],
    "despesa_total": [1250.5, 890.0, 2100.75, 310.2, 15400.0, 670.4],
})

# Decisions raonades:
# - ciutat: nominal, 4 valors             -> one-hot
# - categoria_favorita: nominal, 4 valors -> one-hot
# - pla: ordinal real                     -> ordinal amb ordre explicit
codificador = ColumnTransformer(transformers=[
    ("nominals", OneHotEncoder(sparse_output=False, handle_unknown="ignore"),
     ["ciutat", "categoria_favorita"]),
    ("ordinals", OrdinalEncoder(categories=[["basic", "estandard", "premium"]]),
     ["pla"]),
    ("numeriques", "passthrough", ["despesa_total"]),
])

X = codificador.fit_transform(clients)
print(codificador.get_feature_names_out())
print(pd.DataFrame(X, columns=codificador.get_feature_names_out()).round(2))

El resultat és una matriu totalment numèrica: 4 columnes de ciutat + 4 de categoria + 1 de pla + la despesa. Observa com el ColumnTransformer presentat a 03-03 ha absorbit la codificació com una branca més: el preprocessament continua sent un únic objecte amb fit i transform. Fixa't en la neteja de la ciutat a 03-01: si no haguéssim unificat "BCN" i " barcelona ", el one-hot hauria creat columnes fantasma per a cada variant.

Errors Comuns i Consells

  • Enters arbitraris sobre nominals. Ciutat = 1, 2, 3 inventa ordre i distàncies. Si no pots dir quina és "més gran", no facis servir ordinal.
  • Deixar que OrdinalEncoder ordeni alfabèticament un ordinal real. Passa sempre categories=[...] amb l'ordre de negoci.
  • Fer servir get_dummies en producció. Sense memòria de categories, cada lot pot generar columnes diferents. En fluxos de ML, OneHotEncoder amb handle_unknown="ignore".
  • One-hot sobre 800 codis postals. Explosió de columnes i categories sense dades: agrupa primer o canvia de tècnica.
  • Target encoding calculat amb tot el dataset. És la fuita d'informació en la seva forma més pura: les mitjanes per categoria s'aprenen només d'entrenament, amb suavitzat.
  • Codificar abans de netejar el text. "Madrid" i "madrid " generen dues columnes. La normalització de 03-01 va sempre abans que la codificació.
  • Consell: després de codificar, imprimeix get_feature_names_out() i revisa que el nombre i els noms de columnes són els que esperaves. Els errors de codificació són silenciosos; aquesta comprovació de 5 segons els caça gairebé tots.

Exercicis

Exercici 1

Classifica cada variable de MercaFresh com a nominal o ordinal i indica el mètode de codificació adequat: (a) mètode de pagament (targeta, PayPal, contra reemborsament); (b) valoració de l'últim lliurament (dolenta, regular, bona, excel·lent); (c) barri de lliurament a Barcelona (73 barris); (d) tipus de client (particular, empresa).

Exercici 2

Codifica amb OrdinalEncoder la columna valoracio = ["bona", "dolenta", "excel·lent", "regular", "bona"] respectant l'ordre real. Mostra quina codificació (incorrecta) hauria produït l'ordre alfabètic per defecte.

Exercici 3

Aplica frequency encoding a la columna barri = ["Gracia", "Eixample", "Gracia", "Sants", "Eixample", "Gracia", "Raval", "Eixample"] fent servir proporcions. Quins dos barris queden indistingibles després de la codificació i per què és una limitació del mètode?

Solucions

Exercici 1

  • (a) Nominal (cap mètode de pagament no és "més gran") → one-hot (3 categories, sense problema).
  • (b) Ordinal real (dolenta < regular < bona < excel·lent) → ordinal encoding amb ordre explícit.
  • (c) Nominal d'alta cardinalitat (73 valors) → agrupar per districte si el negoci ho permet; si no, frequency o target encoding amb salvaguardes.
  • (d) Nominal binària → one-hot; amb dues categories n'hi ha prou amb una sola columna 0/1 (és exactament el cas on drop="first" resulta natural).

Exercici 2

from sklearn.preprocessing import OrdinalEncoder
import pandas as pd

df = pd.DataFrame({"valoracio": ["bona", "dolenta", "excel·lent", "regular", "bona"]})

# Correcte: ordre de negoci explicit
enc = OrdinalEncoder(categories=[["dolenta", "regular", "bona", "excel·lent"]])
print(enc.fit_transform(df))       # bona=2, dolenta=0, excel·lent=3, regular=1

# Incorrecte: ordre alfabetic per defecte
enc_malament = OrdinalEncoder()
print(enc_malament.fit_transform(df))   # bona=0, dolenta=1, excel·lent=2, regular=3

Amb l'ordre alfabètic, "regular" (3) quedaria per sobre d'"excel·lent" (2) i "bona" (0) per sota de "dolenta" (1): el model aprendria que valoracions pitjors són "més grans" que les millors.

Exercici 3

s = pd.Series(["Gracia", "Eixample", "Gracia", "Sants",
               "Eixample", "Gracia", "Raval", "Eixample"])
freq = s.value_counts(normalize=True)
print(s.map(freq))
# Gracia -> 0.375, Eixample -> 0.375, Sants -> 0.125, Raval -> 0.125

Gracia i Eixample col·lideixen en 0.375, i Sants i Raval en 0.125: barris diferents es tornen idèntics per al model. El frequency encoding només conserva "com de comuna és la categoria", no la seva identitat — el preu de comprimir-ho tot en un nombre.

Conclusió

Ja saps traduir categories a nombres sense mentir al model: ordinal encoding amb ordre explícit per als ordinals reals, one-hot com a opció per defecte per als nominals (amb OneHotEncoder i handle_unknown="ignore" en fluxos seriosos), estratègies d'agrupació o frequency/target encoding per a l'alta cardinalitat —amb el target encoding senyalitzat com la zona de més risc de fuita—, i tot integrat en el ColumnTransformer que anem construint. La matriu del churn de MercaFresh ja és completament numèrica.

Però fixa-t'hi: les columnes one-hot valen 0 o 1, el pla va de 0 a 2... i la despesa total arriba a 15.400. Per als algorismes que mesuren distàncies o descendeixen gradients, aquesta disparitat d'escales fa que una sola columna ofegui totes les altres. Aquest és exactament el problema de la propera lliçó: normalització i estandardització.

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