Menemukan maksimum / minimum lokal dengan Numpy dalam larik numpy 1D

116

Dapatkah Anda menyarankan fungsi modul dari numpy / scipy yang dapat menemukan maxima / minima lokal dalam array numpy 1D? Jelas pendekatan paling sederhana adalah dengan melihat tetangga terdekat, tetapi saya ingin memiliki solusi yang diterima yang merupakan bagian dari distro numpy.

Navi
sumber
1
Tidak, itu dalam 2D ​​(saya berbicara tentang 1D) dan melibatkan fungsi khusus. Saya memiliki implementasi sederhana saya sendiri, tetapi saya bertanya-tanya apakah ada yang lebih baik, yang dilengkapi dengan modul Numpy / Scipy.
Navi
Mungkin Anda dapat memperbarui pertanyaan untuk memasukkan bahwa (1) Anda memiliki larik 1d dan (2) jenis minimum lokal apa yang Anda cari. Hanya entri yang lebih kecil dari dua entri yang berdekatan?
Sven Marnach
1
Anda dapat melihat scipy.signal.find_peaks_cwt jika Anda berbicara tentang data dengan kebisingan
Lakshay Garg

Jawaban:

66

Jika Anda mencari semua entri dalam larik 1d yang alebih kecil dari tetangganya, Anda dapat mencobanya

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

Anda juga bisa menghaluskan larik Anda sebelum langkah ini menggunakan numpy.convolve().

Saya rasa tidak ada fungsi khusus untuk ini.

Sven Marnach
sumber
Hmm, kenapa saya harus menghaluskan? Untuk menghilangkan kebisingan? Kedengarannya menarik. Menurut saya, saya dapat menggunakan bilangan bulat lain daripada 1 dalam kode contoh Anda. Saya juga berpikir untuk menghitung gradien. Pokoknya jika tidak ada fungsi dari itu terlalu buruk.
Navi
1
@Navi: Masalahnya adalah bahwa gagasan "minimum lokal" sangat bervariasi dari kasus penggunaan ke kasus penggunaan, jadi sulit untuk menyediakan fungsi "standar" untuk tujuan ini. Menghaluskan membantu memperhitungkan lebih dari sekedar tetangga terdekat. Menggunakan bilangan bulat yang berbeda dari pada 1, katakanlah 3, akan aneh karena hanya akan mempertimbangkan elemen ketiga berikutnya di kedua arah, tetapi bukan tetangga langsung.
Sven Marnach
1
@ Sven Marnach: resep yang Anda tautkan menunda sinyal. ada resep kedua yang menggunakan filtfilt dari scipy.signal
bobrobbob
2
Hanya demi itu, mengganti <dengan >akan memberi Anda maksima lokal alih-alih minima
DarkCygnus
1
@ SvenMarnach Saya telah menggunakan solusi Anda di atas untuk memecahkan masalah saya diposting di sini stackoverflow.com/questions/57403659/… tapi saya mendapat keluaran [False False]Apa yang bisa menjadi masalah di sini?
Msquare
221

Dalam 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)

Menghasilkan

>>> 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]),)

Catatan, ini adalah indeks x yang merupakan maks / menit lokal. Untuk mendapatkan nilainya, coba:

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

scipy.signaljuga menyediakan argrelmaxdan argrelminuntuk menemukan masing-masing maxima dan minima.

danodonovan
sumber
1
Apa signifikansi dari 12?
marshmallow
7
@marshmallow: np.random.random(12)menghasilkan 12 nilai acak, mereka digunakan untuk mendemonstrasikan fungsinya argrelextrema.
sebix
2
jika masukannya test02=np.array([10,4,4,4,5,6,7,6]), maka itu tidak berfungsi. Itu tidak mengakui nilai-nilai berurutan sebagai minimum lokal.
Leos313
1
terima kasih, @Cleb. Saya ingin menunjukkan masalah lain: bagaimana dengan titik ekstrim dari array? elemen pertama adalah maksimum lokal juga karena elemen terakhir dari array juga merupakan minimum lokal. Dan, juga, tidak mengembalikan berapa banyak nilai berurutan yang didirikan. Namun, saya mengusulkan solusi dalam kode pertanyaan ini di sini . Terima kasih!!
Leos313
1
Terima kasih, ini adalah salah satu solusi terbaik yang saya temukan sejauh ini
Noufal E
37

