Detección y tratamiento de valores atípicos
1 ¿Qué son los valores atípicos?
Un valor atípico (outlier) es un dato que se desvía significativamente del resto de observaciones. No son necesariamente errores: pueden ser variaciones legítimas, pero distorsionan estadísticas, escalado y el rendimiento de los modelos.
1.1 ¿Por qué importan?
- Sesgan la media y la desviación estándar, afectando la estandarización.
- Empeoran la regresión lineal, que minimiza errores cuadráticos (un outlier lejano tiene un efecto desproporcionado).
- Confunden KNN y SVM, que usan distancias.
- Pueden ser ruido que el modelo memoriza en lugar de patrones reales.
Nota
No siempre hay que eliminar outliers. En fraude bancario, los outliers son la señal. En detección de anomalías, los outliers son exactamente lo que buscas. El contexto manda.
2 Detección de outliers
2.1 Método 1: Regla de las 3 desviaciones estándar
Si los datos siguen una distribución aproximadamente normal, el 99.7% de los valores están dentro de \(\mu \pm 3\sigma\). Los valores fuera de ese rango son atípicos.
import numpy as np
import pandas as pd
np.random.seed(42)
datos = np.concatenate([np.random.normal(50, 10, 100), [150, -30, 200]])
df = pd.DataFrame({'valor': datos})
media = df['valor'].mean()
std = df['valor'].std()
outliers_zscore = df[(np.abs(df['valor'] - media) > 3 * std)]
print(f"Outliers detectados: {len(outliers_zscore)}")
print(outliers_zscore)
Advertencia
Suposición fuerte: Asume distribución normal. Si los datos no son normales, este método detecta falsos positivos o misses outliers reales.
2.2 Método 2: Rango intercuartílico (IQR)
El IQR es la distancia entre el tercer cuartil (\(Q_3\)) y el primero (\(Q_1\)). Es robusto a outliers porque usa percentiles en lugar de media/desviación estándar.
Q1 = df['valor'].quantile(0.25)
Q3 = df['valor'].quantile(0.75)
IQR = Q3 - Q1
limite_inferior = Q1 - 1.5 * IQR
limite_superior = Q3 + 1.5 * IQR
outliers_iqr = df[(df['valor'] < limite_inferior) | (df['valor'] > limite_superior)]
print(f"Outliers detectados: {len(outliers_iqr)}")
print(f"Límites: [{limite_inferior:.1f}, {limite_superior:.1f}]")
2.3 Método 3: Isolation Forest
El Isolation Forest es un algoritmo de machine learning que aísla observaciones aleatoriamente. Los outliers necesitan menos aleatorios para ser aislados (están “más solos”):
from sklearn.ensemble import IsolationForest
iso_forest = IsolationForest(contamination=0.05, random_state=42)
df['outlier'] = iso_forest.fit_predict(df[['valor']])
print(f"Outliers: {(df['outlier'] == -1).sum()}")
print(df[df['outlier'] == -1])
Ventaja: No asume distribución y funciona con múltiples dimensiones.
2.4 Método 4: Gráficos (visual)
import matplotlib.pyplot as plt
fig, axes = plt.subplots(1, 3, figsize=(14, 4))
axes[0].boxplot(df['valor'])
axes[0].set_title('Boxplot')
axes[1].hist(df['valor'], bins=30, edgecolor='black')
axes[1].axvline(limite_inferior, color='r', linestyle='--', label='Límite IQR')
axes[1].axvline(limite_superior, color='r', linestyle='--')
axes[1].legend()
axes[1].set_title('Histograma')
axes[2].scatter(range(len(df)), df['valor'], c=df['outlier'].map({1: 'blue', -1: 'red'}), s=10)
axes[2].set_title('Scatter (rojo = outlier)')
plt.tight_layout()
2.5 Comparación de métodos
| Método | Ventaja | Desventaja | Ideal para |
|---|---|---|---|
| 3σ | Simple | Asume normalidad | Distribuciones simétricas |
| IQR | Robusto, no paramétrico | Unidimensional | Exploración rápida |
| Isolation Forest | Multidimensional, no paramétrico | Más complejo | Datasets con múltiples features |
3 Tratamiento de outliers
Una vez detectados, hay varias estrategias para manejarlos:
3.1 1. Eliminar registros
Cuándo usar: Cuando el outlier es claramente un error (sensor defectuoso, dato corrupto).
3.2 2. Capping (limitar)
Reemplaza los valores extremos por el límite del IQR:
df_capped = df.copy()
df_capped['valor'] = df_capped['valor'].clip(lower=limite_inferior, upper=limite_superior)
print(f"Original - media: {df['valor'].mean():.2f}, std: {df['valor'].std():.2f}")
print(f"Capped - media: {df_capped['valor'].mean():.2f}, std: {df_capped['valor'].std():.2f}")
Cuándo usar: Cuando quieres conservar la fila pero reducir el impacto del outlier.
3.3 3. Transformación logarítmica
La transformación log comprime valores extremos y es especialmente útil para distribuciones con cola pesada (ingresos, precios, población):
import numpy as np
df['valor_log'] = np.log1p(df['valor'])
print(f"Original - rango: [{df['valor'].min():.1f}, {df['valor'].max():.1f}]")
print(f"Log - rango: [{df['valor_log'].min():.2f}, {df['valor_log'].max():.2f}]")
Tip
np.log1p calcula \(\log(1 + x)\), lo que evita el problema de \(\log(0)\). Es la versión segura de np.log para datos que incluyen ceros.
3.4 4. Winsorización
Similar al capping pero usando percentiles (por ejemplo, reemplazar todo lo que esté por debajo del 1% o por encima del 99% por esos percentiles):
from scipy.stats import mstats
df['valor_winsorized'] = mstats.winsorize(df['valor'], limits=[0.01, 0.01])
3.5 5. Imputación
Tratar el outlier como un valor faltante y reemplazarlo con la mediana:
df_imputed = df.copy()
mask_outlier = df_imputed['outlier'] == -1
df_imputed.loc[mask_outlier, 'valor'] = df_imputed.loc[~mask_outlier, 'valor'].median()
Advertencia
Cuidado con la imputación de outliers. Si imputas con la media/mediana, estás asumiendo que el outlier es un error. Si es un dato legítimo pero extremo, la imputación introduce sesgo.
4 Resumen
| Estrategia | Cuándo usar | Efecto |
|---|---|---|
| Eliminar | Error claro | Reduce tamaño del dataset |
| Capping | Conservar fila, reducir impacto | Trunca valores extremos |
| Transformación log | Distribución con cola pesada | Comprime colas |
| Winsorización | Percentiles extremos | Reemplaza por percentil |
| Imputación | Outlier = error | Reemplaza con central |
Regla práctica: Primero identifica (IQR o Isolation Forest). Luego diagnostica (¿es error o dato legítimo?). Solo entonces trata (eliminar, cap, transformar o imputar).