Подтвердить что ты не робот

STL-декомпозиция временных рядов с отсутствующими значениями для обнаружения аномалий

Я пытаюсь обнаружить аномальные значения во временном ряду климатических данных с некоторыми отсутствующими наблюдениями. Поиск в Интернете я нашел много доступных подходов. Из них stl-декомпозиция кажется привлекательной, в смысле удаления трендовых и сезонных компонентов и изучения остатка. Чтение STL: процедура разложения сезонных трендов на основе Loess, stl, по-видимому, является гибким в определении параметров для назначения изменчивости, не зависящих от выбросов и возможно, несмотря на отсутствующие значения. Однако, пытаясь применить его в R, с четырьмя годами наблюдений и определением всех параметров в соответствии с http://stat.ethz.ch/R-manual/R-patched/library/stats/html/stl.html, я столкнулся с ошибкой:

"временные ряды содержат внутренние NA", когда na.action = na.omit,

и "ряды не являются периодическими или имеют менее двух периодов", когда na.action = na.exclude.

Я дважды проверил, что частота правильно определена. Я видел соответствующие вопросы в блогах, но не нашел предложений, которые могли бы решить эту проблему. Невозможно ли применить stl в серии с отсутствующими значениями? Я очень неохотно интерполирую их, поскольку я не хочу вводить (и, следовательно, обнаруживать...) артефакты. По той же причине я не знаю, насколько целесообразно было бы использовать подходы ARIMA (и если отсутствующие значения все равно будут проблемой).

Пожалуйста, поделитесь, если вы знаете способ применения stl в серии с отсутствующими значениями, или если вы считаете, что мои варианты методологически не звучат, или если у вас есть какие-либо предложения. Я совершенно новичок в этой области и перегружен грудами (по-видимому...) соответствующей информации.

4b9b3361

Ответ 1

В начале stl найти

x <- na.action(as.ts(x))

и вскоре после этого

period <- frequency(x)
if (period < 2 || n <= 2 * period) 
    stop("series is not periodic or has less than two periods")

То есть stl ожидает, что x будет ts объектом после na.action(as.ts(x)) (иначе period == 1). Сначала проверим na.omit и na.exclude.

Ясно, что в конце getAnywhere("na.omit.ts") находим

if (any(is.na(object))) 
    stop("time series contains internal NAs")

что является простым и ничего не может быть сделано (na.omit не исключает NAs из объектов ts). Теперь getAnywhere("na.exclude.default") исключает наблюдения NA, но возвращает объект класса exclude:

    attr(omit, "class") <- "exclude"

и это совсем другая ситуация. Как упоминалось выше, stl ожидает, что na.action(as.ts(x)) будет ts, но na.exclude(as.ts(x)) имеет класс exclude.

Следовательно, если выполнено условие NAs исключения, например,

nottem[3] <- NA
frequency(nottem)
# [1] 12
na.new <- function(x) ts(na.exclude(x), frequency = 12)
stl(nottem, na.action = na.new, s.window = "per")

работает. В общем случае stl не работает с NA значениями (т.е. С na.action = na.pass), он падает глубже в Fortran (см. Полный исходный код здесь):

z <- .Fortran(C_stl, ...

Альтернативы na.new не радуют:

  • na.contaguous - находит самое длинное последовательное растяжение не пропущенных значений в объекте временного ряда.
  • na.approx, na.locf из zoo или некоторой другой интерполяционной функции.
  • Не уверен в этом, но еще одна реализация Fortran для Python здесь. Можно использовать Python, возможно, установить R из источника после некоторых изменений, если этот модуль действительно позволяет пропустить значения.

Как мы видим в paper, нет простой процедуры для пропущенных значений (например, аппроксимировать их в самом начале) который может быть применен к временному ряду перед вызовом stl. Поэтому, учитывая тот факт, что оригинальная реализация довольно длинная, я подумал бы о некоторых других альтернативах, чем целая новая реализация.

Обновить: довольно оптимальный выбор во многих аспектах, если NAs может быть na.approx из zoo, поэтому давайте проверим его производительность, т.е. сравним результаты stl с полным набор данных и результаты при наличии некоторого количества NAs, используя na.approx. Я использую MAPE как меру точности, но только для тренда, потому что сезонный компонент и остаток пересекают ноль, и это искажает результат. Позиции для NAs выбираются случайным образом.

library(zoo)
library(plyr)
library(reshape)
library(ggplot2)
mape <- function(f, x) colMeans(abs(1 - f / x) * 100)

stlCheck <- function(data, p = 3, ...){
  set.seed(20130201)
  pos <- lapply(3^(0:p), function(x) sample(1:length(data), x))
  datasetsNA <- lapply(pos, function(x) {data[x] <- NA; data})
  original <- data.frame(stl(data, ...)$time.series, stringsAsFactors = FALSE)
  original$id <- "Original"
  datasetsNA <- lapply(datasetsNA, function(x) 
    data.frame(stl(x, na.action = na.approx, ...)$time.series, 
               id = paste(sum(is.na(x)), "NAs"), 
               stringsAsFactors = FALSE))
  stlAll <- rbind.fill(c(list(original), datasetsNA))
  stlAll$Date <- time(data)
  stlAll <- melt(stlAll, id.var = c("id", "Date"))
  results <- data.frame(trend = sapply(lapply(datasetsNA, '[', i = "trend"), mape, original[, "trend"]))
  results$id <- paste(3^(0:p), "NAs")
  results <- melt(results, id.var = "id")
  results$x <- min(stlAll$Date) + diff(range(stlAll$Date)) / 4
  results$y <- min(original[, "trend"]) + diff(range(original[, "trend"])) / (4 * p) * (0:p)
  results$value <- round(results$value, 2)
  ggplot(stlAll, aes(x = Date, y = value, colour = id, group = id)) + geom_line() + 
    facet_wrap(~ variable, scales = "free_y") + theme_bw() +
    theme(legend.title = element_blank(), strip.background = element_rect(fill = "white")) + 
    labs(x = NULL, y = NULL) + scale_colour_brewer(palette = "Set1") +
    lapply(unique(results$id), function(z)
      geom_text(data = results, colour = "black", size = 3,
                aes(x = x, y = y, label = paste0("MAPE (", id, "): ", value, "%"))))
}

nottem, 240 наблюдений

stlCheck(nottem, s.window = 4, t.window = 50, t.jump = 1)

enter image description here

co2, 468 наблюдений

stlCheck(log(co2), s.window = 21)

enter image description here

mdeaths, 72 наблюдения

stlCheck(mdeaths, s.window = "per")

enter image description here

Визуально мы видим некоторые различия в тенденции в случаях 1 и 3. Но эти различия довольно малы в 1, а также удовлетворительны в 3, учитывая размер выборки (72).

Ответ 2

Поймите, это старый вопрос, но я думал, что обновляюсь, поскольку в R есть новый stl пакет, который называется stlplus. Вот его главная страница github. Вы можете установить его из CRAN с помощью install.packages("stlplus") или непосредственно из github с помощью devtools::install_github("hafen/stlplus").