Cómo detectar desviaciones de distribución con Kolmogorov-Smirnov en Machine Learning
Este tutorial muestra cómo detectar desviaciones de distribución (data drift) en Machine Learning usando la prueba Kolmogorov-Smirnov en Python. Saber cuándo las features cambian a lo largo del tiempo es útil para mantener modelos robustos y evitar la degradación del rendimiento. Voy a explicar por qué sirve la prueba, dar un ejemplo práctico con código y mostrar cómo interpretar y actuar sobre los resultados.
Requisitos previos
- Python 3.8+ y bibliotecas: pandas, scipy, matplotlib (instalar con pip).
- Un conjunto de datos dividido en dos muestras: reference (entrenamiento) y current (producción).
- Conocimientos básicos de pandas e interpretación de estadísticas.
- Para producción, idealmente tener muestras con tamaños representativos: p. ej. 1k-10k para referencia y 200-5k para corriente, dependiendo del caso.
Paso 1: Entender el propósito de la prueba Kolmogorov-Smirnov
La prueba Kolmogorov-Smirnov (KS) compara dos muestras para verificar si provienen de la misma distribución continua. El resultado devuelve la estadística KS (la mayor diferencia entre las funciones de distribución empíricas, en el intervalo [0,1]) y un p‑valor que cuantifica la evidencia contra la hipótesis nula de que ambas muestras provienen de la misma distribución. El KS es no paramétrico y sensible a diferencias en la ubicación y en la forma de la distribución. Esto significa que detecta cambios de media, varianza e incluso alteraciones más sutiles en las colas.
Paso 2: Preparar datos de ejemplo
Crea dos muestras: reference (entrenamiento) y current (producción) — con pequeños cambios para simular drift. Mantén los nombres de las columnas iguales. En el ejemplo abajo usamos 1000 puntos en la referencia y 500 en la producción; modificamos la escala de 'income' para simular drift y añadimos algunos outliers y valores missing para acercar escenarios reales.
import numpy as np
import pandas as pd
np.random.seed(42)
# reference: distribuição normal e exponencial
reference = pd.DataFrame({
'age': np.random.normal(40, 10, 1000),
'income': np.random.exponential(40000, 1000)
})
# introduzir alguns missing e outliers
reference.loc[::200, 'income'] = np.nan
reference.loc[5, 'age'] = 120 # outlier
# current: simula drift na 'income' (mudança de escala) e pequena mudança na idade
current = pd.DataFrame({
'age': np.random.normal(41, 11, 500),
'income': np.random.exponential(60000, 500)
})
current.loc[::150, 'income'] = np.nan
Paso 3: Implementar la prueba KS por feature
Usamos scipy.stats.ks_2samp para calcular la estadística KS y el p‑valor. Interpretación simple: p‑valor bajo (p. ej.: < 0.05) indica diferencia significativa entre distribuciones. Atención: con muchas features tenemos múltiples pruebas; aplicar corrección (p. ej. Bonferroni) reduce falsos positivos. También considera el tamaño de las muestras: con grandes n pequeños desvíos pueden ser significativos estadísticamente pero sin relevancia práctica.
from scipy.stats import ks_2samp
def detect_drift_ks(ref, cur, alpha=0.05, bonferroni=False):
results = []
numeric_cols = ref.select_dtypes(include=[np.number]).columns
m = len(numeric_cols)
for col in numeric_cols:
a = ref[col].dropna()
b = cur[col].dropna()
stat, pvalue = ks_2samp(a, b)
if bonferroni:
pvalue_adj = min(pvalue * m, 1.0)
else:
pvalue_adj = pvalue
drift = pvalue_adj < alpha
results.append({
'feature': col,
'ks_stat': float(stat),
'p_value': float(pvalue),
'p_value_adj': float(pvalue_adj),
'drift': bool(drift)
})
return pd.DataFrame(results)
results = detect_drift_ks(reference, current, alpha=0.05, bonferroni=True)
print(results)
Paso 4: Visualizar diferencias (ejemplo)
Las visualizaciones ayudan a confirmar lo que indica la prueba. Trazar histogramas o KDE para comparar shapes de las distribuciones entre reference y current. Si el KS indica drift en 'income', el histograma normalmente muestra desplazamiento de la cola o aumento de la dispersión. Incluye boxplots para ver outliers y diferencias en la mediana.
import matplotlib.pyplot as plt
for col in ['age', 'income']:
plt.figure(figsize=(6,3))
plt.hist(reference[col].dropna(), bins=40, alpha=0.5, label='reference', density=True)
plt.hist(current[col].dropna(), bins=40, alpha=0.5, label='current', density=True)
plt.title(f'Comparação de distribuições: {col}')
plt.legend()
plt.tight_layout()
plt.show()
Paso 5: Manejar el drift detectado
Si el drift es significativo, acciones posibles: reentrenar el modelo con datos actualizados, aplicar recalibración, usar normalizaciones/transformaciones que sean estables (p. ej. quantile transformer entrenado en la referencia), o implementar monitorización continua. Prioriza features con mayor importancia en el modelo y cuantifica el impacto reevaluando métricas de rendimiento (AUC, RMSE, etc.). En muchos equipos, un workflow práctico es: 1) alertar si 1-3 features críticas tienen drift; 2) correr test A/B o reentrenar en ventana móvil; 3) validar performance en validación holdout.
Verificar el resultado
Confirma que el pipeline funciona observando el DataFrame de resultados: columnas con 'drift' = True identifican features con p‑valor < alpha. Valida visualmente con los histogramas; si ambos indican cambio, hay drift real. Prueba con diferentes seeds/muestras y verifica la estabilidad de la prueba: a veces pequeñas muestras producen p‑valores inestables. Considera también métricas de magnitud del drift (ks_stat) para priorizar intervenciones: valores cercanos a 0.1 son débiles, por encima de 0.2-0.3 ya son cambios relevantes en muchos contextos.
Conclusión
La prueba Kolmogorov-Smirnov es una herramienta simple y eficaz para detectar desviaciones de distribución en features numéricas y ayudar a decidir si un modelo necesita mantenimiento. Para producción, automatiza este check, define thresholds por feature y combínalo con pruebas para variables categóricas (p. ej.: Chi-square) y con monitorización del rendimiento del modelo. Consejo práctico: empieza por monitorizar las 5-10 features con mayor importance en el modelo y registra métricas como ks_stat y p_value a lo largo del tiempo para identificar tendencias antes de actuar.