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

Detetar anomalias em séries temporais com Isolation Forest

João Barros 24 de July de 2026 4 min de leitura

Este tutorial mostra como detetar anomalias em séries temporais com Isolation Forest, explicando por que criar características temporais e usar a técnica para sinalizar pontos fora do padrão. É útil para manter a qualidade de dados, monitorizar sistemas ou avisar sobre eventos raros em produção. A abordagem é leve, não prescinde de validação manual e funciona bem quando não há muitas etiquetas de anomalia disponíveis.

Pré-requisitos

  • Python 3.7+ com pandas e scikit-learn instalados
  • Conhecimentos básicos de Python e séries temporais
  • Ficheiro CSV com duas colunas: timestamp e value (ex.: timestamp, value)

Idealmente tenha pelo menos algumas centenas de pontos (por exemplo 1k–100k). Para séries muito curtas (por exemplo <200 pontos) os indicadores estatísticos podem ser instáveis e convém usar validação adicional. Para séries horárias pense em janelas de 24 ou 168; para séries com frequência irregular normalizar para uma frequência fixa antes de extrair características.

Passo 1: Carregar os dados e perceber o problema

Primeiro carregue a série temporal e verifique frequência e valores em falta. É importante saber se a série é diária, horária, ou com outra periodicidade porque as características temporais (lags, janelas) dependem disso. Verifique também outliers óbvios por inspeção: um pico com amplitude 10x da média pode ser uma anomalia 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())

Exemplo prático: se a diferença mais comum for 1H tem uma série horária; se for 1D, série diária. Se houver gaps grandes, considere reamostrar (resample) e preencher com interpolação ou com indicadores de falta.

Passo 2: Preparar características para detetar anomalias

Isolation Forest trabalha melhor com características que expressem comportamento temporal e variações locais. Crie janelas móveis (rolling mean/std), diferenças (lags) e características cíclicas (hora do dia, dia da semana). Para séries horárias, janelas comuns: 3, 24, 168 (3h, 1 dia, 1 semana). Para séries diárias, janelas: 7, 30, 90.

import numpy as np

# assumir df com 'timestamp' e 'value'
df['value'] = df['value'].astype(float)
# características temporais básicas
df['hour'] = df['timestamp'].dt.hour
df['dayofweek'] = df['timestamp'].dt.dayofweek
# janelas móveis
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)
# diferença em relação à janela 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: pode também incluir lags específicos (value.shift(24)), indicadores sazonais (valor do mesmo período na semana anterior) e codificação cíclica para a hora: sin/cos(hour*2pi/24) para evitar descontinuidades na transição 23→0.

Passo 3: Escalar e treinar o Isolation Forest

Escalar ajuda a evitar que variáveis com grande amplitude dominem a decisão. Depois treine o Isolation Forest; ajuste o parameter contamination para a percentagem esperada de anomalias. Se espera ~1% de anomalias use contamination=0.01; para sistemas mais sensíveis, 0.005 ou menos.

from sklearn.ensemble import IsolationForest
from sklearn.preprocessing import StandardScaler

scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)

# contamination: proporção esperada de anomalias (ex.: 0.01 = 1%)
model = IsolationForest(n_estimators=100, contamination=0.01, random_state=42)
model.fit(X_scaled)

# -1 = anomalia, 1 = normal
labels = model.predict(X_scaled)
df['anomaly'] = (labels == -1).astype(int)
# score: valores mais negativos mais anómalos
df['anomaly_score'] = model.decision_function(X_scaled)

Conselho prático: n_estimators=100 é um bom compromisso; aumentar para 200–500 pode melhorar a estabilidade mas aumenta o tempo de treino. Em dados com 100k pontos, n_estimators=100 normalmente treina em alguns segundos numa máquina moderna; teste e meça.

Passo 4: Afinar e reduzir falsos positivos

Nem tudo o que é raro é uma falha; por isso é comum afinar o modelo para reduzir alarmes. Experimente alterar contamination, trocar features ou usar thresholds no anomaly_score. Outra técnica é exigir persistência: confirmar anomalia apenas se ocorrerem 2+ pontos consecutivos ou se a anomalia exceder um percentil.

# exemplo simples: exigir 2 pontos consecutivos para confirmar anomalia
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 do score para definir corte
threshold = np.percentile(df['anomaly_score'], 1)  # 1% mais anómalos
df['anomaly_by_score'] = (df['anomaly_score'] <= threshold).astype(int)

Outras estratégias: agrupar anomalias próximas no tempo (cluster time-based), aplicar filtro de mediana para reduzir ruído, ou combinar com regras heurísticas (por exemplo, ignorar picos que coincidam com manutenção programada).

Verificar o resultado

Confirme que as anomalias identificadas fazem sentido: inspeccione timestamps e valores e compare com registos operacionais. Se tiver etiquetas verdadeiras calcule precision e recall. Sem etiquetas, use amostragem manual: rever 50–200 casos identificados e ajustar contamination até obter uma taxa de falsos positivos aceitável (ex.: <10%).

# mostrar anomalias detetadas
print(df[df['anomaly_confirmed'] == 1][['timestamp','value','anomaly_score']].head())

# se tiver labels verdadeiras (coluna 'is_true_anomaly') pode calcular:
# from sklearn.metrics import precision_score, recall_score
# print(precision_score(df['is_true_anomaly'], df['anomaly_confirmed']))

Conclusão

Com Isolation Forest e características temporais simples consegue detetar anomalias em séries temporais de forma prática e eficiente. Experimente variar janelas (ex.: 24/168), incluir lags sazonais e ajustar contamination conforme a prevalência esperada. Para casos mais sofisticados combine com modelos de série temporal (ex.: Prophet + residuals) ou com validação humana para reduzir alarmes. Dica final: defina KPI de aceitabilidade (quantos falsos positivos por dia são toleráveis?) e ajuste o processo em função desse objetivo operacional.