Нахождение локальных максимумов / минимумов с помощью Numpy в одномерном массиве Numpy - PullRequest
100 голосов
/ 07 января 2011

Можете ли вы предложить функцию модуля из numpy / scipy, которая может найти локальные максимумы / минимумы в одномерном массиве numpy? Очевидно, что самый простой из подходов - взглянуть на ближайших соседей, но я бы хотел, чтобы было принято решение, которое является частью большого дистрибутива.

Ответы [ 10 ]

192 голосов
/ 21 ноября 2012

В SciPy> = 0,11

import numpy as np
from scipy.signal import argrelextrema

x = np.random.random(12)

# for local maxima
argrelextrema(x, np.greater)

# for local minima
argrelextrema(x, np.less)

Производит

>>> x
array([ 0.56660112,  0.76309473,  0.69597908,  0.38260156,  0.24346445,
    0.56021785,  0.24109326,  0.41884061,  0.35461957,  0.54398472,
    0.59572658,  0.92377974])
>>> argrelextrema(x, np.greater)
(array([1, 5, 7]),)
>>> argrelextrema(x, np.less)
(array([4, 6, 8]),)

Обратите внимание, это индексы х, которые являются локальными макс / мин. Чтобы получить значения, попробуйте:

>>> x[argrelextrema(x, np.greater)[0]]

scipy.signal также предоставляет argrelmax и argrelmin для нахождения максимумов и минимумов соответственно.

54 голосов
/ 07 января 2011

Если вы ищете все записи в массиве 1d a меньше своих соседей, вы можете попробовать

numpy.r_[True, a[1:] < a[:-1]] & numpy.r_[a[:-1] < a[1:], True]

Вы также можете сгладить свой массив перед этим шагом, используя numpy.convolve().

Не думаю, что для этого есть специальная функция.

34 голосов
/ 12 марта 2012

Для кривых с не слишком большим шумом, я рекомендую следующий небольшой фрагмент кода:

from numpy import *

# example data with some peaks:
x = linspace(0,4,1e3)
data = .2*sin(10*x)+ exp(-abs(2-x)**2)

# that's the line, you need:
a = diff(sign(diff(data))).nonzero()[0] + 1 # local min+max
b = (diff(sign(diff(data))) > 0).nonzero()[0] + 1 # local min
c = (diff(sign(diff(data))) < 0).nonzero()[0] + 1 # local max


# graphical output...
from pylab import *
plot(x,data)
plot(x[b], data[b], "o", label="min")
plot(x[c], data[c], "o", label="max")
legend()
show()

Значение +1 важно, поскольку diff уменьшает исходный индекс.

19 голосов
/ 07 ноября 2013

Другой подход (больше слов, меньше кода), который может помочь:

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

К сожалению, первая производная имеет тенденцию «усиливать» шум, поэтому, когда в исходных данных присутствует значительный шум, первую производную лучше всего использовать только после того, как к исходным данным применена некоторая степень сглаживания.

Поскольку сглаживание - это, в самом простом смысле, фильтр нижних частот, сглаживание часто лучше (ну, проще всего) выполнить с помощью ядра свертки, а «формирование» этого ядра может обеспечить удивительное количество функций, сохраняющих свойства. / Расширение возможностей. Процесс поиска оптимального ядра может быть автоматизирован с использованием различных средств, но лучшим может быть простой метод грубой силы (достаточно быстрый для поиска небольших ядер). Хорошее ядро ​​(как и предполагалось) будет сильно искажать исходные данные, но это НЕ повлияет на расположение интересующих пиков / долин.

