Detección de anomalías en series temporales con Isolation Forest
Este tutorial muestra cómo detectar anomalías en series temporales con Isolation Forest, explicando por qué crear características temporales y usar la técnica para señalar puntos fuera de lo habitual. Es útil para mantener la calidad de los datos, monitorizar sistemas o avisar sobre eventos raros en producción. El enfoque es ligero, no prescinde de validación manual y funciona bien cuando no hay muchas etiquetas de anomalía disponibles.
Requisitos previos
- Python 3.7+ con pandas y scikit-learn instalados
- Conocimientos básicos de Python y series temporales
- Archivo CSV con dos columnas: timestamp y value (p. ej.: timestamp, value)
Idealmente tenga al menos algunas centenas de puntos (por ejemplo 1k–100k). Para series muy cortas (por ejemplo <200 puntos) los indicadores estadísticos pueden ser inestables y conviene usar validación adicional. Para series horarias piense en ventanas de 24 o 168; para series con frecuencia irregular normalizar a una frecuencia fija antes de extraer características.
Paso 1: Cargar los datos y entender el problema
Primero cargue la serie temporal y verifique frecuencia y valores faltantes. Es importante saber si la serie es diaria, horaria u otra periodicidad porque las características temporales (lags, ventanas) dependen de ello. Verifique también outliers evidentes por inspección: un pico con amplitud 10x de la media puede ser una anomalía legítima.
import pandas as pd
df = pd.read_csv('serie.csv', parse_dates=['timestamp'])
df = df.sort_values('timestamp').reset_index(drop=True)
print(df.head())
print(df['timestamp'].diff().value_counts().head())
Ejemplo práctico: si la diferencia más común es 1H tiene una serie horaria; si es 1D, serie diaria. Si hay gaps grandes, considere re-muestrear (resample) y rellenar con interpolación o con indicadores de ausencia.
Paso 2: Preparar características para detectar anomalías
Isolation Forest funciona mejor con características que expresen comportamiento temporal y variaciones locales. Cree ventanas móviles (rolling mean/std), diferencias (lags) y características cíclicas (hora del día, día de la semana). Para series horarias, ventanas comunes: 3, 24, 168 (3h, 1 día, 1 semana). Para series diarias, ventanas: 7, 30, 90.
import numpy as np
# asumir df con 'timestamp' y 'value'
df['value'] = df['value'].astype(float)
# características temporales básicas
df['hour'] = df['timestamp'].dt.hour
df['dayofweek'] = df['timestamp'].dt.dayofweek
# ventanas móviles
df['roll_mean_3'] = df['value'].rolling(3, center=False, min_periods=1).mean()
df['roll_std_3'] = df['value'].rolling(3, center=False, min_periods=1).std().fillna(0)
# diferencia respecto a la ventana anterior
df['diff_1'] = df['value'].diff().fillna(0)
features = ['value', 'roll_mean_3', 'roll_std_3', 'diff_1', 'hour', 'dayofweek']
X = df[features].fillna(0)
Nota: también puede incluir lags específicos (value.shift(24)), indicadores estacionales (valor del mismo período en la semana anterior) y codificación cíclica para la hora: sin/cos(hour*2pi/24) para evitar discontinuidades en la transición 23→0.
Paso 3: Escalar y entrenar el Isolation Forest
Escalar ayuda a evitar que variables con gran amplitud dominen la decisión. Después entrene el Isolation Forest; ajuste el parámetro contamination para el porcentaje esperado de anomalías. Si espera ~1% de anomalías use contamination=0.01; para sistemas más sensibles, 0.005 o menos.
from sklearn.ensemble import IsolationForest
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
# contamination: proporción esperada de anomalías (p. ej.: 0.01 = 1%)
model = IsolationForest(n_estimators=100, contamination=0.01, random_state=42)
model.fit(X_scaled)
# -1 = anomalía, 1 = normal
labels = model.predict(X_scaled)
df['anomaly'] = (labels == -1).astype(int)
# score: valores más negativos más anómalos
df['anomaly_score'] = model.decision_function(X_scaled)
Consejo práctico: n_estimators=100 es un buen compromiso; aumentarlo a 200–500 puede mejorar la estabilidad pero incrementa el tiempo de entrenamiento. En datos con 100k puntos, n_estimators=100 normalmente entrena en pocos segundos en una máquina moderna; pruebe y mida.
Paso 4: Afinar y reducir falsos positivos
No todo lo raro es una falla; por eso es habitual afinar el modelo para reducir alarmas. Pruebe a cambiar contamination, sustituir features o usar umbrales en el anomaly_score. Otra técnica es exigir persistencia: confirmar anomalía solo si ocurren 2+ puntos consecutivos o si la anomalía excede un percentil.
# ejemplo simple: exigir 2 puntos consecutivos para confirmar anomalía
df['anomaly_confirmed'] = 0
for i in range(1, len(df)):
if df.loc[i, 'anomaly'] == 1 and df.loc[i-1, 'anomaly'] == 1:
df.loc[i-1:i, 'anomaly_confirmed'] = 1
# alternativa: usar percentil del score para definir corte
threshold = np.percentile(df['anomaly_score'], 1) # 1% más anómalos
df['anomaly_by_score'] = (df['anomaly_score'] <= threshold).astype(int)
Otras estrategias: agrupar anomalías próximas en el tiempo (cluster time-based), aplicar filtro de mediana para reducir ruido, o combinar con reglas heurísticas (por ejemplo, ignorar picos que coincidan con mantenimiento programado).
Verificar el resultado
Confirme que las anomalías identificadas tienen sentido: inspeccione timestamps y valores y compárelos con registros operativos. Si tiene etiquetas verdaderas calcule precision y recall. Sin etiquetas, use muestreo manual: revisar 50–200 casos identificados y ajustar contamination hasta obtener una tasa de falsos positivos aceptable (p. ej.: <10%).
# mostrar anomalías detectadas
print(df[df['anomaly_confirmed'] == 1][['timestamp','value','anomaly_score']].head())
# si tiene labels verdaderas (columna 'is_true_anomaly') puede calcular:
# from sklearn.metrics import precision_score, recall_score
# print(precision_score(df['is_true_anomaly'], df['anomaly_confirmed']))
Conclusión
Con Isolation Forest y características temporales simples puede detectar anomalías en series temporales de forma práctica y eficiente. Pruebe a variar ventanas (p. ej.: 24/168), incluir lags estacionales y ajustar contamination según la prevalencia esperada. Para casos más sofisticados combine con modelos de series temporales (p. ej.: Prophet + residuals) o con validación humana para reducir alarmas. Consejo final: defina un KPI de aceptabilidad (¿cuántos falsos positivos por día son tolerables?) y ajuste el proceso en función de ese objetivo operacional.