Untuk kurva dengan noise yang tidak terlalu banyak, saya merekomendasikan potongan kode kecil berikut:

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()

Ini +1penting, karena diffmengurangi nomor indeks asli.

RC
sumber
1
penggunaan yang bagus dari fungsi numpy bersarang! tetapi perhatikan bahwa ini tidak mencapai maksimal di kedua ujung larik :)
danodonovan
2
Ini juga akan bertindak aneh jika ada nilai yang berulang. misalnya jika Anda mengambil larik [1, 2, 2, 3, 3, 3, 2, 2, 1], maksima lokal jelas berada di antara 3 di tengah. Tetapi jika Anda menjalankan fungsi yang Anda berikan, Anda mendapatkan maksimas pada indeks 2,6 dan minimal pada indeks 1,3,5,7, yang bagi saya tidak masuk akal.
Korem
5
Untuk menghindari ini +1alih-alih np.diff()digunakan np.gradient().
ankostis
Saya tahu utas ini sudah berumur bertahun-tahun, tetapi perlu ditambahkan bahwa jika lekukan Anda terlalu berisik, Anda selalu dapat mencoba penyaringan low-pass terlebih dahulu untuk menghaluskan. Bagi saya setidaknya, sebagian besar penggunaan maks / mnt lokal saya adalah untuk maks / mnt global dalam beberapa area lokal (misalnya, puncak dan lembah besar, tidak setiap variasi dalam data)
marcman
25

Pendekatan lain (lebih banyak kata, lebih sedikit kode) yang mungkin membantu:

Lokasi maksimum dan minimum lokal juga merupakan lokasi penyeberangan nol dari turunan pertama. Secara umum, jauh lebih mudah untuk menemukan penyeberangan nol daripada menemukan langsung maksima dan minimum lokal.

Sayangnya, turunan pertama cenderung "memperkuat" derau, jadi ketika derau yang signifikan hadir dalam data asli, turunan pertama paling baik digunakan hanya setelah data asli menerapkan beberapa tingkat penghalusan.

Karena penghalusan, dalam arti yang paling sederhana, filter lolos rendah, penghalusan sering kali paling baik (baik, paling mudah) dilakukan dengan menggunakan kernel konvolusi, dan "membentuk" kernel tersebut dapat memberikan jumlah yang mengejutkan dari kemampuan mempertahankan / meningkatkan fitur . Proses menemukan kernel yang optimal dapat diotomatiskan menggunakan berbagai cara, tetapi yang terbaik mungkin adalah kekerasan sederhana (sangat cepat untuk menemukan kernel kecil). Kernel yang baik akan (sebagaimana dimaksud) secara besar-besaran mendistorsi data asli, tetapi TIDAK akan mempengaruhi lokasi puncak / lembah yang diinginkan.

