我正在尝试检测气候数据的时间序列中的异常值,但缺少一些观察结果。在网上搜索我找到了许多可用的方法。其中,从消除趋势和季节性成分以及研究其余部分的意义上来说,stl分解似乎很有吸引力。阅读STL: A Seasonal-Trend Decomposition Procedure Based on Loess,stl似乎可以灵活地确定分配变异性的设置,不受异常值的影响,并且可以应用尽管缺失值。但是,尝试将其应用于R,经过四年的观察并根据http://stat.ethz.ch/R-manual/R-patched/library/stats/html/stl.html定义所有参数,我遇到错误:
时间序列包含内部NAs
na.action = na.omit
和
系列不是周期性的或少于两个周期
na.action = na.exclude
时。
我仔细检查了频率是否正确定义。我在博客中看到了相关问题,但没有找到任何可以解决此问题的建议。是否无法在缺少值的系列中应用stl?我非常不愿意插入它们,因为我不想引入(并因此检测......)工件。出于同样的原因,我不知道使用ARIMA方法会是多么可取(如果缺失值仍然存在问题)。
如果您知道在缺失值的系列中应用stl的方法,或者如果您认为我的选择在方法上不合理,或者您有任何更好的建议,请分享。我在这个领域很新,并且被大量的(看似......)相关信息所震撼。
答案 0 :(得分:19)
在stl
我们find
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中更深入崩溃(请参阅完整源代码here):
z <- .Fortran(C_stl, ...
na.new
的替代方案并不令人愉快:
na.contaguous
- 在时间序列对象中找到最长的连续延伸的非缺失值。na.approx
的na.locf
,zoo
或其他插值函数。正如我们在paper中看到的那样,没有一些简单的过程可以在调用stl
之前将缺失值(例如在一开始就近似它们)这些过程应用于时间序列。因此,考虑到original implementation相当冗长的事实,我会考虑除了全新的实现之外的其他一些选择。
更新:在NAs
na.approx
来自zoo
的{{1}}时,在多个方面选择时非常优秀,所以让我们检查一下它的性能,即比较结果具有完整数据集的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中非常小,并且考虑到样本量(72)在3中也令人满意。
答案 1 :(得分:6)
意识到这是一个老问题,但我想我会更新,因为R中有一个名为stl
的新stlplus
包。 Here is its homepage on github。您可以使用install.packages("stlplus")
从CRAN安装,也可以使用devtools::install_github("hafen/stlplus")
直接从github安装。