Ga naar hoofdinhoud

Detecteer anomalieën met Anomalize in R

Leer hoe je anomalieën in grote tijdreeksdatabases detecteert en inzichten veel eenvoudiger presenteert.
· 9 min lezen

Verken met AI

Anomalize: korte beschrijving

Als we het over anomalieën hebben, bedoelen we datapunten die uitschieters zijn of een uitzonderlijke gebeurtenis vormen. Zulke gebeurtenissen identificeren is eenvoudig in kleine datasets en kan met simpele analysegrafieken zoals boxplots. Maar het wordt al snel ingewikkeld bij grote datasets, zeker bij tijdreeksen. Een tijdreeks is data die op vaste tijdsintervallen over een periode is vastgelegd en bij analyse een trend of seizoenspatroon laat zien. Het identificeren van anomalieën in zulke gevallen is best lastig.

Daarvoor is er het anomalize-pakket voor anomaliedetectie in tijdreeksanalyse. Het is een nette, op tijd gebaseerde anomaliedetectie-algoritme dat schaalbaar is van één tot veel tijdreeksen.

Er zijn verschillende beschikbare pakketten en methoden die bijdragen aan de werking, of je kunt zeggen dat het een combinatie is van bestaande bronnen met een schaalbare aanpak.

De open-sourceprojecten die hebben geholpen, zijn onder andere:

  • Twitter’s AnomalyDetection-pakket: beschikbaar op Github (cran anaomalyDetection is ander werk).
  • Rob Hyndman’s functie forecast::tsoutliers() uit het forecast-pakket.
  • Javier Lopez-de-lacalle’s pakket tsoutliers op CRAN.

Deze pakketten en functies worden geïntegreerd in een schaalbare workflow.

De workflow van anomalize is opgedeeld in drie delen:

  • Tijdreeksdecompositie met time_decompose().
  • Anomaliedetectie op de remainder met anomalize().
  • Transformatie van onder- en bovengrens van anomalieën met time_recompose().

Workflow van anomaliedetectie

Decompositie van tijdreeksen

De eerste stap is de tijdreeksdecompositie met time_decompose(). De gemeten of numerieke waarde waarop je voor een bepaalde groep wilt detecteren, wordt opgesplitst in vier kolommen: observed, season, trend en remainder. De standaardmethode voor decompositie is stl, een seizoensdecompositie met een Loess-smoother.

Loess-regressie is de meest gebruikte methode om een volatiele tijdreeks te vergladden. Er worden meerdere regressies gefit in de lokale buurt; je kunt ook zeggen dat de data wordt opgedeeld en dat op elk deel regressie wordt toegepast. Dat is nuttig bij tijdreeksen, omdat we de begrenzing in de tijd kennen, die hier de X-variabele is. Deze methode werkt goed wanneer de trend domineert over de seizoensinvloed van de tijdreeks.

Hier is trend de langetermijngroei die over veel observaties plaatsvindt, en seizoensinvloed het cyclische patroon dat optreedt op een dagelijkse cyclus per minuut of uur, of wekelijks.

Er is een tweede techniek voor seizoensdecompositie in tijdreeksen op basis van de mediaan: de Twitter-methode, die ook wordt gebruikt in het AnomalyDetection-pakket. Deze is identiek aan STL voor het verwijderen van de seizoenscomponent. Het verschil zit in het verwijderen van de trend: die gebruikt een stukgewijze mediaan van de data (één of meerdere medianen gesplitst op opgegeven intervallen) in plaats van een smoother te fitten. Deze methode werkt goed wanneer de seizoensinvloed domineert over de trend in de tijdreeks.

Laten we het hebben over de output van de functie time_decompose(). Zoals hierboven besproken produceert die 4 kolommen:

  • observed: De werkelijke waarden.
  • season: De seizoens- of cyclische component. Standaard is er een wekelijkse seizoensinvloed.
  • trend: De langetermijntrend. Standaard is de span 3 maanden.
  • remainder: Wordt gebruikt voor het analyseren van uitschieters. Het is simpelweg observed minus zowel season als trend.

De functie time_decompose() heeft een argument merge. Door dit op TRUE te zetten, behoud je de originele data samen met de geproduceerde kolommen.

# 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...

