Обнаружение пиков во временных рядах
В настоящее время я работаю над небольшим проектом, в котором я хочу сравнить два временных ряда. Мера сходства действительно расплывчата, они считаются похожими, если два временных ряда грубо имеют одинаковую форму.
Поэтому я подумал про себя: "Ну, если им нужна только одна и та же форма, я просто сравниваю пики двух временных рядов, если пики находятся в одном и том же положении, то, конечно же, временные ряды будут аналогичными"
Теперь моя проблема - найти хороший алгоритм обнаружения пика. Я использовал Google, но я только придумал бумагу Простые алгоритмы для обнаружения пиков в Time-Series. Проблема в том, что алгоритмы, описанные в этой статье, хорошо работают с действительно экстремальными и тонкими пиками, но в большинстве случаев мои временные ряды довольно плоские пики, чтобы они не были обнаружены.
Кто-нибудь знает, где я могу найти или найти алгоритм, который будет определять пики, показанные на следующем изображении?
![time-series]()
Ответы
Ответ 1
Кажется, вы просто ищете инверсию наклона (от положительного к отрицательному и наоборот). Грубый java-алгоритм может быть (не проверен):
List<Point> points = ... //all the points in your curve
List<Point> extremes = new ArrayList<Point> ();
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;
}
Наконец, хороший способ измерения подобия - это корреляция. В вашем случае я бы посмотрел на корреляцию% move (другими словами, вы хотите, чтобы ваши 2 серии поднимались или опускались одновременно) - это обычно то, что делается в финансах, где вы вычисляете корреляцию между двумя доходами активов, например
- создайте 2 новые серии с перемещением% для каждой точки 2-й серии
- вычислить корреляцию между этими двумя сериями
Вы можете больше узнать о например, вернете корреляции. Итак, если ваши значения:
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%
И вы вычисляете соотношение этих двух рядов возвращений (в этом примере: 0,96), чтобы получить оценку того, насколько 2 кривые выглядят одинаково. Вы можете настроить результат для дисперсии (т.е. Если одна форма имеет гораздо более широкий диапазон, чем другой).
Ответ 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<Integer> ext = new ArrayList<Integer> ();
for (int i = 0; i<f.length-2; i++) {
if ((f[i+1]-f[i])*(f[i+2]-f[i+1]) <= 0) { // changed sign?
ext.add(i+1);
}
}
// now you have the indices of the extremes in your list `ext`
Это будет хорошо работать с гладкими рядами. Если у вас есть определенный вариант в ваших данных, вы должны сначала перенести его через фильтр нижних частот. Очень простая реализация фильтра нижних частот будет скользящим средним (каждая точка заменяется средним значением ближайших значений k, а k - размером окна).
Ответ 3
Алгоритм максимума, предложенный Эли Биллауэром, работает очень хорошо и легко реализуется:
http://www.billauer.co.il/peakdet.html
Алгоритм работает особенно хорошо с шумными сигналами, когда методы с использованием первой производной не работают.
Ответ 4
Если вам нужно что-то статистически более здоровое, вы можете измерить кросс-корреляцию между двумя сериями. Вы можете проверить Wikipedia или этот сайт.
Ответ 5
Я не уверен в корреляции между временными рядами или конкретными алгоритмами обнаружения пиков, но здесь есть небольшой алгоритм максимального пикового обнаружения, который я написал. Он не обнаруживает минимальные пики, но может быть легко расширен, чтобы сделать это, изменив операции в цикле for.
List<XYDataItem> 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<XYDataItem> pointList = (List<XYDataItem>) 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 как двойное. Это можно легко заменить чем-то эквивалентным.