Escalado y normalización
1 ¿Por qué escalar las características?
Muchos algoritmos de machine learning son sensibles a la escala de las variables. Si una variable tiene un rango de 0 a 1.000.000 y otra de 0 a 1, la primera dominará la función de pérdida simplemente por su magnitud, no por su importancia real.
1.1 Algoritmos que necesitan escalado
| Algoritmo | ¿Necesita escalado? | Razón |
|---|---|---|
| KNN | Sí | Basado en distancias euclidianas |
| SVM | Sí | El hiperplano depende de la magnitud de las features |
| Redes neuronales | Sí | Los gradientes dependen de la escala de entrada |
| Regresión lineal/logística | Recomendado | Acelera convergencia, mejora interpretabilidad |
| Árboles de decisión | No | Solo usa umbrales, la escala no afecta |
| Random Forest | No | Misma razón que los árboles |
| XGBoost | No | Basado en árboles |
Nota
Regla simple: Si el algoritmo usa distancias o gradientes, escala. Si usa solo umbrales (árboles), no es necesario.
2 Normalización (Min-Max)
La normalización comprime todos los valores al rango \([0, 1]\):
from sklearn.preprocessing import MinMaxScaler
import numpy as np
X = np.array([[100, 0.5], [200, 1.0], [300, 1.5], [400, 2.0]])
scaler = MinMaxScaler()
X_normalized = scaler.fit_transform(X)
print("Original:\n", X)
print("Normalizado:\n", X_normalized)
print("Rango por columna:", X_normalized.min(axis=0), "-", X_normalized.max(axis=0))
2.1 Cuándo usar Min-Max
- Cuando conoces los límites exactos de tus datos.
- Cuando necesitas valores en un rango específico (por ejemplo, imágenes en [0, 1]).
- Cuando la distribución no es gaussiana.
Advertencia
Sensible a outliers. Si tienes un valor atípico extremo, comprime todos los demás valores a un rango muy pequeño. Ejemplo: si la mayoría de edades están entre 20-60 pero hay una persona de 120 años, todos los valores se comprimen en [0, 0.45].
3 Estandarización (Z-score)
La estandarización transforma los datos para que tengan media 0 y desviación estándar 1:
donde \(\mu\) es la media y \(\sigma\) la desviación estándar.
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
X_standardized = scaler.fit_transform(X)
print("Original - Media:", X.mean(axis=0), "Std:", X.std(axis=0))
print("Estandarizado - Media:", X_standardized.mean(axis=0).round(10), "Std:", X_standardized.std(axis=0).round(4))
3.1 Cuándo usar Z-score
- Cuando la distribución es aproximadamente gaussiana.
- Cuando tienes outliers y quieres minimizar su impacto.
- Para la mayoría de algoritmos de ML, es la opción por defecto recomendada.
Tip
¿Por qué es más robusta a outliers? Porque usa la media y desviación estándar, que se distribuyen más uniformemente que los percentiles (min/max). Un outlier afecta la media, pero no comprime todos los demás valores a un rango tiny como Min-Max.
4 Estandarización robusta
La estandarización robusta usa la mediana y el rango intercuartílico (IQR) en lugar de media y desviación estándar, lo que la hace completamente inmune a outliers:
from sklearn.preprocessing import RobustScaler
scaler = RobustScaler()
X_robust = scaler.fit_transform(X)
print("RobustScaler:\n", X_robust)
Cuándo usar: Cuando tus datos tienen muchos outliers y no puedes (o no quieres) eliminarlos antes de escalar.
5 Máxima magnitud (MaxAbs)
Escala cada feature por su máximo valor absoluto, manteniendo el rango en \([-1, 1]\):
from sklearn.preprocessing import MaxAbsScaler
scaler = MaxAbsScaler()
X_maxabs = scaler.fit_transform(X)
print("MaxAbsScaler:\n", X_maxabs)
Ventaja: Preserva la dispersión de datos dispersos (sparse data). Ideal para TF-IDF y datos de texto.
6 Comparación visual
import matplotlib.pyplot as plt
import numpy as np
from sklearn.preprocessing import MinMaxScaler, StandardScaler, RobustScaler
# Datos con outlier
np.random.seed(42)
X_normal = np.random.randn(100, 1) * 10 + 50
X_outlier = np.array([[200]]) # outlier
X_data = np.vstack([X_normal, X_outlier])
scalers = {
'Original': None,
'Min-Max': MinMaxScaler(),
'Standard': StandardScaler(),
'Robust': RobustScaler()
}
fig, axes = plt.subplots(1, 4, figsize=(16, 4))
for ax, (name, scaler) in zip(axes, scalers.items()):
if scaler is None:
X_plot = X_data
else:
X_plot = scaler.fit_transform(X_data)
ax.hist(X_plot, bins=30, edgecolor='black', alpha=0.7)
ax.set_title(name)
ax.set_xlabel('Valor')
ax.set_ylabel('Frecuencia')
plt.tight_layout()
7 Escalado en pipelines
Importante
Regla de oro: fit_transform() solo en entrenamiento, transform() en validación/test. Si calculas la media y desviación estándar con todos los datos, se filtra información del test al entrenamiento (data leakage).
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2)
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train) # fit + transform
X_test_scaled = scaler.transform(X_test) # solo transform
7.1 Integrado con Pipeline
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.svm import SVC
pipe = Pipeline([
('scaler', StandardScaler()),
('svm', SVC(kernel='rbf'))
])
# Se aplica el escalado correctamente en cross-validation
from sklearn.model_selection import cross_val_score
scores = cross_val_score(pipe, X, y, cv=5)
8 Normalización L1/L2 (por fila)
Nota
No confundir con estandarización. La normalización L1/L2 se aplica por fila (a cada vector de features individualmente). La estandarización (Z-score) se aplica por columna (a cada feature en todo el dataset).
La normalización L2 transforma cada fila para que tenga norma unitaria (magnitud = 1). Es especialmente útil para calcular similitud del coseno en procesamiento de texto y sistemas de recomendación.
8.1 Similitud del coseno
Si los vectores ya están normalizados con L2 (\(\|A\| = \|B\| = 1\)), la fórmula se simplifica a solo el producto punto.
from sklearn.preprocessing import Normalizer, normalize
from sklearn.metrics.pairwise import cosine_similarity
import pandas as pd
import numpy as np
# Datos de ejemplo (cada fila = un documento)
df = pd.DataFrame({
'palabra_1': [1, 2, 3],
'palabra_2': [4, 5, 6],
'palabra_3': [7, 8, 9]
})
# Normalización L2 (por fila)
normalizer = Normalizer(norm='l2')
df_norm = pd.DataFrame(normalizer.fit_transform(df), columns=df.columns)
print("Normalizado L2:")
print(df_norm.round(4))
# Similitud del coseno
sim = cosine_similarity(df_norm)
print(f"\nSimilitud coseno doc 0 vs doc 1: {sim[0, 1]:.4f}")
8.2 Normas disponibles
| Norma | Fórmula | Cuándo usar |
|---|---|---|
| L1 | \(\|x\|_1 = \sum \|x_i\|\) | Preservar linealidad, sparse features |
| L2 | \(\|x\|_2 = \sqrt{\sum x_i^2}\) | Similitud del coseno (texto, recomendación) |
| Max | \(\max(\|x_i\|)\) | Preservar el valor absoluto máximo |
# L1: suma de valores absolutos = 1
df_l1 = pd.DataFrame(normalize(df, norm='l1'), columns=df.columns)
# L2: raíz cuadrada de suma de cuadrados = 1
df_l2 = pd.DataFrame(normalize(df, norm='l2'), columns=df.columns)
# Max: valor absoluto máximo = 1
df_max = pd.DataFrame(normalize(df, norm='max'), columns=df.columns)
9 Power Transformer: hacer los datos más gaussianos
Muchos modelos asumen distribución gaussiana. El PowerTransformer aplica transformaciones de potencia para hacer que los datos sesgados se parezcan más a una normal.
9.1 Box-Cox vs. Yeo-Johnson
| Método | Requisito | Ventaja |
|---|---|---|
| Box-Cox | Datos estrictamente positivos (\(x > 0\)) | Más interpretable |
| Yeo-Johnson | Acepta ceros y negativos | Más flexible |
from sklearn.preprocessing import PowerTransformer
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
# Datos con distribución sesgada (exponencial)
np.random.seed(42)
data = pd.DataFrame({
'ingresos': np.random.exponential(scale=50000, size=1000),
'deuda': np.random.chisquare(df=2, size=1000) * 10000
})
print("Sesgo original:")
print(data.skew().round(2))
# ingresos 1.98
# deuda 2.03
# Aplicar Yeo-Johnson (acepta negativos)
pt = PowerTransformer(method='yeo-johnson')
data_transformed = pd.DataFrame(
pt.fit_transform(data), columns=data.columns
)
print("\nSesgo tras transformación:")
print(data_transformed.skew().round(2))
# ingresos -0.02
# deuda -0.01
Tip
¿Cuándo usar PowerTransformer? Cuando tus features tienen distribución sesgada (colas pesadas) y vas a usar modelos sensibles a la distribución: regresión lineal, regresión logística, SVM lineal. Para árboles de decisión no es necesario.
10 Resumen
| Método | Fórmula | Rango | Robusto a outliers | Ideal para |
|---|---|---|---|---|
| Min-Max | \((x - \min)/(\max - \min)\) | [0, 1] | No | Datos acotados, imágenes |
| Z-score | \((x - \mu)/\sigma\) | ~[-3, 3] | Parcialmente | La mayoría de algoritmos |
| Robust | \((x - \text{med})/IQR\) | Variable | Sí | Datos con outliers |
| MaxAbs | $x / | x_{\max} | $ | [-1, 1] |
| L2 (Normalizer) | \(x / \|x\|_2\) | Unitario | No | Similitud del coseno |
| Power Transformer | Yeo-Johnson / Box-Cox | ~gaussiana | Parcialmente | Datos sesgados |
En la práctica: Usa StandardScaler como punto de partida. Si tienes muchos outliers, prueba RobustScaler. Si trabajas con datos dispersos o texto, usa Normalizer. Si tus features están sesgadas, usa PowerTransformer antes del escalado.