时间序列中的峰值检测

我目前正在开展一个小项目,我想比较两个时间序列。 相似性度量实际上是模糊的,如果两个时间序列大致具有相同的形状,则它们被认为是相似的。

所以我对自己说“好吧,如果它们只需要具有相同的形状,我只需比较两个时间序列的峰值,如果峰值处于相同的位置,那么时间序列肯定会相似”

我现在的问题是为峰值检测找到一个好的算法。 我使用谷歌,但我只想出了时间序列中的峰值检测简易算法 。 问题是,本文中描述的算法适用于非常极端和薄的峰值,但在大多数情况下,我的时间序列具有相当平坦的峰值,因此不会被检测到。

有没有人知道我在哪里可以找到或搜索能够检测下图所示峰值的算法?

时间序列

你似乎只是寻找斜率反转(从正面到负面,反之亦然)。 粗略的java算法可能(未经测试):

List points = ... //all the points in your curve List extremes = new ArrayList (); double previous = null; double previousSlope = 0; for (Point p : points) { if (previous == null) { previous = p; continue; } double slope = p.getValue() - previous.getValue(); if (slope * previousSlope < 0) { //look for sign changes extremes.add(previous); } previousSlope = slope; previous = p; } 

最后,衡量相似性的一个好方法是相关性。 在你的情况下,我会看看%移动相关性(换句话说,你希望你的2系列同时上升或下降) - 这通常是在财务中做的,你计算2个资产回报之间的相关性,例如:

  • 为2系列中的每个点创建2个新系列,其中%move
  • 计算那两个系列之间的相关性

例如,您可以在此处阅读有关退货相关性的更多信息。 总之,如果您的值是:

 Series 1 Series 2 100 50 98 49 100 52 102 54 

“回归”系列将是:

 Series 1 Series 2 -2.00% -2.00% +2.04% +6.12% +2.00% +3.85% 

然后计算这2个回归序列的相关性(在本例中为0.96),以计算2条曲线看起来相似的程度。 您可能希望调整方差的结果(即,如果一个形状的范围比另一个更宽)。

您可以使用非常简单的局部极值检测器:

 // those are your points: double[] f = {1, 2, 3, 4, 5, 6, 5, 4, 7, 8, 9, 3, 1, 4, 6, 8, 9, 7, 4, 1}; List ext = new ArrayList (); for (int i = 0; i 

这对顺利系列很有用。 如果您的数据存在某种变化,则应首先通过低通滤波器。 低通滤波器的一个非常简单的实现是移动平均值(每个点由最近的k值的平均值代替,其中k是窗口大小)。

Eli Billauer提出的peakdet算法运行良好,易于实现:

http://www.billauer.co.il/peakdet.html

该算法特别适用于噪声信号,其中使用一阶导数的方法失败。

如果你想要统计上更健全的东西,你可以测量两个系列之间的互相关 。 您可以查看维基百科或本网站 。

我不确定时间序列或特定峰值检测算法之间的相关性,但这里是我写的一点点最大峰值检测算法。 它不会检测到最小峰值,但可以通过反转for循环中的操作来轻松扩展。

 List maxPoints = ... //list to store the maximums XYDataItem leftPeakPoint = new XYDataItem(0, 0); int leftPeakPointIndex = 0; XYDataItem rightPeakPoint = new XYDataItem(0, 0); boolean first = true; int index = -1; List pointList = (List) lrpSeries.getItems(); for (XYDataItem point : pointList) { index++; if (first) { //initialize the first point leftPeakPoint = point; leftPeakPointIndex = index; first = false; continue; } if (leftPeakPoint.getYValue() < point.getYValue()) { leftPeakPoint = point; leftPeakPointIndex = index; rightPeakPoint = point; } else if (leftPeakPoint.getYValue() == point.getYValue()) { rightPeakPoint = point; } else { //determine if we are coming down off of a peak by looking at the Y value of the point before the //left most point that was detected as a part of a peak if (leftPeakPointIndex > 0) { XYDataItem prev = pointList.get(leftPeakPointIndex - 1); //if two points back has a Y value that is less than or equal to the left peak point //then we have found the end of the peak and we can process as such if (prev.getYValue() <= leftPeakPoint.getYValue()) { double peakx = rightPeakPoint.getXValue() - ((rightPeakPoint.getXValue() - leftPeakPoint.getXValue()) / 2D); maxPoints.add(new XYDataItem(peakx, leftPeakPoint.getYValue())); } } leftPeakPoint = point; leftPeakPointIndex = index; rightPeakPoint = point; } } 

其结果将检测到的峰值集中在平坦部分上,其中连续数据点的Y值相同。 XYDataItem只是一个包含X和Y值作为double的类。 这很容易被等同的东西取代。