Как найти все пики и впадины приливных данных?

Я работаю с некоторыми данными об океанских приливах, которые структурированы следующим образом:

$data = array('date' => array('time' => array('predicted','observed')));

Вот пример реальных данных, которые я использую: http://pastebin.com/raw.php?i=bRc2rmpG

И это моя попытка найти высокие/низкие значения: http://pastebin.com/8PS1frc0

Текущие проблемы с моим кодом:

  • Когда показания колеблются (как видно в диапазоне от 11/14/2010=>11:30:00 до 11/14/2010=>11:54:00 в выборке данных), это создает «колебание» в логике направления. Это создает ошибочные пик и впадину. Как этого избежать/исправить?

Примечание. Мой метод очень "специальный". Я полагал, что мне не понадобятся какие-то замечательные математические штуки, поскольку я не пытаюсь найти какие-либо средние значения, приближения или будущие оценки. Я был бы очень признателен за пример кода лучшего метода, даже если это означает отказ от кода, который я написал до сих пор.


person drudge    schedule 12.11.2010    source источник
comment
Если у вас нет ошибок, маловероятно, что люди будут просматривать ваш код.   -  person Fosco    schedule 12.11.2010
comment
Я обновил свой вопрос двумя проблемами, которые возникли, когда я начал использовать фактические данные, а не тестовые значения. Я предоставил экспорт некоторых реальных данных, которые я использую.   -  person drudge    schedule 13.11.2010
comment
посмотрите на stackoverflow.com/a/10303971/987850 этот метод существует в Python, C и Fortran - billauer.co.il/peakdet.html   -  person 23W    schedule 26.03.2013


Ответы (7)


Я полагаю, вы ищете локальные минимумы и максимумы? Это очень легко сделать:

<?php

$data = array(1, 9, 4, 5, 6, 9, 9, 1);

function minima($data, $radius = 2)
{
  $minima = array();

  for ($i = 0; $i < count($data); $i += $radius)
  {
    $minima[] = min(array_slice($data, $i, $radius));
  }

  return $minima;
}

function maxima($data, $radius = 2)
{
  $maxima = array();

  for ($i = 0; $i < count($data); $i += $radius)
  {
    $maxima[] = max(array_slice($data, $i, $radius));
  }

  return $maxima;
}

print_r(minima($data));
print_r(maxima($data));

?>

Вам просто нужно указать радиус поиска, и он вернет вам массив локальных минимумов и максимумов данных. Он работает просто: он разрезает массив на отрезки длиной $radius и находит минимум этого отрезка. Этот процесс повторяется для всего набора данных.

Будьте осторожны с радиусом: обычно вы хотите выбрать радиус как среднее расстояние от пика до впадины данных, но вам придется найти его вручную. По умолчанию он равен 2, и он будет искать только минимумы/максимумы в радиусе 2, что, вероятно, даст ложные срабатывания с вашим набором данных. Выбирайте радиус с умом.

Вам придется взломать его в своем сценарии, но это совсем не должно быть слишком сложно.

Удачи!

person Blender    schedule 22.11.2010

Мне приходилось выполнять аналогичные задачи на зашумленных физиологических данных. На мой взгляд, у вас проблема с формированием сигнала. Вот процесс, который сработал для меня.

  1. Преобразуйте значения времени в секунды, т. е. (ЧЧ*3600)+(ММ*60)+(СС), чтобы сгенерировать числовое значение «X».
  2. Сгладьте результирующие массивы X и Y с помощью скользящего окна, скажем, шириной 10 точек. На этом шаге вы также можете рассмотреть возможность фильтрации данных с избыточными и/или фиктивными временными метками.
  3. Выполните обнаружение фазы индикации путем сравнения сглаженных Y[1] и Y[0]. Как и в сообщении выше, если (Y[1] > Y[0]), вы можете предположить, что данные поднимаются до пика. Если (Y[1] ‹ Y[0]), вы можете предположить, что данные опускаются до минимума.
  4. Как только вы узнаете начальную фазу, можно выполнить обнаружение пика и минимума, как описано выше: если Y[i] > Y[i+1] и Y[i] ‹ Y[i-1], вы столкнулись с пиком.
  5. Вы можете оценить время пика/минимума, спроецировав сглаженное значение X обратно в исходные данные X, учитывая размер скользящего окна (чтобы компенсировать «задержку сигнала», вызванную скользящим окном). Полученное значение времени (в секундах) затем можно преобразовать обратно в формат ЧЧ:ММ:СС для отчетности.