Untungnya, sering kali kernel yang sesuai dapat dibuat melalui SWAG sederhana ("tebakan secara cerdas"). Lebar kernel penghalus harus sedikit lebih lebar dari puncak terlebar yang diharapkan "menarik" dalam data asli, dan bentuknya akan menyerupai puncak itu (wavelet berskala tunggal). Untuk kernel yang menjaga rata-rata (filter penghalus yang bagus), jumlah elemen kernel harus sama persis dengan 1,00, dan kernel harus simetris dengan bagian tengahnya (artinya akan memiliki jumlah elemen ganjil.

Mengingat kernel penghalusan yang optimal (atau sejumlah kecil kernel yang dioptimalkan untuk konten data yang berbeda), tingkat penghalusan menjadi faktor penskalaan untuk ("perolehan") kernel konvolusi.

Menentukan derajat pemulusan yang "benar" (optimal) (penguatan kernel konvolusi) bahkan dapat diotomatiskan: Bandingkan deviasi standar data turunan pertama dengan deviasi standar data yang dihaluskan. Bagaimana rasio dari dua deviasi standar berubah dengan perubahan tingkat penghalusan cam digunakan untuk memprediksi nilai penghalusan yang efektif. Beberapa data manual yang dijalankan (yang benar-benar representatif) seharusnya menjadi semua yang dibutuhkan.

Semua solusi sebelumnya yang diposting di atas menghitung turunan pertama, tetapi solusi di atas tidak memperlakukannya sebagai ukuran statistik, juga tidak solusi di atas mencoba melakukan pelestarian fitur / peningkatan penghalusan (untuk membantu puncak halus "melompati" kebisingan).

Akhirnya, berita buruknya: Menemukan puncak yang "nyata" menjadi masalah besar ketika kebisingan juga memiliki fitur yang terlihat seperti puncak nyata (bandwidth yang tumpang tindih). Solusi yang lebih kompleks berikutnya umumnya menggunakan kernel konvolusi yang lebih panjang ("bukaan kernel yang lebih lebar") yang memperhitungkan hubungan antara puncak "nyata" yang berdekatan (seperti kecepatan minimum atau maksimum untuk kejadian puncak), atau menggunakan beberapa lintasan konvolusi menggunakan kernel yang memiliki lebar berbeda (tetapi hanya jika lebih cepat: merupakan kebenaran matematika fundamental bahwa konvolusi linier yang dilakukan secara berurutan selalu dapat digabung menjadi satu konvolusi tunggal). Tetapi seringkali jauh lebih mudah untuk terlebih dahulu menemukan urutan kernel yang berguna (dengan lebar yang bervariasi) dan menggabungkannya bersama-sama daripada mencari langsung kernel akhir dalam satu langkah.

Mudah-mudahan ini memberikan info yang cukup untuk membiarkan Google (dan mungkin teks statistik yang bagus) mengisi kekosongan. Saya sangat berharap saya punya waktu untuk memberikan contoh yang berhasil, atau tautan ke salah satunya. Jika ada yang menemukannya secara online, silakan posting di sini!

BobC
sumber
24

Mulai SciPy versi 1.1, Anda juga dapat menggunakan find_peaks . Di bawah ini adalah dua contoh yang diambil dari dokumentasi itu sendiri.

Dengan menggunakan heightargumen, seseorang dapat memilih semua maksima di atas ambang tertentu (dalam contoh ini, semua maksima non-negatif; ini bisa sangat berguna jika seseorang harus berurusan dengan baseline yang berisik; jika Anda ingin mencari minimum, cukup kalikan masukan Anda oleh -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()

masukkan deskripsi gambar di sini

Argumen lain yang sangat membantu adalah distance, yang mendefinisikan jarak minimum antara dua puncak:

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()

masukkan deskripsi gambar di sini

Cleb
sumber
10

Mengapa tidak menggunakan fungsi signal.find_peaks_cwt Scipy built-in untuk melakukan pekerjaan itu?

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]

hasil:

maxima [ 0.9995736]
minima [ 0.09146464]

Salam

A STEFANI
sumber
7
Daripada melakukan pembagian (dengan kemungkinan kehilangan presisi), mengapa tidak mengalikan dengan -1 untuk beralih dari maksimal ke minimal?
Livius
Saya mencoba mengubah '1 / data' menjadi 'data * -1', tetapi kemudian menimbulkan kesalahan, dapatkah Anda berbagi cara menerapkan metode Anda?
STEFANI
Mungkin karena kami tidak ingin pengguna akhir menginstal scipy tambahan.
Damian Yerrick
5

Pembaruan: Saya tidak senang dengan gradien jadi saya merasa lebih dapat diandalkan untuk digunakan numpy.diff. Tolong beri tahu saya jika itu sesuai dengan keinginan Anda.

Berkaitan dengan masalah kebisingan, masalah matematisnya adalah mencari letak maxima / minima jika kita ingin melihat noise kita dapat menggunakan sesuatu seperti convolve yang telah disebutkan sebelumnya.

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()
Mike Vella
sumber
Tahukah Anda bagaimana gradien ini dihitung? Jika Anda memiliki data yang berisik mungkin gradiennya banyak berubah, tetapi itu tidak berarti bahwa ada maks / menit.
Navi
Ya saya tahu, bagaimanapun data berisik adalah masalah yang berbeda. Untuk itu saya kira gunakan convolve.
Mike Vella
Saya membutuhkan sesuatu yang serupa untuk proyek yang saya kerjakan dan menggunakan metode numpy.diff yang disebutkan di atas, saya pikir mungkin berguna untuk menyebutkan bahwa untuk data saya kode di atas melewatkan beberapa maxima dan minima, dengan mengubah istilah tengah di keduanya jika pernyataan untuk <= dan> = masing-masing, saya bisa menangkap semua poin.
5

Padahal pertanyaan ini sangat tua. Saya percaya ada pendekatan yang jauh lebih sederhana di numpy (satu liner).

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])

