Пошук локальних максимумів / мінімумів за допомогою Numpy в масиві 1D numpy


116

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



1
Ні, це в 2D (я говорю про 1D) і включає спеціальні функції. У мене є своя проста реалізація, але мені було цікаво, чи є краща, яка поставляється з модулями Numpy / Scipy.
Наві

Можливо, ви можете оновити питання, щоб включити, що (1) у вас є 1d масив і (2) який локальний мінімум ви шукаєте. Просто запис менший, ніж два суміжні записи?
Свен Марнах

1
Ви можете подивитися на scipy.signal.find_peaks_cwt, якщо ви говорите про дані із шумом
Lakshay Garg

Відповіді:


66

Якщо ви шукаєте всі записи в 1d масиві aменше, ніж їхні сусіди, ви можете спробувати

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

Ви також можете згладити масив перед цим кроком numpy.convolve().

Я не думаю, що для цього є виділена функція.


Хм, навіщо мені потрібно згладжувати? Щоб видалити шум? Це звучить цікаво. Мені здається, я міг використовувати інше ціле число замість 1 у вашому прикладі коду. Я також думав обчислити градієнти. У будь-якому випадку, якщо немає функції, це дуже погано.
Наві

1
@Navi: Проблема полягає в тому, що поняття "локальний мінімум" сильно відрізняється від випадку використання до випадку, тому важко забезпечити "стандартну" функцію для цієї мети. Згладжування допомагає врахувати більше, ніж просто найближчого сусіда. Використання іншого цілого числа замість 1, скажімо 3, було б дивним, оскільки він би розглядав лише третій-наступний елемент в обох напрямках, але не прямий neihgbors.
Свен Марнах

1
@Sven Marnach: рецепт, який ви посилаєте, затримує сигнал. є другою рецепт , який використовує filtfilt з scipy.signal
bobrobbob

2
Тільки заради нього, змінивши <з >дасть вам локальні максимуми замість мінімумів
DarkCygnus

1
@SvenMarnach Я використовував ваше вище рішення для вирішення моєї проблеми, розміщеної тут stackoverflow.com/questions/57403659/…, але я отримав висновок [False False]Що тут може бути проблемою?
Msquare

221

У 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для знаходження максимумів і мінімумів відповідно.


1
Яке значення має 12?
зефір

7
@marshmallow: np.random.random(12)генерує 12 випадкових значень, вони використовуються для демонстрації функції argrelextrema.
sebix

2
якщо вхід є test02=np.array([10,4,4,4,5,6,7,6]), то він не працює. Він не визнає послідовних значень як локальних мінімумів.
Леос313

1
дякую, @Cleb. Хочу вказати на інші проблеми: як щодо крайніх точок масиву? перший елемент є і локальним максимумом, оскільки останній елемент масиву є також локальним мінімумом. Крім того, він не повертає, скільки значень поспіль засновано. Однак я запропонував рішення в коді цього питання тут . Дякую!!
Леос313

1
Дякую, це одне з найкращих рішень, які я знайшов досі
Noufal E

37

Для кривих з не надто великим рівнем шуму я рекомендую наступний невеликий фрагмент коду:

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зменшує початкове число індексу.


1
приємне використання вкладених функцій numpy! але зауважте, що це не вистачає максимумів на будь-якому кінці масиву :)
danodonovan

2
Це також буде діяти дивно, якщо є повторювані значення. Наприклад, якщо ви берете масив [1, 2, 2, 3, 3, 3, 2, 2, 1], локальні максимуми, очевидно, десь між 3-ю в середині. Але якщо ви запускаєте передбачені вами функції, ви отримуєте максими в індексах 2,6 і мінімуми при індексах 1,3,5,7, що для мене не має великого сенсу.
Корем

5
Щоб уникнути цього +1замість np.diff()використання np.gradient().
ankostis

Я знаю, що цій нитці вже років, але варто додати, що якщо крива надто галаслива, ви завжди можете спробувати фільтрування з низьким проходом спочатку для згладжування. Принаймні, для мене більшість моїх локальних max / min використовуються для глобальних max / min в межах певної місцевості (наприклад, великі вершини та долини, не кожна зміна даних)
marcman

25

Інший підхід (більше слів, менше коду), який може допомогти:

Розташування локальних максимумів і мінімумів - це також місця нульових перетинів першої похідної. Як правило, набагато простіше знайти нульові переправи, ніж безпосередньо знайти локальні максимуми та мінімуми.

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

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

На щастя, досить часто відповідне ядро ​​можна створити за допомогою простого SWAG ("освіченої здогадки"). Ширина згладжуючого ядра повинна бути трохи ширшою, ніж найширший очікуваний "цікавий" пік в оригінальних даних, а його форма буде нагадувати цей пік (одномасштабний вейвлет). Для середніх збережених ядер (яким повинен бути будь-який хороший фільтр згладжування) сума елементів ядра повинна бути точно рівною 1,00, а ядро ​​має бути симетричним щодо його центру (це означає, що у нього буде непарна кількість елементів.

Враховуючи оптимальне згладжування ядра (або невелику кількість ядер, оптимізованих для різного вмісту даних), ступінь згладжування стає масштабним фактором ("посилення") ядра згортки.

Визначення "правильної" (оптимальної) ступеня згладжування (посилення ядра згортки) навіть може бути автоматизовано: порівняйте стандартне відхилення перших похідних даних зі стандартним відхиленням згладжених даних. Як співвідношення двох стандартних відхилень змінюється зі зміною ступеня згладжування кулачка, що використовується для прогнозування ефективних значень згладжування. Кілька ручних запусків даних (які є справді репрезентативними) повинні бути всіма необхідними.

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

Нарешті, погана новина: пошук "справжніх" піків стає королівським болем, коли шум також має функції, схожі на справжні піки (пропускна здатність, що перекривається). Наступне більш складне рішення, як правило, полягає у використанні більш тривалого ядра згортки ("ширшої діафрагми ядра"), що враховує взаємозв'язок між сусідніми "реальними" піками (наприклад, мінімальні або максимальні показники для пікового виникнення) або використовувати кілька згортання проходить за допомогою ядер, що мають різну ширину (але лише якщо це швидше: це основна математична істина, що лінійні згортки, які виконуються послідовно, завжди можна об'єднати в одну згортку). Але часто набагато простіше спочатку знайти послідовність корисних ядер (різної ширини) та з'єднати їх разом, ніж безпосередньо знайти остаточне ядро ​​за один крок.

