Artículos / MLOps
Monitoreo ML con Evidently: Detecta Data Drift en Python
Compara batches con Evidently 0.7.23, define variables numéricas, exporta métricas y pruebas de drift y evalúa calidad con labels por separado.

Data drift significa que cambió la distribución de una entrada, predicción u otro valor registrado frente a un período de referencia. Es una señal para investigar, no una prueba de que bajó la calidad del modelo. Para monitorear calidad hacen falta labels reales con retraso y una métrica ligada a la decisión.
Este ejemplo se probó con Python 3.13 y Evidently 0.7.23: python -m pip install "evidently==0.7.23" pandas numpy. El bloque opcional de calidad con labels también usa scikit-learn y pyarrow para leer Parquet. En un proyecto real fija el conjunto completo de dependencias y valida actualizaciones; los imports antiguos evidently.report y evidently.metric_preset pertenecen a versiones 0.6.7 o anteriores.
Crea un batch de referencia y uno actual
Usa los mismos nombres, tipos y etapa de preprocesamiento en ambos datasets. Estos datos son sintéticos: el cambio de monthly_charge demuestra un chequeo, no un modelo productivo degradado.
import numpy as np
import pandas as pd
rng = np.random.default_rng(42)
reference = pd.DataFrame({
"tenure_months": rng.exponential(24, 600),
"monthly_charge": rng.normal(65, 12, 600),
"support_calls": rng.poisson(1.5, 600),
})
current = pd.DataFrame({
"tenure_months": rng.exponential(24, 300),
"monthly_charge": rng.normal(90, 12, 300), # cambio sintético intencional
"support_calls": rng.poisson(1.5, 300),
})
La referencia debe representar un período en que el pipeline y las condiciones de operación eran aceptables. No la reemplaces silenciosamente después de una alerta: conserva su identificador y versión para que la comparación siga siendo interpretable.
Ejecuta y exporta un reporte de drift
from evidently import DataDefinition, Dataset, Report
from evidently.presets import DataDriftPreset
definition = DataDefinition(numerical_columns=list(reference.columns))
current_dataset = Dataset.from_pandas(current, data_definition=definition)
reference_dataset = Dataset.from_pandas(reference, data_definition=definition)
report = Report([DataDriftPreset(columns=list(reference.columns))], include_tests=True)
snapshot = report.run(current_dataset, reference_dataset, name="daily-input-drift")
snapshot.save_html("input_drift.html")
with open("input_drift.json", "w", encoding="utf-8") as output:
output.write(snapshot.json())
La definición trata estas tres mediciones como numéricas, incluido el conteo support_calls. La inferencia automática puede tratar un entero con pocos valores distintos como categórico; en esta muestra sintética, la comparación chi-cuadrado resultante encuentra una categoría vacía en la referencia y divide entre cero. El tipo explícito responde al significado de la variable, sin ocultar esa advertencia.
report.run(current_dataset, reference_dataset) devuelve un Snapshot; exporta desde ese objeto. include_tests=True activa condiciones de prueba automáticas además de las métricas de drift. Sin esa opción, el reporte exportado contiene métricas pero ninguna prueba en la versión fijada. El umbral predeterminado de proporción de columnas con drift es 0.5: una columna cambiada puede fallar su prueba mientras pasa la del dataset completo. Inspecciona las columnas y calibra umbrales con batches históricos estables; ese valor predeterminado no es una política de alerta de negocio.
Agrega calidad solo cuando lleguen labels
Puede haber drift de entrada sin pérdida de calidad y bajar la calidad sin drift detectable. Cuando llegue el resultado real, regístralo con la predicción y evalúalo por separado. El siguiente código calcula una métrica reproducible; no inventa labels para afirmar degradación.
from sklearn.metrics import roc_auc_score
# `label` y el `score` continuo vienen de la misma cohorte ya completada.
# Los labels son binarios y el batch tiene al menos un ejemplo de cada clase.
scored_batch = pd.read_parquet("labeled_production_batch.parquet")
auc = roc_auc_score(scored_batch["label"], scored_batch["score"])
print(f"ROC AUC del batch: {auc:.3f}")
Compara el valor con una línea base definida y una incertidumbre acorde al volumen y la decisión. Un cambio puede venir de labels, muestreo, joins faltantes, un modelo que produce scores distintos o una relación real distinta; un umbral de decisión por sí solo no cambia el ROC AUC calculado con scores continuos. Revisa calidad de datos y serving antes de reentrenar.
Respuesta práctica a una alerta
- Conserva el reporte, ventana del batch, versión de referencia y versión de código.
- Inspecciona columnas cambiadas y transformaciones upstream; estacionalidad y un cambio de esquema requieren acciones distintas.
- Revisa la distribución de predicciones y, cuando existan, métricas con labels.
- Decide con el responsable del modelo si corregir datos, recolectar labels, ajustar un umbral documentado, reentrenar o pausar la decisión.
Este script crea evidencia para revisión. No programa jobs, envía alertas, garantiza detectar drift ni demuestra que un modelo reentrenado sea seguro en producción.
Fuentes
¿Vas a construir una plataforma de IA o de datos?
Si necesitas adaptar este enfoque a tu sistema, cuéntame qué datos tienes y qué decisión técnica necesitas resolver.