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
- Per què els models necessiten nombres
- Nominal vs ordinal: la distinció que ho governa tot
- Label i ordinal encoding
- One-hot encoding
- El problema de l'alta cardinalitat
- Frequency i target encoding
- Taula comparativa i decisió
- 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:
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=Falseretorna una matriu normal; amb moltes columnes convé deixar la sortida dispersa (sparse) per estalviar memòria.- Encaixa directament com la branca categòrica del
ColumnTransformerde 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:
- Agrupar per coneixement de negoci: codis postals → província o zona de repartiment.
- Agrupar la cua rara: conservar les categories freqüents i fusionar la resta en
"altres"(OneHotEncoder(min_frequency=...)ho fa sol). - 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
TargetEncoderde 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) | Bé | 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
OrdinalEncoderordeni alfabèticament un ordinal real. Passa semprecategories=[...]amb l'ordre de negoci. - Fer servir
get_dummiesen producció. Sense memòria de categories, cada lot pot generar columnes diferents. En fluxos de ML,OneHotEncoderambhandle_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=3Amb 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.125Gracia 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
- Què és el Machine Learning?
- Història i evolució del Machine Learning
- Tipus de Machine Learning
- Aplicacions del Machine Learning
- El flux de treball d'un projecte de Machine Learning
Mòdul 2: Fonaments d'Estadística i Probabilitat
- Conceptes bàsics d'estadística
- Distribucions de probabilitat
- Correlació i covariància
- Inferència estadística
- Teorema de Bayes
Mòdul 3: Preprocessament de Dades
- Neteja de dades
- Gestió de dades mancants
- Transformació de dades
- Codificació de variables categòriques
- Normalització i estandardització
- Enginyeria de característiques
Mòdul 4: Algorismes de Machine Learning Supervisat
- Regressió lineal
- Regressió logística
- Arbres de decisió
- Màquines de suport vectorial (SVM)
- K veïns més propers (K-NN)
- Naive Bayes
- Xarxes neuronals
Mòdul 5: Algorismes de Machine Learning No Supervisat
- Clustering: K-means
- Clustering jeràrquic
- Anàlisi de components principals (PCA)
- Anàlisi d'agrupament DBSCAN
- Visualització de dades amb t-SNE i UMAP
Mòdul 6: Avaluació i Validació de Models
- Divisió de dades: entrenament, validació i prova
- Mètriques d'avaluació
- Validació creuada
- Corba ROC i AUC
- Overfitting i underfitting
Mòdul 7: Tècniques Avançades i Optimització
- Regularització: Ridge, Lasso i Elastic Net
- Ensemble Learning
- Gradient Boosting
- Xarxes neuronals profundes (Deep Learning)
- Optimització d'hiperparàmetres
Mòdul 8: Implementació i Desplegament de Models
- Frameworks i biblioteques populars
- Implementació de models en producció
- Manteniment i monitoratge de models
- Consideracions ètiques i de privadesa
Mòdul 9: Projectes Pràctics
- Projecte 1: Predicció de preus d'habitatges
- Projecte 2: Classificació d'imatges
- Projecte 3: Anàlisi de sentiments a les xarxes socials
- Projecte 4: Detecció de fraus
- Projecte 5: Segmentació de clients