Сподіваємось, це надає достатньо інформації, щоб Google (і, можливо, хороший текст статистики) заповнив прогалини. Я дуже хочу, щоб у мене був час надати відпрацьований приклад або посилання на один. Якщо хтось натрапив на один онлайн, будь ласка, розмістіть його тут


24

Як і для 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()

введіть тут опис зображення

Ще один надзвичайно корисний аргумент - 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()

введіть тут опис зображення


10

Чому б не використати вбудовану функцію 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]

З повагою


7
Замість того, щоб робити поділ (з можливою втратою точності), чому б не просто помножити на -1, щоб перейти від максимумів до мінімумів?
Лівій

Я спробував змінити '1 / data' на 'data * -1', але потім це викликало помилку, чи можете ви поділитися, як реалізувати свій метод?
А СТЕФАНІ

Можливо, тому, що ми не хочемо вимагати від кінцевих користувачів додатково встановлювати scipy.
Даміян Єрік

5

Оновлення: я не був задоволений градієнтом, тому вважав його більш надійним у використанні 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()

Чи знаєте ви, як обчислюється цей градієнт? Якщо у вас є шумні дані, можливо, градієнт сильно змінюється, але це не означає, що існує макс / хв.
Наві

Так, я знаю, проте шумні дані - це інше питання. Для цього я думаю, використовуйте convolve.
Майк Велла

Мені було потрібно щось подібне для проекту, над яким я працював, і використовував згаданий вище метод numpy.diff, я вважав, що може бути корисним згадати, що для моїх даних вищевказаний код пропустив кілька максимумів і мінімумів, змінивши середній термін в обох якщо твердження до <= і> = відповідно, я зміг наздогнати всі моменти.

5

Хоча це питання справді старе. Я вважаю, що в 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])

Щоб знайти локальний max або min, ми, по суті, хочемо знайти, коли різниця між значеннями у списку (3-1, 9-3 ...) змінюється від позитивного на негативне (max) або від'ємного на позитивне (min). Тому спочатку знаходимо різницю. Потім ми знаходимо знак, а потім знаходимо зміни знаку, приймаючи різницю знову. (Начебто перша і друга похідна в обчисленні. Тільки ми маємо дискретні дані і не маємо безперервної функції.)

Висновок у моєму прикладі не містить екстремумів (перше і останнє значення у списку). Крім того, як і числення, якщо друга похідна від'ємна, у вас є макс., А якщо вона позитивна - у вас є хв.

Таким чином, ми маємо наступний збіг:

[1,  3,  9,  5,  2,  5,  6,  9,  7]
    [0, -2,  0,  2,  0,  0, -2]
        Max     Min         Max

1
Я думаю, що ця (гарна!) Відповідь така сама, як відповідь РК з 2012 року? Він пропонує три однолінійні рішення, залежно від того, чи хоче абонент хоче хвилини, максимуми чи обидва, якщо я правильно читаю його рішення.
Брендон Родос

3

Жодне з цих рішень не працювало для мене, оскільки я хотів знайти піки і в центрі повторюваних значень. наприклад, в

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
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містять показники мінімумів і максимумів відповідно. Для величезного набору даних він дасть безліч максимумів / мінімумів, тому в цьому випадку спочатку згладьте криву, а потім застосуйте цей алгоритм.


це виглядає цікаво. Немає бібліотек. Як це працює?
John ktejik

1
пройдіть криву від початкової точки і подивіться, чи рухаєтесь ви вгору або вниз постійно, коли ви змінюєтесь вгору або вниз, це означає, що ви отримали максимум, якщо ви спускаєтесь вгору, у вас є мінімуми.
prtkp

1

Ще одне рішення із застосуванням по суті оператора з розширення

import numpy as np
from scipy.ndimage import rank_filter

def find_local_maxima(x):
   x_dilate = rank_filter(x, -1, size=3)
   return x_dilate == x

а для мінімумів:

def find_local_minima(x):
   x_erode = rank_filter(x, -0, size=3)
   return x_erode == x

Також від scipy.ndimageвас можна замінити rank_filter(x, -1, size=3)на grey_dilationі rank_filter(x, 0, size=3)на grey_erosion. Для цього не потрібно місцевого сортування, тому це трохи швидше.


вона працює належним чином для цієї проблеми. Тут рішення ідеальне (+1)
Leos313

0

Інший:


def local_maxima_mask(vec):
    """
    Get a mask of all points in vec which are local maxima
    :param vec: A real-valued vector
    :return: A boolean mask of the same size where True elements correspond to maxima. 
    """
    mask = np.zeros(vec.shape, dtype=np.bool)
    greater_than_the_last = np.diff(vec)>0  # N-1
    mask[1:] = greater_than_the_last
    mask[:-1] &= ~greater_than_the_last
    return mask
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.