如果我有一个生成如下图的数据集,我将如何算法确定所显示峰的x值(在本例中为其中的三个):
如果我有一个生成如下图的数据集,我将如何算法确定所显示峰的x值(在本例中为其中的三个):
Answers:
一种通用方法是对数据进行平滑处理,然后通过将局部最大滤波器与平滑处理进行比较来找到峰值。在R
:
argmax <- function(x, y, w=1, ...) {
require(zoo)
n <- length(y)
y.smooth <- loess(y ~ x, ...)$fitted
y.max <- rollapply(zoo(y.smooth), 2*w+1, max, align="center")
delta <- y.max - y.smooth[-c(1:w, n+1-1:w)]
i.max <- which(delta <= 0) + w
list(x=x[i.max], i=i.max, y.hat=y.smooth)
}
它的返回值包括局部最大值(x
)的参数(可回答问题),以及局部最大值出现的x和y数组的索引(i
)。
有两种参数可根据情况调整: w
是用于计算局部最大值的窗口的半角。(其值应实质上小于数据数组长度的一半。)较小的值将拾取微小的局部凸起,而较大的值将经过这些凸起。另一个(在此代码中未明确span
列出)是loess
平滑器的参数。(它通常介于零和一之间;它反映的是窗口宽度与x值范围的比例。)较大的值将更积极地平滑数据,从而使局部凹凸完全消失。
为了看到这种调整的效果,让我们创建一个小的测试函数来绘制结果:
test <- function(w, span) {
peaks <- argmax(x, y, w=w, span=span)
plot(x, y, cex=0.75, col="Gray", main=paste("w = ", w, ", span = ", span, sep=""))
lines(x, peaks$y.hat, lwd=2) #$
y.min <- min(y)
sapply(peaks$i, function(i) lines(c(x[i],x[i]), c(y.min, peaks$y.hat[i]),
col="Red", lty=2))
points(x[peaks$i], peaks$y.hat[peaks$i], col="Red", pch=19, cex=1.25)
}
以下是一些适用于某些合成的,噪音较小的数据的实验。
x <- 1:1000 / 100 - 5
y <- exp(abs(x)/20) * sin(2 * x + (x/5)^2) + cos(10*x) / 5 + rnorm(length(x), sd=0.05)
par(mfrow=c(3,1))
test(2, 0.05)
test(30, 0.05)
test(2, 0.2)
宽窗口(中间图)或更积极的平滑(底部图)消除了在顶部图中检测到的局部最大值。此处最好的组合可能是宽窗口且只能进行柔和的平滑处理,因为积极的平滑处理似乎会移动这些峰(请参阅底部图中的中点和右点,并将它们的位置与原始数据的表观峰进行比较)。在这个例子中,w=50
并span=0.05
做了伟大的工作(未显示)。
请注意,未检测到端点处的局部最大值。这些可以单独检查。(为此,argmax
返回平滑的y值。)
与针对通用工作的更为正式的建模相比,该方法具有多个优点:
它没有采用任何先入为主的数据模型。
可以适应数据特征。
它可以适用于检测一个人感兴趣的峰的种类。
w
和span
,同时也发现,值越高,span
发生转变的峰值。感觉甚至这些步骤也可以自动化。例如,对于第一个问题,如果我们可以评估发现的峰的质量,则可以optimize
使用这些参数!对于第二个问题,例如,在发现的峰的任一侧选择一个窗口并寻找更高的值。
正如我在评论中提到的,如果时间序列似乎是周期性拟合,则谐波回归模型提供了一种通过应用一阶和二阶导数测试来平滑函数并识别峰的方法。Huber指出了一种非参数测试,当存在多个峰并且该函数不一定是周期性的时,它具有优势。但是没有免费的午餐。尽管他提到了他的方法的优点,但如果参数模型合适,可能会有缺点。这始终是使用非参数技术的另一面。尽管它避免了参数假设,但当参数假设合适时,参数方法会更好。他的过程也没有充分利用数据中的时间序列结构。
我认为尽管指出建议程序的优点是适当的,但指出潜在的缺点也很重要。我的方法和Huber都以有效的方式找到了高峰。但是,我认为当局部最大值低于先前确定的最高峰时,他的程序需要做更多的工作。