A la lliçó anterior vam acabar amb uns quants NaN sembrats a propòsit: vam convertir edats impossibles i dates futures en valors absents perquè era més honest que inventar-nos un nombre. Ara toca decidir què fem amb aquests forats, i amb tots els que ja venien d'origen en les dades de MercaFresh. Les dades mancants són inevitables en qualsevol projecte real, i la manera de tractar-les pot canviar del tot les conclusions d'un model: eliminar files pot esbiaixar la mostra, imputar malament pot fabricar patrons falsos, i fer-ho en el moment equivocat pot filtrar informació que el model no hauria de tenir. En aquesta lliçó aprendràs a diagnosticar per què falten les dades, a visualitzar-ne els patrons i a triar entre eliminar i imputar amb criteri.
Contingut
- Per què falten dades: MCAR, MAR i MNAR
- Detecció i visualització de nuls
- Eliminació: files i columnes
- Imputació simple: mitjana, mediana, moda i constants
SimpleImputerde scikit-learn- Imputació avançada: KNNImputer i indicadors d'absència
- El risc silenciós: fuita d'informació
Per què falten dades: MCAR, MAR i MNAR
Abans d'omplir un forat cal preguntar-se per què és buit. L'estadística distingeix tres mecanismes d'absència, i la diferència no és acadèmica: determina quines estratègies són vàlides.
MCAR: Missing Completely At Random
L'absència no depèn de res: és pur atzar. A MercaFresh, una fallada intermitent del servidor va perdre el camp temps_lliurament d'un 2 % de comandes triades a l'atzar. Cap tipus de comanda no tenia més probabilitat de perdre'l que un altre.
- Conseqüència: les dades que queden continuen sent representatives. Eliminar aquestes files no esbiaixa; només redueix la mostra.
MAR: Missing At Random (condicionat a allò observat)
L'absència depèn d'altres columnes que sí que tenim. A MercaFresh, l'edat falta molt més entre els clients que es van registrar per l'app mòbil (on el camp era opcional) que entre els del formulari web (on era obligatori). Sabent el canal de registre, l'absència ja no aporta informació extra.
- Conseqüència: eliminar files esbiaixa la mostra (perdríem usuaris d'app, que potser tenen un altre perfil de churn), però es pot imputar bé fent servir les columnes relacionades.
MNAR: Missing Not At Random
L'absència depèn del mateix valor que falta. Exemple clàssic a MercaFresh: la valoració post-lliurament (satisfaccio) falta sobretot entre els clients descontents, que no es molesten a respondre l'enquesta. El forat és informació: qui no respon tendeix a estar insatisfet.
- Conseqüència: és el cas més perillós. Imputar la mitjana fabricaria clients "mitjanament satisfets" que en realitat estaven emprenyats — justament els que faran churn. Aquí els indicadors d'absència (els veurem més avall) són gairebé obligatoris.
| Mecanisme | De què depèn l'absència? | Exemple MercaFresh | Estratègia raonable |
|---|---|---|---|
| MCAR | De res (atzar pur) | Fallada aleatòria del servidor | Eliminar o imputar: totes dues segures |
| MAR | D'altres columnes observades | L'edat falta segons el canal de registre | Imputar fent servir aquestes columnes |
| MNAR | Del mateix valor absent | No responen els insatisfets | Indicador d'absència + cautela |
A la pràctica no hi ha cap test definitiu per distingir-los: es raona amb coneixement del negoci. La pregunta útil és sempre: qui o què va decidir no omplir aquest camp, i té això relació amb el que vull predir?
Detecció i visualització de nuls
Reconstruïm un extracte de clients de MercaFresh, ja net de duplicats i valors impossibles (herència de 03-01), però amb els seus nuls:
import pandas as pd
import numpy as np
df = pd.DataFrame({
"id_client": [101, 104, 105, 106, 107, 108, 109, 110, 111, 112],
"edat": [34, np.nan, 41, 38, np.nan, 45, 31, 27, np.nan, 52],
"canal_registre": ["web", "app", "web", "app", "app", "web", "app", "web", "app", "web"],
"despesa_total": [1250.5, 0.0, 2100.75, 310.2, 15400.0, 670.4, 0.0, 980.1, 45.9, 1530.0],
"satisfaccio": [4.5, np.nan, 4.8, np.nan, 4.2, 3.9, np.nan, 4.6, np.nan, 4.1],
"temps_lliurament_min": [42, 55, np.nan, 61, 38, 47, 52, np.nan, 66, 44],
})El recompte bàsic:
# Nuls per columna, en absolut i en percentatge
print(df.isna().sum())
print((df.isna().mean() * 100).round(1))
# edat 30.0 %
# satisfaccio 40.0 %
# temps_lliurament_min 20.0 %isna() retorna un DataFrame de booleans (True on hi ha nul); sum() els compta i mean() dona directament la proporció. Però els totals no ho expliquen tot: importa el patró. Falten sempre a les mateixes files? Falta satisfaccio en els mateixos clients que edat?
# Quantes columnes falten per fila?
print(df.isna().sum(axis=1).value_counts())
# Coincideixen els nuls de dues columnes?
print(pd.crosstab(df["edat"].isna(), df["satisfaccio"].isna()))
# Pista sobre el mecanisme: l'absencia d'edat depen del canal?
print(df.groupby("canal_registre")["edat"].apply(lambda s: s.isna().mean()))
# app 0.6 <- falta el 60 % a l'app i el 0 % al web: fa olor de MAR!
# web 0.0Aquest darrer groupby és l'eina detectiu: creua l'absència amb altres columnes per intuir el mecanisme. Per a datasets grans, un mapa visual ajuda:
import matplotlib.pyplot as plt
plt.imshow(df.isna(), aspect="auto", cmap="gray_r")
plt.xticks(range(len(df.columns)), df.columns, rotation=45)
plt.ylabel("Fila")
plt.title("Mapa de nuls (negre = absent)")
plt.tight_layout()
plt.show()Cada cel·la negra és un forat: bandes verticals indiquen columnes problemàtiques; patrons alineats entre columnes, absències correlacionades. (La llibreria missingno automatitza aquests gràfics, però amb imshow en tens prou amb les eines que ja tens.)
Eliminació: files i columnes
L'opció més simple és esborrar. dropna la implementa amb diversos matisos:
# Eliminar tota fila amb ALMENYS un nul (agressiu: aqui perdriem 7 de 10)
df_sense_nuls = df.dropna()
# Eliminar nomes files on falti una columna critica concreta
df_edat = df.dropna(subset=["edat"])
# Eliminar files amb mes de 2 nuls (thresh = minim de valors NO nuls exigit)
df_thresh = df.dropna(thresh=len(df.columns) - 2)
# Eliminar una COLUMNA sencera
df_sense_satisf = df.drop(columns=["satisfaccio"])Quan és acceptable eliminar?
- Files: quan els nuls són pocs (regla orientativa: < 5 % de les files) i el mecanisme és MCAR. Si és MAR o MNAR, eliminar files distorsiona la mostra: en el nostre exemple, esborrar les files sense
edateliminaria sobretot clients d'app. - Columnes: quan en falta una proporció enorme (> 50-60 %) i la columna no és crítica per al negoci. Compte amb l'excepció MNAR:
satisfacciofalta un 40 %, però que falti pot predir churn — abans de llençar-la, considera conservar-ne l'indicador d'absència. - Mai no eliminis la fila si el nul és a la variable objectiu d'una dada de producció que vols predir; només en entrenament té sentit descartar files sense etiqueta.
Imputació simple: mitjana, mediana, moda i constants
Imputar és omplir el forat amb un valor estimat. Les receptes bàsiques amb pandas:
# Mitjana: per a numeriques simetriques
df["edat"] = df["edat"].fillna(df["edat"].mean())
# Mediana: per a numeriques amb outliers o asimetria (recorda 02-01!)
df["temps_lliurament_min"] = df["temps_lliurament_min"].fillna(
df["temps_lliurament_min"].median())
# Moda: per a categoriques ([0] perque mode() pot retornar diversos empats)
df["canal_registre"] = df["canal_registre"].fillna(
df["canal_registre"].mode()[0])
# Constant amb significat de negoci
df["satisfaccio"] = df["satisfaccio"].fillna(-1) # -1 = "no va respondre"Mitjana o mediana? El criteri ve directe del mòdul 2: la mitjana és sensible als outliers, la mediana no. Amb el client-restaurant de 15.400 € al dataset, imputar despesa_total amb la mitjana inflaria tots els forats; la mediana és la tria robusta per defecte.
Una imputació per grups aprofita l'estructura MAR: si l'edat falta segons el canal, imputa amb la mediana de cada canal:
Cost ocult de tota imputació simple: redueix artificialment la variància. Cada forat omplert amb la mediana és un punt clavat al centre de la distribució; si imputes el 30 % d'una columna, el seu histograma tindrà un pic artificial i les seves correlacions (02-03) s'afebliran. Imputar molt no surt gratis.
SimpleImputer de scikit-learn
Per què fer servir scikit-learn si fillna ja funciona? Perquè SimpleImputer memoritza el valor amb què imputa (l'aprèn a fit) i el reaplica idèntic a dades noves (transform). Aquesta separació fit/transform és la clau de l'apartat final d'aquesta lliçó, i el patró general de tota la llibreria.
from sklearn.impute import SimpleImputer
# Imputador per a columnes numeriques, amb estrategia mediana
imp_num = SimpleImputer(strategy="median")
cols_num = ["edat", "temps_lliurament_min"]
df[cols_num] = imp_num.fit_transform(df[cols_num])
# El valor apres queda guardat, a punt per aplicar-se a dades futures:
print(imp_num.statistics_) # [37.0, 49.5] p. ex.: medianes de cada columnaEstratègies disponibles: "mean", "median", "most_frequent" (serveix per a categòriques) i "constant" (amb fill_value=). El mateix objecte, ja ajustat, s'aplica després amb imp_num.transform(dades_noves) — sense recalcular res.
Imputació avançada: KNNImputer i indicadors d'absència
KNNImputer
La imputació simple ignora el context de la fila: a un client jove de despesa alta i a un jubilat de compra ocasional els assigna la mateixa edat mediana. KNNImputer és més fi: per a cada forat busca les k files més semblants en les altres columnes (els seus "veïns") i en fa la mitjana dels valors.
from sklearn.impute import KNNImputer
imp_knn = KNNImputer(n_neighbors=3)
df[["edat", "despesa_total", "temps_lliurament_min"]] = imp_knn.fit_transform(
df[["edat", "despesa_total", "temps_lliurament_min"]])La mecànica interna de "veïns més propers" és exactament l'algorisme K-NN que estudiarem a fons a la lliçó 04-05; de moment n'hi ha prou amb la intuïció: omple cada forat copiant de les files que més s'hi assemblen. Dues advertències pràctiques: només treballa amb columnes numèriques, i com que mesura distàncies, les columnes amb escales grans dominen el càlcul — un problema que resoldrem a la lliçó 03-05.
Indicadors d'absència
Quan l'absència és informativa (MNAR), imputar i callar destrueix el senyal. La solució: afegir una columna booleana que recordi on hi havia un forat, i després imputar tranquil·lament.
# 1) Guardar l'empremta de l'absencia ABANS d'imputar
df["satisfaccio_absent"] = df["satisfaccio"].isna().astype(int)
# 2) Ara si, imputar la columna original
df["satisfaccio"] = df["satisfaccio"].fillna(df["satisfaccio"].median())Així el model de churn pot aprendre el patró real: "els clients que no responen l'enquesta es donen de baixa més". SimpleImputer(add_indicator=True) genera aquests indicadors automàticament. És una tècnica barata i sorprenentment potent: en molts datasets, l'indicador acaba sent millor predictor que la columna imputada.
El risc silenciós: fuita d'informació
Queda l'advertència més important de la lliçó. Al mòdul 6 (lliçó 06-01) veurem que les dades es divideixen en un conjunt d'entrenament i un altre de prova, i que aquest últim ha de simular dades del futur que el model no ha vist mai. Doncs bé: si calcules la mediana per imputar fent servir tot el dataset i després divideixes, les dades de prova hauran influït en aquell valor. El model haurà "espiat" el futur. És el que s'anomena fuita d'informació (data leakage).
# MALAMENT: la mediana es calcula amb TOTES les dades, incloses les de prova
df["edat"] = df["edat"].fillna(df["edat"].median())
# ... i despres es divideix en train/test
# BE (esquema conceptual, es desenvolupa a 06-01):
# 1. dividir primer en train i test
# 2. imp = SimpleImputer(strategy="median"); imp.fit(X_train) <- apren NOMES de train
# 3. X_train = imp.transform(X_train)
# 4. X_test = imp.transform(X_test) <- aplica el valor apres a trainAquí és on la parella fit/transform de scikit-learn deixa de ser una mania estilística i es converteix en la barrera contra la fuita: s'aprèn de train, s'aplica a tot. L'efecte de fer-ho malament és traïdor perquè no dona cap error: senzillament la teva avaluació sortirà millor del que el model mereix, i el desengany arribarà en producció. Guarda aquesta idea: reapareixerà amb l'escalat (03-05), amb el target encoding (03-04) i en general amb qualsevol pas que "aprengui" alguna cosa de les dades.
Errors Comuns i Consells
- Imputar sense preguntar-se el mecanisme. Omplir
satisfaccio(MNAR) amb la mitjana fabrica clients contents que no existeixen. Primer diagnostica (MCAR/MAR/MNAR), després actua. - Fer servir la mitjana en columnes amb outliers. Amb el client de 15.400 € a dins, la mitjana de despesa no representa ningú. Mediana per defecte; mitjana només en distribucions raonablement simètriques.
- Eliminar files alegrement. Un
dropna()sense més pot buidar-te mig dataset i esbiaixar la resta. Mira sempre quantes files perds i qui són. - Imputar abans de dividir train/test. La fuita d'informació no avisa: infla les teves mètriques en silenci. Aprèn els valors d'imputació només amb dades d'entrenament.
- Oblidar l'indicador d'absència. Si sospites MNAR, crea la columna
_absentabans d'imputar; després ja no la podràs reconstruir. - Confondre nuls amb sentinelles. Un
999o un-1heretats del sistema origen no sónNaNper a pandas: converteix-los primer (df.replace(999, np.nan)) o els teus recomptes de nuls mentiran. - Consell: deixa registrat al codi quants nuls hi havia, quina estratègia vas aplicar a cada columna i per què. La imputació és una decisió de modelatge, no una feina de lampisteria.
Exercicis
Exercici 1
Per a cada escenari de MercaFresh, classifica el mecanisme (MCAR, MAR o MNAR) i justifica-ho en una frase: (a) el pes de la comanda falta als registres d'una setmana en què la bàscula del magatzem va estar espatllada; (b) el codi postal falta més en comandes fetes per telèfon, perquè l'operador de vegades l'omet; (c) els ingressos declarats falten sobretot entre els clients de rendes altes, que prefereixen no dir-los.
Exercici 2
Amb el DataFrame df de la lliçó (versió amb nuls), imputa temps_lliurament_min amb la mediana per ciutat sabent que existeix una columna ciutat. Escriu el codi amb groupby + transform i explica per què pot ser millor que la mediana global.
Exercici 3
Crea un SimpleImputer amb estratègia mediana i add_indicator=True, ajusta'l sobre les columnes ["edat", "satisfaccio"] i mostra la forma (shape) del resultat. Explica per què surten més columnes de les que van entrar.
Solucions
Exercici 1
- (a) MCAR: l'avaria no té relació amb el tipus de comanda ni amb el valor del pes; és atzar (temporal) pur.
- (b) MAR: l'absència depèn del canal (columna observada), no del valor del mateix codi postal; coneixent el canal, es pot imputar sense biaix.
- (c) MNAR: l'absència depèn del valor que falta (les rendes altes s'amaguen més); imputar la mitjana subestimaria els ingressos absents, i convé un indicador d'absència.
Exercici 2
df["temps_lliurament_min"] = df["temps_lliurament_min"].fillna(
df.groupby("ciutat")["temps_lliurament_min"].transform("median"))transform("median") retorna una Serie de la mateixa mida que df amb la mediana del grup de cada fila, així que fillna omple cada forat amb la mediana de la seva ciutat. És millor que la global si el temps de lliurament varia sistemàticament per ciutat (repartir a Barcelona ≠ repartir a Sevilla): estem explotant estructura MAR, igual que amb l'edat per canal.
Exercici 3
from sklearn.impute import SimpleImputer
imp = SimpleImputer(strategy="median", add_indicator=True)
resultat = imp.fit_transform(df[["edat", "satisfaccio"]])
print(resultat.shape) # (10, 4)Van entrar 2 columnes i en surten 4: les dues originals imputades més un indicador binari per cada columna que tenia nuls (1 = el valor era absent). Així es conserva la informació d'absència, essencial si el mecanisme és MNAR com sospitem de satisfaccio.
Conclusió
Ja saps tractar els forats amb criteri: diagnosticar el mecanisme (MCAR, MAR, MNAR) abans d'actuar, quantificar i visualitzar els patrons de nuls, eliminar només quan és segur, imputar amb mitjana/mediana/moda o per grups, escalar a SimpleImputer i KNNImputer quan cal, conservar el senyal de l'absència amb indicadors, i —sobretot— aprendre els valors d'imputació únicament de les dades d'entrenament per no filtrar informació. El dataset de churn de MercaFresh ja està complet: sense duplicats, sense valors impossibles i sense forats.
Però complet no vol dir a punt: diverses de les seves variables tenen distribucions molt asimètriques (la despesa, amb la seva llarga cua de clients-restaurant), les dates continuen sent poc útils tal com estan, i les comandes encara no estan resumides per client. A la propera lliçó, transformació de dades, remodelarem aquestes variables —logaritmes, discretització, components de data, agregacions— i coneixerem els pipelines de scikit-learn per encadenar tots aquests passos de manera ordenada.
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