person Throwback1986    schedule 15.11.2010
comment
1) Я не вижу, как это полезно. 2) В моих данных не будет фиктивных или избыточных временных меток. 3) Я уже делаю это с переменной $direction. 4) Я уже делаю это с переменными $last и $current. 5) Как указано в вопросе, я ничего не пытаюсь оценить. - person drudge; 16.11.2010
comment
Из вашего первоначального описания проблемы я понял, что 00:00:00 и 23:54:00 являются посторонними значениями (т.е. ваш вопрос, как я могу их игнорировать). Таким образом, мое предположение о фиктивных данных. Перечитав пост несколько раз, я думаю, что понимаю, что сами значения разумны, но ваш алгоритм ошибочно выбирает их. - person Throwback1986; 16.11.2010
comment
что касается комментария 4 - вы не выполняете обнаружение пика и минимума на сглаженных данных. Сглаживание данных может помочь решить вашу проблему обнаружения ложных сигналов — см. ваш комментарий относительно колебания логики направления. - person Throwback1986; 16.11.2010
comment
Что касается комментария 5 - если вы выполняете обнаружение пика/минимума на сглаженных данных, то обнаруженная вами точка перегиба может находиться между одной из ваших фактических точек выборки (более того, она будет отставать от исходных данных на 1/2 ширины сглаживания). окно). Таким образом, вы должны спроецировать точку перегиба обратно в исходные данные, чтобы получить одну из ваших исходных выборок данных. Отсюда мое использование слова оценка. - person Throwback1986; 16.11.2010
comment
Что касается комментария 1 - у вас проблемы с определением желаемых точек перегиба, потому что вы не видите полезности в сглаживании данных ;-) - person Throwback1986; 16.11.2010
comment
@ Throwback1986: Спасибо за пояснения. Я понимаю, почему сглаженные данные были бы полезны, но понятия не имею, как начать превращать эту идею в код. - person drudge; 16.11.2010
comment
Предполагая, что размер окна равен 3, вы можете вычислить сглаженное значение Ys[0] следующим образом: Ys[0] = (Y[0] + Y[1] + y[2])/3. В более широком смысле Ys[1] = (Y[1]+Y[2]+Y[3])/3 и т. д. Пара вложенных циклов for добьется цели (сложно проиллюстрировать в комментарии!) - person Throwback1986; 16.11.2010
comment
Обратите внимание, что приведенное выше вызывает задержку сигнала Ys, которая пропорциональна размеру окна. Вы также можете рассмотреть окно сглаживания, которое не отстает, но не может сглаживать края. Продолжая приведенный выше пример: Ys[1] можно вычислить как (Y[0]+Y[1]+Y[3])/3, а Ys[2] = (Y[1]+Y[2]+Y [3])/3 и т. д. Обратите внимание, что Ys[0] должен обрабатываться таким образом, который имеет смысл для вашего приложения (т.е. Ys[0]=0), что может быть приемлемым. - person Throwback1986; 16.11.2010

Я не читал его подробно, но ваш подход кажется очень нестандартным. Вероятно, более правильным способом было бы подогнать его под функцию

 f(A,B,w,p;t)=Asin(wt+p)+B 

используя такой метод, как нелинейный метод наименьших квадратов (который, к сожалению, приходится решать с помощью итерационный метод). Глядя на ваши образцы данных, кажется, что это было бы хорошо. Когда вы рассчитали w и p, легко найти пики и впадины, просто взяв производную функции по времени и найдя ноль:

t = (pi(1+2n)-2p)/w

Но я полагаю, что если ваш код действительно делает то, что вы хотите, нет смысла все усложнять. Перестаньте сомневаться в себе. :)

person Emil H    schedule 12.11.2010
comment
Я не думаю, что это совсем то, что я ищу. Мне просто нужно найти значения из предоставленных данных. - person drudge; 14.11.2010

