Al final de la lección anterior nos quedó una matriz totalmente numérica pero con escalas dispares: columnas one-hot que valen 0 o 1 conviviendo con gastos de hasta 15.400 €. Para muchos algoritmos, esa disparidad es un problema serio: una columna con números grandes domina los cálculos y las demás se vuelven invisibles, no porque importen menos, sino por puro accidente de unidades. Normalizar y estandarizar significa poner todas las variables en escalas comparables. En esta lección entenderás por qué la escala importa (y para qué algoritmos), dominarás los tres escaladores fundamentales de scikit-learn —MinMaxScaler, StandardScaler y RobustScaler—, verás su efecto antes y después, y aprenderás la regla de oro que evita fugas: ajustar el escalador solo con datos de entrenamiento.
Contenido
- Por qué la escala importa
- Qué algoritmos lo necesitan y cuáles no
- Min-Max scaling: llevar todo a [0, 1]
- Estandarización z-score:
StandardScaler RobustScaler: escalar con outliers en casa- Comparación visual antes y después
- Qué escalador usar: tabla de decisión
- Fit en train, transform en test
Por qué la escala importa
Tomemos dos clientes de MercaFresh descritos por dos variables: gasto total en euros y antigüedad en años.
- Cliente A: 1.200 € de gasto, 1 año de antigüedad.
- Cliente B: 1.150 € de gasto, 9 años de antigüedad.
Para un algoritmo basado en distancias (como el K-NN que veremos en 04-05), la "diferencia" entre ambos es:
import numpy as np
A = np.array([1200, 1])
B = np.array([1150, 9])
print(np.sqrt(((A - B) ** 2).sum())) # 50.64La distancia sale 50,64... de los cuales 50 vienen del gasto y solo 0,64 de la antigüedad. Los 8 años de diferencia —enormes en términos de fidelidad del cliente— quedan aplastados por 50 € de diferencia de gasto, que es calderilla. La antigüedad podría eliminarse del dataset y el resultado apenas cambiaría: la unidad de medida, no la importancia real, está decidiendo qué variable manda.
El mismo problema aparece, con otra mecánica, en los algoritmos que aprenden por gradiente (regresión logística 04-02, SVM 04-04, redes neuronales 04-07): cuando las variables tienen escalas muy distintas, la superficie de error se vuelve un valle alargado y estrecho, y el proceso de optimización zigzaguea lentamente en lugar de bajar recto. Escalar redondea el valle y acelera —y estabiliza— el entrenamiento. Los detalles de cada algoritmo pertenecen al módulo 4; aquí basta el mensaje: distancias y gradientes exigen escalas comparables.
Qué algoritmos lo necesitan y cuáles no
| Familia | ¿Sensible a la escala? | Por qué |
|---|---|---|
| K-NN (04-05), K-means (05-01) | Sí, crítico | Calculan distancias entre filas |
| SVM (04-04) | Sí, crítico | Distancias al hiperplano + optimización |
| Regresión lineal/logística con gradiente (04-01, 04-02) | Sí | Convergencia del gradiente; comparabilidad de coeficientes |
| Redes neuronales (04-07) | Sí | Gradientes estables |
| PCA (05-03) | Sí, crítico | La varianza grande domina las componentes |
| Árboles de decisión (04-03), Random Forest, Gradient Boosting (07-02/03) | No | Solo hacen cortes tipo "¿gasto > 500?"; el corte es el mismo en cualquier escala |
| Naive Bayes (04-06) | En general no | Trabaja con probabilidades por variable |
Esta tabla te acompañará todo el curso: antes de entrenar cualquier modelo, pregúntate en qué fila cae. Y un recordatorio de la lección 03-02: el KNNImputer también mide distancias, así que también agradece datos escalados.
Min-Max scaling: llevar todo a [0, 1]
La normalización Min-Max transforma cada variable para que su mínimo sea 0 y su máximo 1:
import pandas as pd
from sklearn.preprocessing import MinMaxScaler
clientes = pd.DataFrame({
"gasto_total": [1250.5, 890.0, 2100.75, 310.2, 15400.0, 670.4, 980.1, 45.9],
"antiguedad_anios": [3, 1, 6, 2, 9, 4, 1, 5],
"num_pedidos": [18, 12, 25, 4, 210, 9, 14, 6],
})
mms = MinMaxScaler()
escalado = pd.DataFrame(mms.fit_transform(clientes), columns=clientes.columns)
print(escalado.round(3))
print(mms.data_min_, mms.data_max_) # lo aprendido en fitInterpretación directa: 0 es "el cliente que menos", 1 "el que más", 0,5 "a mitad de camino". Ventajas e inconvenientes:
- A favor: rango acotado y predecible ([0, 1]), interpretación intuitiva, encaja donde se exige un rango fijo (por ejemplo, algunas redes neuronales o imágenes con píxeles 0-255).
- En contra: extremadamente sensible a outliers. Mira la columna
gasto_total: el cliente de 15.400 € se lleva el 1, y todos los demás quedan comprimidos entre 0,00 y 0,13. El outlier ha secuestrado la escala y el resto de clientes son casi indistinguibles entre sí. - Además, un dato futuro mayor que el máximo de entrenamiento saldrá > 1: el rango [0, 1] solo está garantizado para los datos con los que se hizo
fit.
Estandarización z-score: StandardScaler
La estandarización transforma cada variable restando su media y dividiendo por su desviación estándar:
¿Te suena? Es exactamente el z-score de la lección 02-02: cada valor pasa a expresarse en "número de desviaciones estándar respecto a la media". Un cliente con z = +2 en gasto está dos desviaciones por encima del gasto medio; uno con z = 0 es exactamente promedio. Lo que allí usábamos como detector de rarezas, aquí se convierte en un idioma común para todas las variables: tras estandarizar, todas tienen media 0 y desviación 1, y "estar 2 desviaciones por encima" significa lo mismo en euros que en años.
from sklearn.preprocessing import StandardScaler
ss = StandardScaler()
estandarizado = pd.DataFrame(ss.fit_transform(clientes), columns=clientes.columns)
print(estandarizado.round(2))
print(estandarizado.mean().round(2)) # ~0 en todas las columnas
print(estandarizado.std().round(2)) # ~1 en todas las columnas
print(ss.mean_, ss.scale_) # media y desviación aprendidas en fitPropiedades:
- No acota el rango: los valores típicos caen entre -3 y +3 (la regla 68-95-99.7 de 02-02), pero un outlier puede salir a z = 6 sin problema.
- No cambia la forma de la distribución: si el gasto era asimétrico, el gasto estandarizado sigue siendo asimétrico. Escalar no es transformar (eso fue 03-03); son pasos complementarios: primero
log1pcorrige la forma, despuésStandardScalerajusta la escala. - Es el escalador por defecto en la práctica: robusto "suficiente" para la mayoría de casos y el que asumen muchos algoritmos.
- Sigue usando media y desviación, así que los outliers lo perturban (menos que a Min-Max, pero lo hacen): con el cliente de 15.400 € dentro, la media y la desviación del gasto están infladas.
RobustScaler: escalar con outliers en casa
Cuando los outliers son parte legítima del dataset —y en MercaFresh lo son: los clientes-restaurante existen y no queremos eliminarlos—, conviene escalar con estadísticos que los ignoren. RobustScaler usa la mediana y el IQR (el rango intercuartílico de 02-01) en lugar de la media y la desviación:
from sklearn.preprocessing import RobustScaler
rs = RobustScaler()
robusto = pd.DataFrame(rs.fit_transform(clientes), columns=clientes.columns)
print(robusto.round(2))
print(rs.center_, rs.scale_) # mediana e IQR aprendidos en fitComo mediana e IQR apenas se mueven aunque haya valores extremos (lo viste en 02-01: son estadísticos robustos), el grueso de los clientes queda bien repartido y el outlier simplemente sale con un valor grande, sin comprimir a los demás. Es la elección natural cuando la limpieza de 03-01 concluyó "estos outliers son reales y se quedan".
Comparación visual antes y después
Nada convence más que verlo. Comparemos los tres escaladores sobre el gasto, con su outlier incluido:
import matplotlib.pyplot as plt
gasto = clientes[["gasto_total"]]
versiones = {
"Original (EUR)": gasto.values.ravel(),
"MinMaxScaler": MinMaxScaler().fit_transform(gasto).ravel(),
"StandardScaler": StandardScaler().fit_transform(gasto).ravel(),
"RobustScaler": RobustScaler().fit_transform(gasto).ravel(),
}
fig, axes = plt.subplots(1, 4, figsize=(14, 3))
for ax, (titulo, valores) in zip(axes, versiones.items()):
ax.boxplot(valores, vert=True)
ax.set_title(titulo, fontsize=10)
plt.tight_layout()
plt.show()Lo que muestran los cuatro boxplots (los de 02-01, trabajando de nuevo):
- Original: caja diminuta abajo, outlier lejanísimo arriba. Escala en miles.
- MinMaxScaler: misma silueta exacta, pero comprimida en [0, 1]: la caja entera ocupa un 13 % del rango. El outlier manda.
- StandardScaler: misma silueta, centrada en 0; el outlier sale a z ≈ 2,6 y el resto queda apiñado en una franja estrecha por debajo de la media.
- RobustScaler: la caja central queda bien desplegada alrededor de 0 (de -0,5 a 0,5 aproximadamente, porque el IQR pasa a valer 1) y el outlier se va lejos sin molestar a nadie.
Conclusión visual: escalar nunca cambia la forma de la distribución, solo su regla de medir; lo que distingue a los escaladores es qué tramo de los datos usan como referencia para esa regla.
Qué escalador usar: tabla de decisión
| Situación | Escalador recomendado | Motivo |
|---|---|---|
| Caso general, sin outliers graves | StandardScaler |
Estándar de facto; lo asumen muchos algoritmos |
| Outliers legítimos presentes | RobustScaler |
Mediana e IQR no se dejan arrastrar |
| Se necesita rango acotado [0, 1] | MinMaxScaler |
Único que lo garantiza (en train) |
| Variable ya binaria (one-hot) | Ninguno | Ya está en {0, 1}; escalarla solo resta interpretabilidad |
| Modelo de árboles (04-03, 07-02/03) | Ninguno necesario | Los cortes son invariantes a la escala |
| Distribución muy asimétrica | Transformar primero (03-03), escalar después | Escalar no corrige la forma |
Y la conexión con el flujo que venimos montando: el escalador es un paso más del Pipeline de la rama numérica del ColumnTransformer — imputación → transformación → escalado, en ese orden, mientras las columnas one-hot pasan de largo.
from sklearn.pipeline import Pipeline
from sklearn.impute import SimpleImputer
rama_numerica = Pipeline(steps=[
("imputar", SimpleImputer(strategy="median")),
("escalar", RobustScaler()),
])Fit en train, transform en test
La regla que cierra la lección es la misma que apareció en 03-02 con la imputación, ahora aplicada al escalado — y con los escaladores es donde más se peca. Todo escalador aprende parámetros en fit: mínimos y máximos, medias y desviaciones, medianas e IQRs. Si esos parámetros se calculan con el dataset completo antes de dividir en entrenamiento y prueba (la división que formalizaremos en 06-01), los datos de prueba habrán dejado su huella en la media o el máximo: fuga de información. La evaluación saldrá un poco mejor de lo que merece, y ni un error ni un aviso te alertará.
El protocolo correcto:
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
# 1) Dividir PRIMERO (los detalles, en 06-01)
X_train, X_test = train_test_split(clientes, test_size=0.25, random_state=42)
# 2) Ajustar el escalador SOLO con entrenamiento
scaler = StandardScaler()
scaler.fit(X_train)
# 3) Transformar ambos conjuntos con LO APRENDIDO EN TRAIN
X_train_esc = scaler.transform(X_train)
X_test_esc = scaler.transform(X_test)
# Atajo equivalente para train: scaler.fit_transform(X_train)Consecuencia que sorprende la primera vez: el conjunto de prueba escalado no tendrá media exactamente 0 ni desviación exactamente 1 — se ha medido con la regla de train, no con la suya propia. Y así debe ser: en producción, cada cliente nuevo llegará de uno en uno y se escalará con los parámetros congelados del entrenamiento. fit_transform en train, transform a secas en test y en producción: grábatelo como reflejo. Si además todo vive dentro de un Pipeline, el reflejo viene de serie: el pipeline solo hace fit cuando tú se lo pides sobre train.
Errores Comunes y Consejos
- Hacer
fit_transformsobre el conjunto de prueba. Es el error de fuga más común en principiantes: en test (y en producción) solotransform. Si tu test escalado tiene media 0 perfecta, sospecha. - Escalar con
MinMaxScalerun dataset con outliers. El resto de valores queda comprimido en un rincón del [0, 1]. Con outliers legítimos,RobustScaler. - Esperar que escalar arregle la asimetría. Escalar es una operación lineal: la forma del histograma no cambia. Para la forma, las transformaciones de 03-03.
- Escalar las columnas one-hot. Un 0/1 estandarizado se convierte en valores como -0,58/1,72: se pierde la legibilidad sin ganar nada relevante. Deja las binarias fuera de la rama de escalado en el
ColumnTransformer. - Escalar la variable objetivo de un problema de clasificación. El churn (0/1) es la etiqueta, no una característica: no se escala.
- Olvidar guardar el escalador. Los parámetros aprendidos (
mean_,scale_...) son parte del modelo: en producción necesitarás el mismo objeto para escalar cada cliente nuevo (lo retomaremos en el módulo 8). - Consejo: imprime
describe()del resultado escalado. Medias ~0 y desviaciones ~1 (o rangos [0, 1]) en train confirman que todo funcionó; valores absurdos delatan columnas que no debían escalarse.
Ejercicios
Ejercicio 1
Sin ejecutar código: los tiempos de entrega de MercaFresh son [38, 42, 44, 47, 52, 55, 61, 190] minutos (el 190 fue un pedido con la furgoneta averiada). ¿Qué escalador elegirías y por qué? ¿Qué le pasaría al resto de valores con MinMaxScaler?
Ejercicio 2
Estandariza a mano (sin scikit-learn, solo NumPy) la variable antiguedad = [3, 1, 6, 2, 9, 4, 1, 5] y comprueba que el resultado tiene media 0 y desviación 1. Después verifica que StandardScaler da el mismo resultado.
Ejercicio 3
Este código tiene un error de fuga de información. Encuéntralo y corrígelo:
scaler = StandardScaler()
datos_esc = scaler.fit_transform(datos)
X_train, X_test = train_test_split(datos_esc, test_size=0.3)Soluciones
Ejercicio 1
RobustScaler. El 190 es un outlier con causa conocida (avería) pero presente en los datos; media y desviación (StandardScaler) quedarían infladas por él. Con MinMaxScaler sería peor: 190 se llevaría el 1 y los siete valores normales quedarían comprimidos aproximadamente entre 0,00 y 0,15, casi indistinguibles entre sí. RobustScaler, al usar mediana (49,5) e IQR, despliega bien los valores normales y deja que el 190 salga lejos sin distorsionar a los demás. (Alternativa legítima de 03-01: tratar el 190 como error puntual y corregirlo antes de escalar.)
Ejercicio 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 divide por n, como StandardScaler
print(z.round(3))
print(z.mean().round(10), z.std().round(10)) # 0.0 y 1.0
ss = StandardScaler()
z_sk = ss.fit_transform(x.reshape(-1, 1)).ravel()
print(np.allclose(z, z_sk)) # TrueDetalle fino: StandardScaler usa la desviación poblacional (dividir por n), igual que np.std() por defecto; la muestral de pandas (Series.std(), que divide por n-1) daría valores ligeramente distintos.
Ejercicio 3
El fit_transform se ejecuta sobre todos los datos antes de dividir: la media y la desviación aprendidas contienen información de las filas que luego serán de test. Corrección:
X_train, X_test = train_test_split(datos, test_size=0.3, random_state=42)
scaler = StandardScaler()
X_train_esc = scaler.fit_transform(X_train) # aprende SOLO de train
X_test_esc = scaler.transform(X_test) # aplica lo aprendidoPrimero dividir, después ajustar solo con train, y transformar test con los parámetros de train.
Conclusión
Ya dominas la última pieza puramente mecánica del preprocesamiento: por qué los algoritmos de distancias y gradientes necesitan escalas comparables, cómo MinMaxScaler acota a [0, 1] (pero se rinde ante outliers), cómo StandardScaler convierte cada variable a los z-scores que conociste en 02-02, cómo RobustScaler escala con mediana e IQR cuando los outliers son legítimos, y la regla innegociable de ajustar siempre el escalador solo con datos de entrenamiento. El dataset de churn de MercaFresh está limpio, completo, transformado, codificado y escalado.
¿Está entonces terminado? Técnicamente sí; competitivamente no. Hasta ahora hemos reparado y adaptado las columnas que ya existían. El salto de calidad viene de crear columnas nuevas que condensen conocimiento de negocio: cuánto hace que el cliente no compra, cuánto vale, si su actividad crece o se apaga. Esa es la ingeniería de características, la lección que cierra el módulo — y donde el dataset de MercaFresh alcanzará su forma definitiva.
Curso de Machine Learning
Módulo 1: Introducción al Machine Learning
- ¿Qué es el Machine Learning?
- Historia y evolución del Machine Learning
- Tipos de Machine Learning
- Aplicaciones del Machine Learning
- El flujo de trabajo de un proyecto de Machine Learning
Módulo 2: Fundamentos de Estadística y Probabilidad
- Conceptos básicos de estadística
- Distribuciones de probabilidad
- Correlación y covarianza
- Inferencia estadística
- Teorema de Bayes
Módulo 3: Preprocesamiento de Datos
- Limpieza de datos
- Manejo de datos faltantes
- Transformación de datos
- Codificación de variables categóricas
- Normalización y estandarización
- Ingeniería de características
Módulo 4: Algoritmos de Machine Learning Supervisado
- Regresión lineal
- Regresión logística
- Árboles de decisión
- Máquinas de soporte vectorial (SVM)
- K-Vecinos más cercanos (K-NN)
- Naive Bayes
- Redes neuronales
Módulo 5: Algoritmos de Machine Learning No Supervisado
- Clustering: K-means
- Clustering jerárquico
- Análisis de componentes principales (PCA)
- Análisis de agrupamiento DBSCAN
- Visualización de datos con t-SNE y UMAP
Módulo 6: Evaluación y Validación de Modelos
- División de datos: entrenamiento, validación y prueba
- Métricas de evaluación
- Validación cruzada
- Curva ROC y AUC
- Overfitting y underfitting
Módulo 7: Técnicas Avanzadas y Optimización
- Regularización: Ridge, Lasso y Elastic Net
- Ensemble Learning
- Gradient Boosting
- Redes neuronales profundas (Deep Learning)
- Optimización de hiperparámetros
Módulo 8: Implementación y Despliegue de Modelos
- Frameworks y bibliotecas populares
- Implementación de modelos en producción
- Mantenimiento y monitoreo de modelos
- Consideraciones éticas y de privacidad
Módulo 9: Proyectos Prácticos
- Proyecto 1: Predicción de precios de viviendas
- Proyecto 2: Clasificación de imágenes
- Proyecto 3: Análisis de sentimientos en redes sociales
- Proyecto 4: Detección de fraudes
- Proyecto 5: Segmentación de clientes