К счастью, довольно часто подходящее ядро ​​может быть создано с помощью простого SWAG («обоснованное предположение»). Ширина сглаживающего ядра должна быть немного шире, чем самый ожидаемый «интересный» пик в исходных данных, и его форма будет напоминать этот пик (одномасштабный вейвлет). Для ядер, сохраняющих среднее значение (каким должен быть любой хороший сглаживающий фильтр), сумма элементов ядра должна быть точно равна 1,00, а ядро ​​должно быть симметричным относительно его центра (то есть иметь нечетное количество элементов.

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

Определение «правильной» (оптимальной) степени сглаживания (усиления ядра свертки) можно даже автоматизировать: сравните стандартное отклонение первых производных данных со стандартным отклонением сглаженных данных. Как соотношение двух стандартных отклонений изменяется с изменением степени сглаживания кулачка, можно использовать для прогнозирования эффективных значений сглаживания. Несколько ручных прогонов данных (которые действительно репрезентативны) должны быть всем, что нужно.

Все предыдущие решения, опубликованные выше, вычисляют первую производную, но они не рассматривают ее как статистическую меру, и при этом вышеупомянутые решения не пытаются выполнять функцию сохранения / улучшения сглаживания (чтобы помочь тонким пикам «перепрыгнуть» шум ).

Наконец, плохие новости: поиск «настоящих» пиков становится настоящей болью, когда шум также имеет функции, похожие на реальные пики (перекрывающиеся полосы пропускания). Следующее более сложное решение, как правило, заключается в использовании более длинного сверточного ядра («более широкой апертуры ядра»), которое учитывает взаимосвязь между соседними «реальными» пиками (например, минимальные или максимальные скорости для появления пиков), или использования нескольких свертка проходит с использованием ядер, имеющих разную ширину (но только если она быстрее: фундаментальная математическая истина состоит в том, что линейные свертки, выполняемые последовательно, всегда могут быть сведены вместе в одну свертку). Но зачастую гораздо проще сначала найти последовательность полезных ядер (различной ширины) и собрать их вместе, чем непосредственно найти конечное ядро ​​за один шаг.

Надеюсь, это предоставит достаточно информации, чтобы позволить Google (и, возможно, хороший текст статистики) заполнить пробелы. Мне бы очень хотелось, чтобы у меня было время предоставить работающий пример или ссылку на него. Если кто-то сталкивается с одним онлайн, пожалуйста, отправьте это здесь!

9 голосов
/ 23 ноября 2016

Почему бы не использовать встроенную функцию Scipy signal.find_peaks_cwt для выполнения работы?

from scipy import signal
import numpy as np

#generate junk data (numpy 1D arr)
xs = np.arange(0, np.pi, 0.05)
data = np.sin(xs)

# maxima : use builtin function to find (max) peaks
max_peakind = signal.find_peaks_cwt(data, np.arange(1,10))

# inverse  (in order to find minima)
inv_data = 1/data
# minima : use builtin function fo find (min) peaks (use inversed data)
min_peakind = signal.find_peaks_cwt(inv_data, np.arange(1,10))

#show results
print "maxima",  data[max_peakind]
print "minima",  data[min_peakind]

Результаты:

maxima [ 0.9995736]
minima [ 0.09146464]

С уважением

8 голосов
/ 22 ноября 2018

Начиная с SciPy версии 1.1, вы также можете использовать find_peaks .Ниже приведены два примера, взятых из самой документации.

Используя аргумент height, можно выбрать все максимумы выше определенного порога (в этом примере все неотрицательные максимумы; это может быть очень полезно, если одинприходится иметь дело с шумной базовой линией, если вы хотите найти минимумы, просто умножьте введенные вами значения на -1):

import matplotlib.pyplot as plt
from scipy.misc import electrocardiogram
from scipy.signal import find_peaks
import numpy as np

x = electrocardiogram()[2000:4000]
peaks, _ = find_peaks(x, height=0)
plt.plot(x)
plt.plot(peaks, x[peaks], "x")
plt.plot(np.zeros_like(x), "--", color="gray")
plt.show()

enter image description here

Другойчрезвычайно полезным аргументом является distance, который определяет минимальное расстояние между двумя пиками:

peaks, _ = find_peaks(x, distance=150)
# difference between peaks is >= 150
print(np.diff(peaks))
# prints [186 180 177 171 177 169 167 164 158 162 172]

plt.plot(x)
plt.plot(peaks, x[peaks], "x")
plt.show()

enter image description here

5 голосов
/ 27 января 2011

Обновление: Я не был доволен градиентом, поэтому нашел более надежным использовать numpy.diff. Пожалуйста, дайте мне знать, если он делает то, что вы хотите.

Что касается вопроса шума, математическая проблема состоит в том, чтобы найти максимумы / минимумы, если мы хотим посмотреть на шум, мы можем использовать что-то наподобие извилины, которое упоминалось ранее.

import numpy as np
from matplotlib import pyplot

a=np.array([10.3,2,0.9,4,5,6,7,34,2,5,25,3,-26,-20,-29],dtype=np.float)

gradients=np.diff(a)
print gradients


maxima_num=0
minima_num=0
max_locations=[]
min_locations=[]
count=0
for i in gradients[:-1]:
        count+=1

    if ((cmp(i,0)>0) & (cmp(gradients[count],0)<0) & (i != gradients[count])):
        maxima_num+=1
        max_locations.append(count)     

    if ((cmp(i,0)<0) & (cmp(gradients[count],0)>0) & (i != gradients[count])):
        minima_num+=1
        min_locations.append(count)


turning_points = {'maxima_number':maxima_num,'minima_number':minima_num,'maxima_locations':max_locations,'minima_locations':min_locations}  

print turning_points

pyplot.plot(a)
pyplot.show()
4 голосов
/ 24 апреля 2018

Пока этот вопрос действительно старый. Я полагаю, что в numpy (один вкладыш) гораздо более простой подход.

import numpy as np

list = [1,3,9,5,2,5,6,9,7]

np.diff(np.sign(np.diff(list))) #the one liner

#output
array([ 0, -2,  0,  2,  0,  0, -2])

Чтобы найти локальный максимум или минимум, мы, по сути, хотим найти, когда разница между значениями в списке (3-1, 9-3 ...) меняется с положительного на отрицательный (макс.) Или отрицательного на положительный (мин. ). Поэтому сначала мы находим разницу. Затем мы находим знак, а затем находим изменения в знаке, снова принимая разницу. (Вроде как первая и вторая производные в исчислении, только у нас есть дискретные данные и у нас нет непрерывной функции.)

Вывод в моем примере не содержит экстремумов (первое и последнее значения в списке). Также, как и в исчислении, если вторая производная отрицательна, у вас есть максимум, а если она положительна, у вас есть минимум.

Таким образом, мы имеем следующий матчап:

[1,  3,  9,  5,  2,  5,  6,  9,  7]
    [0, -2,  0,  2,  0,  0, -2]
        Max     Min         Max
3 голосов
/ 09 июня 2017

Ни одно из этих решений не сработало для меня, так как я хотел также найти пики в центре повторяющихся значений. например, в

ar = np.array([0,1,2,2,2,1,3,3,3,2,5,0])

ответ должен быть

array([ 3,  7, 10], dtype=int64)

Я сделал это с помощью цикла. Я знаю, что это не супер чистый, но он выполняет свою работу.

def findLocalMaxima(ar):
# find local maxima of array, including centers of repeating elements    
maxInd = np.zeros_like(ar)
peakVar = -np.inf
i = -1
while i < len(ar)-1:
#for i in range(len(ar)):
    i += 1
    if peakVar < ar[i]:
        peakVar = ar[i]
        for j in range(i,len(ar)):
            if peakVar < ar[j]:
                break
            elif peakVar == ar[j]:
                continue
            elif peakVar > ar[j]:
                peakInd = i + np.floor(abs(i-j)/2)
                maxInd[peakInd.astype(int)] = 1
                i = j
                break
    peakVar = ar[i]
maxInd = np.where(maxInd)[0]
return maxInd 
1 голос
/ 29 июня 2015
import numpy as np
x=np.array([6,3,5,2,1,4,9,7,8])
y=np.array([2,1,3,5,3,9,8,10,7])
sortId=np.argsort(x)
x=x[sortId]
y=y[sortId]
minm = np.array([])
maxm = np.array([])
i = 0
while i < length-1:
    if i < length - 1:
        while i < length-1 and y[i+1] >= y[i]:
            i+=1

        if i != 0 and i < length-1:
            maxm = np.append(maxm,i)

        i+=1

    if i < length - 1:
        while i < length-1 and y[i+1] <= y[i]:
            i+=1

        if i < length-1:
            minm = np.append(minm,i)
        i+=1


print minm
print maxm

minm и maxm содержат индексы минимумов и максимумов соответственно. Для огромного набора данных он даст много максимумов / минимумов, поэтому в этом случае сначала сгладьте кривую, а затем примените этот алгоритм.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...