Vai al contenuto principale

Rilevare anomalie con Anomalize in R

Scopri come rilevare anomalie in grandi set di dati di serie temporali e presentare gli insight in modo molto più semplice.
· 9 min leggi

Scopri con l'IA

Anomalize: breve descrizione

Quando parliamo di anomalie, consideriamo i punti dati che sono outlier o eventi eccezionali. Identificare questi eventi è facile in set di dati piccoli e può essere fatto con alcuni grafici di analisi semplici come i boxplot. Ma i casi diventano via via più complicati quando si passa a set di dati grandi, soprattutto nel caso delle serie temporali. Una serie temporale è un dato raccolto a intervalli fissi nel tempo che, una volta analizzato, mostra una tendenza o una stagionalità. Individuare anomalie in questi casi è piuttosto complicato.

Qui entra in gioco il pacchetto anomalize per il rilevamento di anomalie nell’analisi delle serie temporali: è un algoritmo di rilevamento “tidy”, basato sul tempo e scalabile da una a molte serie temporali.

Esistono alcuni pacchetti e metodi che ne supportano lo sviluppo, o meglio, è una combinazione di risorse disponibili con un approccio scalabile.

Il lavoro open source che ha contribuito è il seguente:

  • Il pacchetto di Twitter AnomalyDetection: disponibile su Github (cran anaomalyDetection è un lavoro diverso).
  • La funzione forecast::tsoutliers() di Rob Hyndman, disponibile tramite il pacchetto forecast.
  • Il pacchetto tsoutliers di Javier Lopez-de-lacalle, su CRAN.

Tutti questi pacchetti e funzioni sono usati per integrare un flusso di lavoro scalabile.

Parlando del flusso di lavoro di anomalize, è suddiviso in tre parti:

  • Scomposizione della serie temporale con time_decompose().
  • Rilevamento delle anomalie del residuo con anomalize().
  • Trasformazione dei limiti inferiore e superiore delle anomalie con time_recompose().

Flusso di lavoro del rilevamento anomalie

Scomposizione della serie temporale

Il primo passo è la scomposizione della serie temporale usando time_decompose(). Il valore misurato o il valore numerico su cui effettuare il rilevamento per un particolare gruppo viene scomposto in quattro colonne: observed, season, trend e remainder. Il metodo predefinito usato per la scomposizione è stl, ovvero una scomposizione stagionale che utilizza un smoother Loess.

La regressione Loess è il metodo più comune usato per levigare una serie temporale volatile: adatta più regressioni nel vicinato locale; si può anche dire che i dati vengono suddivisi e si applica la regressione a ciascuna parte, cosa utile nelle serie temporali perché conosciamo il vincolo del tempo, che in questo caso è la variabile X. Questo metodo funziona bene quando la tendenza domina la stagionalità della serie temporale.

Qui la trend è la crescita di lungo periodo che avviene su molte osservazioni, e la seasonality è il pattern ciclico che si verifica su un ciclo giornaliero, per minuto o per ora, o settimanale.

Esiste una seconda tecnica che puoi usare per la scomposizione stagionale nelle serie temporali basata sulla mediana, il metodo Twitter, usato anche nel pacchetto AnomalyDetection. È identico a STL per la rimozione della componente stagionale. La differenza, nella rimozione della tendenza, è che usa mediane a tratti dei dati (una o più mediane suddivise a intervalli specificati) invece di adattare un smoother. Questo metodo funziona bene quando la stagionalità domina la tendenza nella serie temporale.

Parliamo dell’output della funzione time_decompose(): come detto sopra, produce 4 colonne:

  • observed: i valori effettivi.
  • season: la componente stagionale o ciclica. Il default è una stagionalità settimanale.
  • trend: la tendenza di lungo periodo. Il default è un intervallo di 3 mesi.
  • remainder: usato per analizzare gli outlier. È semplicemente observed meno season e trend.

La funzione time_decompose() contiene un argomento merge: impostandolo a TRUE possiamo mantenere i dati originali insieme alle colonne prodotte.

# Using data package provided in the anomalize package and taking single time series of package purrr

purrr_package = tidyverse_cran_downloads%>%
  filter(package == "purrr")%>%
  ungroup()

