Ответ 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)
co2
, 468 наблюдений
stlCheck(log(co2), s.window = 21)
mdeaths
, 72 наблюдения
stlCheck(mdeaths, s.window = "per")
Визуально мы видим некоторые различия в тенденции в случаях 1 и 3. Но эти различия довольно малы в 1, а также удовлетворительны в 3, учитывая размер выборки (72).