(+351) 21 24 10006  ·  info@bconcepts.pt
Carnaxide, Lisboa

Cómo detectar anomalías en series temporales en Python

João Barros 24 de July de 2026 6 min de lectura

Este tutorial muestra cómo detectar anomalías en series temporales en Python usando el enfoque simple y eficaz de la media móvil y la desviación estándar. Es una técnica útil cuando quieres señalar rápidamente picos o caídas inesperadas en métricas como tráfico, ventas diarias o lecturas de sensores IoT. La ventaja de este método es que es explicable, rápido de ejecutar y facilita iteraciones rápidas sobre parámetros como la ventana (window) y el umbral (k).

Pre-requisitos

  • Python 3.8+
  • pandas, numpy (pip install pandas numpy)
  • matplotlib para visualización (opcional)

Para reproducir los ejemplos garantiza que tienes un entorno con estas bibliotecas. Si tienes datos horarios, piensa en usar ventanas como 24 o 24*7; para datos diarios, ventanas de 7, 14 o 30 son comunes. La elección afecta la sensibilidad: ventanas más grandes hacen el detector menos sensible a fluctuaciones de corto plazo.

Paso 1: Preparar datos para detectar anomalías en series temporales

Los datos deben tener una marca temporal (timestamp) y una columna de valores. Veamos un ejemplo sintético con 200 observaciones diarias, donde introducimos dos anomalías puntuales para validar el método. También muestro cómo leer un archivo CSV con timestamps en caso de que trabajes con datos reales.

import numpy as np
import pandas as pd

# Exemplo sintético
np.random.seed(0)
dates = pd.date_range('2023-01-01', periods=200, freq='D')
values = 50 + np.cumsum(np.random.normal(0, 0.5, size=len(dates)))
# introduzir anomalias: pico positivo e negativo
values[30] += 8   # pico acentuado
values[120] -= 10 # queda acentuada

df = pd.DataFrame({'timestamp': dates, 'value': values})
df = df.set_index('timestamp')

# Se ler de CSV:
# df = pd.read_csv('dados.csv', parse_dates=['timestamp']).set_index('timestamp')

En este ejemplo tenemos 200 puntos; en escenarios reales es común tener miles o millones de puntos. Prueba primero con un subconjunto para ajustar parámetros antes de aplicarlo al conjunto de datos completo.

Paso 2: Calcular media móvil y desviación para detectar anomalías en series temporales

Usamos una ventana rolling para obtener la media móvil y la desviación estándar. La ventana debe reflejar la escala temporal de la variación normal: para datos diarios, 7 captura fluctuaciones semanales, 14 suaviza más y 30 capta tendencia mensual. El umbral k multiplica la desviación; k=3 es clásico (aproximadamente 99.7% para distribución normal), k=2 es más sensible.

window = 14  # exemplo: 14 dias
k = 3        # limiar: k * std (padrão é 3)

rolling_mean = df['value'].rolling(window=window, center=False).mean()
rolling_std = df['value'].rolling(window=window, center=False).std()

df['rolling_mean'] = rolling_mean
df['rolling_std'] = rolling_std

Nota: los primeros window-1 puntos tendrán NaN en las métricas. Para producción puedes usar .fillna(method='bfill') o empezar la detección solo después de que la ventana esté rellena.

Paso 3: Marcar anomalías simples con z-score rodante

Marcamos como anomalías los puntos fuera de mean ± k * std. Esta regla funciona bien cuando los residuos se aproximan a una distribución normal y no existe una fuerte tendencia no eliminada.

df['z_score'] = (df['value'] - df['rolling_mean']) / df['rolling_std']

def flag_anomaly(z, threshold=k):
    if pd.isna(z):
        return False
    return abs(z) > threshold

df['anomaly'] = df['z_score'].apply(lambda z: flag_anomaly(z, k))

En términos prácticos, con k=3 esperas un número reducido de anomalías (por ejemplo, <1% de los puntos) si los datos son estables. Si obtienes muchas anomalías, considera aumentar k o ampliar la ventana.

Paso 4: Manejar tendencia y estacionalidad antes de detectar anomalías

Si la serie tiene fuerte tendencia o estacionalidad, el método directo puede señalar esos efectos como anomalías. Dos enfoques simples: (1) diferenciar la serie para eliminar la tendencia de nivel (diferenciación), o (2) usar medidas robustas como rolling median e IQR para reducir la influencia de outliers.

# Detrending simples por diferença
df['value_diff'] = df['value'].diff()
# Recalcular métricas sobre la serie diferenciada (omitindo o primeiro NaN)
rolling_mean_diff = df['value_diff'].rolling(window=window).mean()
rolling_std_diff = df['value_diff'].rolling(window=window).std()

df['z_score_diff'] = (df['value_diff'] - rolling_mean_diff) / rolling_std_diff

df['anomaly_diff'] = df['z_score_diff'].abs() > k

Ejemplo práctico: en una serie con tendencia ascendente continua, la diferenciación normalmente reduce detecciones falsas. Otra alternativa es usar rolling median y el rango intercuartílico (IQR) para definir límites robustos si tienes muchos outliers.

Paso 5: Visualizar para confirmar y ajustar parámetros

Visualizar ayuda a ajustar window y k. Dibuja la serie, la media móvil y marca las anomalías detectadas. Ajusta hasta que las anomalías identificadas tengan sentido para el contexto del negocio (por ejemplo, picos de tráfico debidos a campañas son esperados).

import matplotlib.pyplot as plt

plt.figure(figsize=(10,4))
plt.plot(df.index, df['value'], label='value')
plt.plot(df.index, df['rolling_mean'], label=f'rolling_mean ({window})', alpha=0.8)
plt.scatter(df.index[df['anomaly']], df['value'][df['anomaly']], color='red', label='anomaly')
plt.legend()
plt.tight_layout()
plt.show()

Verificar el resultado

Confirma que las anomalías tienen sentido: cuéntalas, examina extractos y compara con la versión diferenciada. En nuestro ejemplo sintético, donde introdujimos dos anomalías puntuales (índices 30 y 120), se espera que el método detecte esas dos cuando la ventana y k estén bien elegidos. Si detectas falsos positivos, ajusta k o usa un enfoque robusto.

# Contagem e amostra de anomalias
print('Total anomalias (raw):', df['anomaly'].sum())
print(df[df['anomaly']].head())

# Comparar com abordagem diferenciada
print('Total anomalias (diff):', df['anomaly_diff'].sum())

# Guardar para análise
# df.to_csv('detected_anomalies.csv')

Conclusión

La técnica de media móvil + desviación estándar es un buen punto de partida para detectar anomalías en series temporales en Python: es simple, explicable y rápida de ejecutar en conjuntos de datos moderados. Pasos siguientes posibles incluyen métodos robustos (rolling median + IQR), modelos estadísticos (ARIMA/ETS) para series con patrón, o algoritmos de machine learning (IsolationForest, autoencoders) para patrones más complejos. Consejo práctico: comienza ajustando window y k, haz validación manual sobre muestras y visualiza siempre los resultados — muchas veces 1 o 2 iteraciones son suficientes para converger a valores útiles para tu caso.