purrr_anomaly  = purrr_package %>%
  time_decompose(count)

purrr_anomaly%>% glimpse()
 ## Observations: 425
## Variables: 5
## $ date      <date> 2017-01-01, 2017-01-02, 2017-01-03, 2017-01-04, 201...
## $ observed  <dbl> 550, 1012, 1515, 1702, 1696, 1613, 860, 796, 2008, 2...
## $ season    <dbl> -2158.8893, 692.6571, 1087.5708, 1052.3294, 939.9377...
## $ trend     <dbl> 1496.712, 1511.009, 1525.307, 1539.604, 1553.901, 15...
## $ remainder <dbl> 1212.1777, -1191.6661, -1097.8773, -889.9334, -797.8...

Rilevare anomalie nei residui

Dopo aver completato l’analisi della serie temporale e aver ottenuto dal residuo le caratteristiche desiderate per effettuare il rilevamento, vengono create tre nuove colonne.

  • remainder_l1: il limite inferiore del residuo.
  • remainder_l2: il limite superiore del residuo.
  • anomaly: indica se l’osservazione è un’anomalia oppure no.

Le anomalie sono punti ad alta leva che distorcono la distribuzione. anomalize implementa due metodi resistenti ai punti ad alta leva:

  • IQR: Intervallo interquartile
  • GESD: Generalized Extreme Studentized Deviate Test

IQR

È un metodo simile a quello usato nella funzione tsoutliers() del pacchetto forecast. Con IQR si prende una distribuzione e si usano il 25% e il 75% dell’intervallo interquartile per stabilire la distribuzione del residuo. I limiti sono impostati per impostazione predefinita a un fattore 3 volte sopra e sotto l’intervallo interquartile; qualsiasi residuo oltre tali limiti è considerato un’anomalia.

GESD

Con GESD le anomalie vengono valutate progressivamente rimuovendo i casi peggiori e ricalcolando statistiche di test e valori critici; più semplicemente, si ricalcola l’intervallo dopo aver identificato le anomalie in modo iterativo.

Entrambi IQR e GESD hanno pro e contro: IQR è relativamente più veloce perché non prevede cicli, ma non è accurato quanto GESD poiché le anomalie alterano la mediana, effetto che viene attenuato in GESD.

purrr_anomaly = purrr_anomaly%>%
  anomalize(remainder)

purrr_anomaly%>% glimpse()
## Observations: 425
## Variables: 8
## $ date         <date> 2017-01-01, 2017-01-02, 2017-01-03, 2017-01-04, ...
## $ observed     <dbl> 550, 1012, 1515, 1702, 1696, 1613, 860, 796, 2008...
## $ season       <dbl> -2158.8893, 692.6571, 1087.5708, 1052.3294, 939.9...
## $ trend        <dbl> 1496.712, 1511.009, 1525.307, 1539.604, 1553.901,...
## $ remainder    <dbl> 1212.1777, -1191.6661, -1097.8773, -889.9334, -79...
## $ remainder_l1 <dbl> -4330.511, -4330.511, -4330.511, -4330.511, -4330...
## $ remainder_l2 <dbl> 4400.459, 4400.459, 4400.459, 4400.459, 4400.459,...
## $ anomaly      <chr> "No", "No", "No", "No", "No", "No", "No", "No", "...

Trasformazione dei limiti inferiore e superiore delle anomalie

L’ultimo passo del flusso di lavoro è creare limiti inferiore e superiore attorno ai valori osservati con time_recompose. Ricompone season, trend, remainder_l1 e remainder_l2 in nuovi limiti che sono:

  • recomposed_l1: il limite inferiore degli outlier attorno ai valori osservati.
  • recomposed_l2: il limite superiore degli outlier attorno ai valori osservati.
purrr_anomaly = purrr_anomaly%>%
  time_recompose()