Anomalieën in de remainders detecteren

Nadat de tijdreeksanalyse is voltooid en de remainder de gewenste eigenschappen heeft om anomalieën te detecteren, worden opnieuw drie nieuwe kolommen gemaakt.

  • remainder_l1: De ondergrens van de remainder.
  • remainder_l2: De bovengrens van de remainder.
  • anomaly: Kolom die aangeeft of de observatie een anomalie is of niet.

Anomalieën zijn high-leveragepunten die de verdeling vertekenen. Anomalize implementeert twee methoden die bestand zijn tegen high-leveragepunten:

  • IQR: Interkwartielafstand
  • GESD: Generalized Extreme Studentized Deviate Test

IQR

Dit is vergelijkbaar met de methode die in de functie tsoutliers() van het forecast-pakket wordt gebruikt. Bij IQR neem je een verdeling en gebruik je de 25%- en 75%-kwartielen om de verdeling van de remainder vast te stellen. De grenzen worden standaard ingesteld op een factor 3 keer boven en onder de interkwartielafstand; elke remainder buiten die grenzen wordt als anomalie beschouwd.

GESD

Bij GESD worden anomalieën stapsgewijs geëvalueerd door de grootste overtreders te verwijderen en de teststatistieken en kritieke waarden opnieuw te berekenen; eenvoudiger gezegd: het bereik wordt iteratief herberekend nadat anomalieën zijn geïdentificeerd.

Beide, IQR en GESD, hebben hun voor- en nadelen. IQR is relatief sneller omdat er geen lussen in zitten, maar het is niet zo nauwkeurig als GESD, aangezien anomalieën de mediaan scheeftrekken, wat bij GESD wordt ondervangen.

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", "...

Transformatie van onder- en bovengrens van anomalieën

De laatste stap van de workflow is het creëren van onder- en bovengrenzen rond de observed-waarden met time_recompose. Daarbij worden season, trend, remainder_l1 en remainder_l2 opnieuw samengesteld tot nieuwe grenzen:

  • recomposed_l1: De ondergrens van uitschieters rond de observed-waarden.
  • recomposed_l2: De bovengrens van uitschieters rond de observed-waarden.
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...

Anomalieën plotten

purrr_anomaly%>%
  plot_anomaly_decomposition()+
  ggtitle("Plotting Anomalies")
anomalieën plotten

Parameters afstemmen

Een anomaliedetector modelleren is niet compleet zonder de parameters te finetunen, die volledig afhankelijk zijn van de data.

Laten we de parameters afstellen. De parameters van elk niveau van de workflow verschillen, omdat elk niveau zijn eigen taak uitvoert.

Parameters van decompositie aanpassen

Zoals eerder besproken zijn trend en seizoensinvloed essentieel voor het decomponeren van een tijdreeks. Dus moet je ook de frequentie en trend van de functie time_decompose aanpassen. Standaard worden de waarden automatisch ingesteld: 7 dagen voor de frequentie in beide methoden (STL, Twitter) en voor de trend 91 dagen voor STL en 85 dagen voor Twitter.

Je kunt beide of één van de argumenten naar wens tweaken, maar let goed op vóór je aanpast: wijzigen zonder observatie kan leiden tot overfitting of underfitting van het decompositieproces.

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

Parameters voor anomaliedetectie aanpassen

Zoals hierboven besproken wordt de anomalie hier bepaald op basis van de waarden van de remainder door grenzen te berekenen om uitschieters te categoriseren. De parameters alpha en max_anoms sturen de functie anomalize(). Simpel gezegd: alpha regelt de bandbreedte van de grens; standaard is die 0,05. Door de waarde te verlagen, vergroot je de band, waardoor het lastiger wordt voor een punt om als anomalie te gelden.

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

De parameter max_anom bepaalt het percentage data dat een anomalie mag zijn. Deze parameter is nuttig wanneer alpha lastig af te stemmen is en je je wilt richten op de meest uitgesproken anomalieën.

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")
tijdreeks
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")
tijdreeks

Referenties

Wil je meer leren over R? Volg dan DataCamp’s cursus Introduction to Time Series Analysis.

Bekijk ook onze Time Series Analysis using R: Tutorial.

Onderwerpen

Gerelateerd