Проблема в том, что я думаю, что наблюдения являются наблюдениями и могут содержать небольшие ошибки. Это как минимум нужно учитывать. Например:

  • Меняйте направление только в том случае, если по крайней мере 2 следующих входа также находятся в том же направлении.

  • Не позволяйте принимать решения на основе данных о слишком маленькой разнице. Отбросьте незначащие числа. Вероятно, будет намного лучше, если вы скажете $error = 0.10; и измените свои условия на if $previous - $error > $current и так далее.

person Lucas Moeskops    schedule 22.11.2010

Насколько точным должно быть обнаружение пиков/впадин? Если вам просто нужно найти точную запись, где происходит пик или впадина, разве недостаточно проверить точки перегиба?

например учитывая запись в позиции «i», если запись [i-1] и запись [i+1] «выше», чем запись [i], у вас есть долина. и если запись [i-1] и запись [i+1] меньше, чем запись [i], у вас есть пик. Пока ваша частота дискретизации превышает скорость изменения прилива (посмотрите частоту Найквиста), этот процесс должен дать вам пики/впадины ваших данных.

Если вам нужно сгенерировать график из этого и попытаться экстраполировать более точные временные точки для пиков/впадин, то вам предстоит дополнительная работа.

person Marc B    schedule 12.11.2010
comment
Это только должно быть точным для данных, которые предоставляются. Массив данных (структуру которого я не могу изменить) не использует числовой индекс. Также бывают (нечасто, но они существуют) случаи, когда значение одинаково для двух записей, поэтому проверка, например, с помощью [i-1] < [i] > [i+1] не обязательно будет работать. - person drudge; 13.11.2010

Одним из способов может быть определение абсолютного или относительного отклонения, после которого вы классифицируете дальнейшие пики/впадины как новые, а не колебания вокруг существующего пика/впадины.

В настоящее время $direction определяет, находите ли вы пик или впадину, поэтому вместо перехода в другое состояние (нахождение впадины или пика) после изменения знака производной вы можете рассмотреть возможность изменения состояния только тогда, когда отклонение от текущего пика/ корыто достаточно "большое".

person lijie    schedule 22.11.2010

Учитывая, что вы никогда не должны видеть два максимума или 2 минуты менее чем за 12 часов, простым решением было бы использовать скользящие окна в 3-5 часов или около того и найти максимум и минимум. Если это окажется в первые или последние 30 минут, игнорируйте его.

В качестве примера приведем следующие данные:

1 2 3 4 5 6 5 6 7 8 7 6 5 4 3 2 1 2

и окно размером 8, при этом первое и последнее 2 игнорируются и только смотрят, что вы увидите:

1 2 | 3 4 5 6 | 5 6,  max = 6, ignore = Y
2 3 | 4 5 6 5 | 6 7,  max = 7, ignore = Y
3 4 | 5 6 5 6 | 7 8,  max = 8, ignore = Y
4 5 | 6 5 6 7 | 8 7,  max = 8, ignore = Y
5 6 | 5 6 7 8 | 7 6,  max = 8, ignore = N
6 5 | 6 7 8 7 | 6 5,  max = 8, ignore = N
5 6 | 7 8 7 6 | 5 4,  max = 8, ignore = N
6 7 | 8 7 6 5 | 4 3,  max = 8, ignore = N
7 8 | 7 6 5 4 | 3 2,  max = 8, ignore = Y
8 7 | 6 5 4 3 | 2 1,  max = 8, ignore = Y
7 6 | 5 4 3 2 | 1 2,  max = 7, ignore = Y
person BCS    schedule 22.11.2010
comment
Я пытался сделать что-то в этом роде, но все пошло прахом, когда пик/впадина действительно происходили в те периоды времени. - person drudge; 22.11.2010
comment
@jnpcl: Если эти периоды времени относятся к первым/последним 30 минутам, то я не вижу проблемы. Кажется, у вас есть данные с разрешением 6 минут, поэтому я ожидаю, что каждый пик/впадина будет в эти периоды примерно 10 раз, но каждый из них также будет виден во внутреннем периоде по 20 раз каждый, поэтому вы все равно получите это. - person BCS; 23.11.2010