purrr_anomaly%>% glimpse()
## Observations: 425
## Variables: 10
## $ date          <date> 2017-01-01, 2017-01-02, 2017-01-03, 2017-01-04,...
## $ observed      <dbl> 550, 1012, 1515, 1702, 1696, 1613, 860, 796, 200...
## $ season        <dbl> -2158.8893, 692.6571, 1087.5708, 1052.3294, 939....
## $ trend         <dbl> 1496.712, 1511.009, 1525.307, 1539.604, 1553.901...
## $ remainder     <dbl> 1212.1777, -1191.6661, -1097.8773, -889.9334, -7...
## $ remainder_l1  <dbl> -4330.511, -4330.511, -4330.511, -4330.511, -433...
## $ remainder_l2  <dbl> 4400.459, 4400.459, 4400.459, 4400.459, 4400.459...
## $ anomaly       <chr> "No", "No", "No", "No", "No", "No", "No", "No", ...
## $ recomposed_l1 <dbl> -4992.689, -2126.845, -1717.634, -1738.578, -183...
## $ recomposed_l2 <dbl> 3738.281, 6604.125, 7013.336, 6992.392, 6894.298...

Rappresentare graficamente le anomalie

purrr_anomaly%>%
  plot_anomaly_decomposition()+
  ggtitle("Plotting Anomalies")
rappresentazione delle anomalie

Ottimizzazione dei parametri

Modellare un rilevatore di anomalie sarebbe incompleto senza regolare i parametri, che dipendono interamente dai dati.

Passiamo alla regolazione dei parametri: i parametri di ciascun livello del flusso di lavoro sono diversi, poiché ogni livello svolge il proprio compito.

Regolare i parametri della scomposizione

Come detto, trend e stagionalità sono fondamentali per scomporre una serie temporale, quindi l’adeguamento dovrebbe essere fatto anche nella frequenza e nella tendenza della funzione time_decompose. Per impostazione predefinita, i valori sono assegnati automaticamente: 7 giorni per la frequenza in entrambi i metodi (STL, Twitter) e per la tendenza 91 giorni per STL e 85 giorni per Twitter.

Puoi modificare uno o entrambi gli argomenti a tuo piacimento, ma valuta attentamente prima di intervenire: cambiare senza osservazione può portare a overfitting o underfitting del processo di scomposizione.

purrr_package %>%
  time_decompose(count, frequency = "auto", trend = "2 weeks")%>%
  anomalize(remainder)%>%
  plot_anomaly_decomposition()+
  ggtitle("Trend = 2 Weeks / Frequency = auto ")
serie temporale

Regolare i parametri per il rilevamento delle anomalie

Come detto sopra, qui l’anomalia viene determinata in base ai valori del residuo, calcolando i limiti per categorizzare gli outlier. alpha e max_anoms sono i due parametri che controllano la funzione anomalize(). In parole semplici, alpha controlla l’ampiezza della banda: per default è impostato a 0,05; diminuirne il valore aumenterà le dimensioni della banda, rendendo più difficile che un punto sia un’anomalia.

purrr_package%>%
  time_decompose(count)%>%
  anomalize(remainder, alpha = 0.05)%>%
  time_recompose()%>%
  plot_anomalies(time_recompose = T)+
  ggtitle("alpha = 0.05")
serie temporale
purrr_package%>%
  time_decompose(count)%>%
  anomalize(remainder, alpha = 0.025)%>%
  time_recompose()%>%
  plot_anomalies(time_recompose = T)+
  ggtitle("alpha = 0.025")
serie temporale

Il parametro max_anom controlla la percentuale di dati che può risultare un’anomalia. Questo parametro è utile quando alpha è troppo difficile da calibrare e vuoi concentrarti sulle anomalie più marcate.

purrr_package%>%
  time_decompose(count)%>%
  anomalize(remainder, alpha = 0.2, max_anoms = 0.2)%>%
  time_recompose()%>%
  plot_anomalies(time_recompose = T)+
  ggtitle("20% anomaly Allowed")
serie temporale
purrr_package%>%
  time_decompose(count)%>%
  anomalize(remainder, alpha = 0.2, max_anoms = 0.05)%>%
  time_recompose()%>%
  plot_anomalies(time_recompose = T)+
  ggtitle("5% anomaly Allowed")
serie temporale

Riferimenti

Se vuoi imparare di più su R, segui il corso di DataCamp Introduzione all’analisi delle serie temporali.

Dai un’occhiata al nostro Tutorial: analisi delle serie temporali in R.

Argomenti

Correlato