Untuk menemukan maks atau min lokal, kami pada dasarnya ingin menemukan ketika perbedaan antara nilai-nilai dalam daftar (3-1, 9-3 ...) berubah dari positif ke negatif (maks) atau negatif ke positif (min). Oleh karena itu, pertama kita temukan perbedaannya. Lalu kita temukan tandanya, lalu kita temukan perubahan tanda itu dengan mengambil perbedaannya lagi. (Semacam turunan pertama dan kedua dalam kalkulus, hanya kami yang memiliki data diskrit dan tidak memiliki fungsi kontinu.)

Output dalam contoh saya tidak mengandung extrema (nilai pertama dan terakhir dalam daftar). Juga, seperti kalkulus, jika turunan keduanya negatif, Anda memiliki nilai maks, dan jika positif Anda memiliki nilai min.

Jadi kami memiliki pertarungan berikut:

[1,  3,  9,  5,  2,  5,  6,  9,  7]
    [0, -2,  0,  2,  0,  0, -2]
        Max     Min         Max
Dave
sumber
1
Menurut saya jawaban (bagus!) Ini sama dengan jawaban RC tahun 2012? Dia menawarkan tiga solusi satu baris, tergantung pada apakah penelepon menginginkan menit, maks, atau keduanya, jika saya membaca solusinya dengan benar.
Brandon Rhodes
3

Tak satu pun dari solusi ini berhasil untuk saya karena saya juga ingin menemukan puncak di tengah nilai yang berulang. misalnya, di

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

jawabannya seharusnya

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

Saya melakukan ini menggunakan loop. Saya tahu ini tidak super bersih, tetapi menyelesaikan pekerjaan.

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 
Misha Smirnov
sumber
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

minmdan maxmmasing-masing berisi indeks minimal dan maksimal. Untuk kumpulan data yang sangat besar, itu akan memberikan banyak maksimas / minimas sehingga dalam hal ini menghaluskan kurva terlebih dahulu dan kemudian menerapkan algoritma ini.

prtkp
sumber
ini terlihat menarik. Tidak ada perpustakaan. Bagaimana cara kerjanya?
john ktejik
1
melintasi kurva dari titik awal dan lihat apakah Anda naik atau turun terus menerus, setelah Anda mengubah dari atas ke bawah itu berarti Anda mendapat maksimal, jika Anda turun ke atas, Anda mendapat minimal.
prtkp
1

Solusi lain yang pada dasarnya menggunakan operator dilatasi:

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

dan untuk minima:

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

Juga, dari scipy.ndimageAnda dapat mengganti rank_filter(x, -1, size=3)dengan grey_dilationdan rank_filter(x, 0, size=3)dengan grey_erosion. Ini tidak memerlukan jenis lokal, jadi ini sedikit lebih cepat.

gnodab.dll
sumber
itu bekerja dengan baik untuk masalah ini. Di sini solusinya sempurna (+1)
Leos313
0

Yang lainnya:


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
Peter
sumber