Tìm cực đại / cực tiểu cục bộ với Numpy trong mảng numpy 1D


116

Bạn có thể đề xuất một chức năng mô-đun từ numpy / scipy có thể tìm thấy cực đại / cực tiểu cục bộ trong một mảng numpy 1D không? Rõ ràng cách tiếp cận đơn giản nhất từng có là nhìn vào những người hàng xóm gần nhất, nhưng tôi muốn có một giải pháp được chấp nhận là một phần của bản phân phối numpy.



1
Không có điều đó trong 2D (tôi đang nói về 1D) và liên quan đến các chức năng tùy chỉnh. Tôi có cách thực hiện đơn giản của riêng mình, nhưng tôi đã tự hỏi nếu có một cái tốt hơn, đi kèm với các mô-đun Numpy / Scipy.
Navi

Có lẽ bạn có thể cập nhật câu hỏi để bao gồm (1) bạn có mảng 1d và (2) loại tối thiểu cục bộ nào bạn đang tìm kiếm. Chỉ là một mục nhỏ hơn hai mục liền kề?
Sven Marnach

1
Bạn có thể xem scipy.signal.find_peaks_cwt nếu bạn đang nói về dữ liệu với tiếng ồn
Lakshay Garg

Câu trả lời:


66

Nếu bạn đang tìm kiếm tất cả các mục trong mảng 1d anhỏ hơn hàng xóm của chúng, bạn có thể thử

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

Bạn cũng có thể làm mịn mảng của bạn trước khi bước này sử dụng numpy.convolve().

Tôi không nghĩ rằng có một chức năng dành riêng cho việc này.


Hmm, tại sao tôi cần phải làm mịn? Để loại bỏ tiếng ồn? Nghe có vẻ thú vị. Dường như với tôi rằng tôi có thể sử dụng một số nguyên khác thay vì 1 trong mã ví dụ của bạn. Tôi cũng đã nghĩ đến việc tính toán độ dốc. Dù sao nếu không có chức năng hơn đó là quá xấu.
Navi

1
@Navi: Vấn đề là khái niệm "tối thiểu cục bộ" thay đổi rất nhiều từ trường hợp sử dụng sang trường hợp sử dụng, vì vậy thật khó để cung cấp chức năng "tiêu chuẩn" cho mục đích này. Làm mịn giúp tính đến nhiều hơn là hàng xóm gần nhất. Sử dụng một số nguyên khác thay vì 1, giả sử 3, sẽ lạ vì nó chỉ xem xét phần tử thứ ba tiếp theo theo cả hai hướng, nhưng không phải là neihg Harbor trực tiếp.
Sven Marnach

1
@Sven Marnach: công thức bạn liên kết làm chậm tín hiệu. có một công thức thứ hai trong đó sử dụng filtfilt từ scipy.signal
bobrobbob

2
Chỉ vì lợi ích của nó, thay thế <bằng >sẽ cung cấp cho bạn cực đại địa phương thay vì cực tiểu
DarkCygnus

1
@SvenMarnach Tôi đã sử dụng giải pháp trên của bạn để giải quyết vấn đề của mình được đăng ở đây stackoverflow.com/questions/57403659/ nhưng tôi đã nhận được đầu ra [False False]Vấn đề ở đây là gì?
Msapes

220

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

Sản xuất

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

Lưu ý, đây là các chỉ số của x là max / min cục bộ. Để có được các giá trị, hãy thử:

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

scipy.signalcũng cung cấp argrelmaxargrelminđể tìm cực đại và cực tiểu tương ứng.


1
Ý nghĩa của 12 là gì?
marshmallow

7
@marshmallow: np.random.random(12)tạo 12 giá trị ngẫu nhiên, chúng được sử dụng để thể hiện chức năng argrelextrema.
sebix 22/03/2015

2
nếu đầu vào là test02=np.array([10,4,4,4,5,6,7,6]), thì nó không hoạt động. Nó không nhận ra các giá trị liên tiếp là cực tiểu cục bộ.
Leos313

1
cảm ơn bạn, @Cleb. Tôi muốn chỉ ra các vấn đề khác: còn các điểm cực trị của mảng thì sao? phần tử đầu tiên cũng là một mức tối đa cục bộ vì phần tử cuối cùng của mảng cũng là một mức tối thiểu cục bộ. Và, đồng thời, nó không trả về bao nhiêu giá trị liên tiếp được thiết lập. Tuy nhiên, tôi đã đề xuất một giải pháp trong mã của câu hỏi này ở đây . Cảm ơn bạn!!
Leos313 ngày

1
Cảm ơn bạn, đây là một trong những giải pháp tốt nhất tôi đã tìm thấy cho đến nay
Noufal E

37

Đối với các đường cong không có quá nhiều nhiễu, tôi khuyên bạn nên sử dụng đoạn mã nhỏ sau đây:

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

Điều +1này rất quan trọng, vì difflàm giảm số chỉ mục gốc.


1
sử dụng tốt các chức năng numpy lồng nhau! nhưng lưu ý rằng điều này không bỏ lỡ cực đại ở hai đầu của mảng :)
danodonovan

2
Điều này cũng sẽ hành động kỳ lạ nếu có các giá trị lặp đi lặp lại. ví dụ: nếu bạn lấy mảng [1, 2, 2, 3, 3, 3, 2, 2, 1], cực đại cục bộ rõ ràng nằm ở đâu đó giữa 3 ở giữa. Nhưng nếu bạn chạy các chức năng bạn cung cấp, bạn nhận được maximas ở các chỉ số 2,6 và minimas ở các chỉ số 1,3,5,7, với tôi điều đó không có ý nghĩa gì nhiều.
Korem

5
Để tránh điều này +1thay vì np.diff()sử dụng np.gradient().
ankostis

Tôi biết chủ đề này đã cũ, nhưng đáng nói thêm là nếu đường cong của bạn quá ồn, trước tiên bạn luôn có thể thử lọc thông thấp để làm mịn. Đối với tôi ít nhất, hầu hết các sử dụng tối đa / phút cục bộ của tôi là tối đa toàn cầu / phút trong một số khu vực địa phương (e, g, các đỉnh và thung lũng lớn, không phải mọi biến thể trong dữ liệu)
marcman

25

Một cách tiếp cận khác (nhiều từ hơn, ít mã hơn) có thể giúp:

Các vị trí của cực đại và cực tiểu địa phương cũng là các vị trí của giao điểm 0 của đạo hàm đầu tiên. Nhìn chung, việc tìm kiếm giao điểm bằng 0 dễ dàng hơn nhiều so với tìm trực tiếp cực đại và cực tiểu cục bộ.

Thật không may, đạo hàm đầu tiên có xu hướng "khuếch đại" nhiễu, vì vậy khi có nhiễu đáng kể trong dữ liệu gốc, đạo hàm đầu tiên chỉ được sử dụng tốt nhất sau khi dữ liệu gốc đã được áp dụng ở mức độ nào đó.

Vì làm mịn là, theo cách hiểu đơn giản nhất, bộ lọc thông thấp, việc làm mịn thường được thực hiện tốt nhất (tốt, dễ dàng nhất) bằng cách sử dụng hạt nhân chập và "định hình" hạt nhân đó có thể cung cấp một lượng đáng kinh ngạc về khả năng bảo tồn / tăng cường tính năng . Quá trình tìm kiếm một hạt nhân tối ưu có thể được tự động hóa bằng nhiều phương tiện khác nhau, nhưng tốt nhất có thể là lực lượng vũ phu đơn giản (rất nhanh để tìm ra các hạt nhân nhỏ). Một hạt nhân tốt sẽ (như dự định) sẽ bóp méo dữ liệu gốc, nhưng nó sẽ KHÔNG ảnh hưởng đến vị trí của các đỉnh / thung lũng quan tâm.

May mắn thay, khá thường xuyên một hạt nhân phù hợp có thể được tạo ra thông qua một SWAG đơn giản ("phỏng đoán có giáo dục"). Độ rộng của nhân làm mịn phải rộng hơn một chút so với đỉnh "thú vị" được mong đợi rộng nhất trong dữ liệu gốc và hình dạng của nó sẽ giống với đỉnh đó (một bước sóng đơn tỷ lệ). Đối với các hạt nhân bảo toàn trung bình (bất kỳ bộ lọc làm mịn tốt nào), tổng các phần tử hạt nhân phải chính xác bằng 1,00 và hạt nhân phải đối xứng về tâm của nó (có nghĩa là nó sẽ có số lượng phần tử lẻ.

Với một hạt nhân làm mịn tối ưu (hoặc một số lượng nhỏ các hạt nhân được tối ưu hóa cho các nội dung dữ liệu khác nhau), mức độ làm mịn trở thành một hệ số tỷ lệ cho ("mức tăng" của) hạt nhân chập.

Xác định mức độ làm mịn "chính xác" (tối ưu) (tăng nhân tích chập) thậm chí có thể được tự động hóa: So sánh độ lệch chuẩn của dữ liệu phái sinh đầu tiên với độ lệch chuẩn của dữ liệu được làm mịn. Làm thế nào tỷ lệ của hai độ lệch chuẩn thay đổi với sự thay đổi mức độ làm mịn cam được sử dụng để dự đoán các giá trị làm mịn hiệu quả. Một vài dữ liệu thủ công chạy (đó thực sự là đại diện) nên là tất cả những gì cần thiết.

Tất cả các giải pháp trước đây được đăng ở trên đều tính đạo hàm đầu tiên, nhưng họ không coi đó là một biện pháp thống kê, cũng như các giải pháp trên không cố gắng thực hiện tính năng bảo toàn / tăng cường làm mịn (để giúp các đỉnh tinh tế "vượt lên trên" tiếng ồn).

Cuối cùng, tin xấu: Tìm kiếm các đỉnh "thực" trở thành nỗi đau của hoàng gia khi tiếng ồn cũng có các tính năng trông giống như các đỉnh thực sự (băng thông chồng chéo). Giải pháp phức tạp hơn tiếp theo thường là sử dụng hạt nhân chập dài hơn ("khẩu độ nhân rộng hơn") có tính đến mối quan hệ giữa các đỉnh "thực" liền kề (như tốc độ tối thiểu hoặc tối đa cho sự xuất hiện cực đại) hoặc sử dụng nhiều tích chập vượt qua bằng cách sử dụng các hạt nhân có độ rộng khác nhau (nhưng chỉ khi nó nhanh hơn: đó là một sự thật toán học cơ bản mà các phép tích tuyến tính được thực hiện theo trình tự luôn có thể được kết hợp thành một tổ hợp). Nhưng trước tiên thường dễ dàng hơn nhiều khi tìm thấy một chuỗi các hạt nhân hữu ích (có độ rộng khác nhau) và kết hợp chúng lại với nhau hơn là tìm trực tiếp hạt nhân cuối cùng trong một bước duy nhất.

Hy vọng rằng điều này cung cấp đủ thông tin để cho phép Google (và có lẽ một văn bản thống kê tốt) điền vào các khoảng trống. Tôi thực sự muốn tôi có thời gian để cung cấp một ví dụ hoạt động, hoặc một liên kết đến một ví dụ. Nếu bất cứ ai đi qua một trực tuyến, xin vui lòng gửi nó ở đây!


21

Kể từ phiên bản SciPy 1.1, bạn cũng có thể sử dụng find_peaks . Dưới đây là hai ví dụ được lấy từ các tài liệu chính.

Sử dụng heightđối số, người ta có thể chọn tất cả các cực đại trên một ngưỡng nhất định (trong ví dụ này, tất cả các cực đại không âm; điều này có thể rất hữu ích nếu người ta phải xử lý một đường cơ sở ồn ào; nếu bạn muốn tìm cực tiểu, chỉ cần nhân bội số của bạn bởi -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()

nhập mô tả hình ảnh ở đây

Một đối số cực kỳ hữu ích khác là distancexác định khoảng cách tối thiểu giữa hai đỉnh:

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

nhập mô tả hình ảnh ở đây


10

Tại sao không sử dụng chức năng tích hợp Scipy signal.find_peaks_cwt để thực hiện công việc?

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]

các kết quả:

maxima [ 0.9995736]
minima [ 0.09146464]

Trân trọng


7
Thay vì thực hiện phép chia (với khả năng mất độ chính xác có thể), tại sao không chỉ nhân với -1 để đi từ cực đại đến cực tiểu?
Livius

Tôi đã cố gắng thay đổi '1 / dữ liệu' thành 'dữ liệu * -1', nhưng sau đó nó phát sinh lỗi, bạn có thể chia sẻ cách triển khai phương pháp của mình không?
Một STEFani

Có lẽ bởi vì chúng tôi không muốn yêu cầu người dùng cuối cài đặt thêm scipy.
Damian Yerrick

5

Cập nhật: Tôi không hài lòng với gradient nên tôi thấy nó đáng tin cậy hơn khi sử dụng numpy.diff. Xin vui lòng cho tôi biết nếu nó làm những gì bạn muốn.

Liên quan đến vấn đề nhiễu, vấn đề toán học là xác định vị trí cực đại / cực tiểu nếu chúng ta muốn xem xét nhiễu, chúng ta có thể sử dụng một cái gì đó như tích chập đã được đề cập trước đó.

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

Bạn có biết độ dốc này được tính như thế nào không? Nếu bạn có dữ liệu nhiễu, có thể độ dốc thay đổi rất nhiều, nhưng điều đó không có nghĩa là có tối đa / phút.
Navi

Có tôi biết, tuy nhiên dữ liệu ồn ào là một vấn đề khác. Cho rằng tôi đoán sử dụng chập.
Mike Vella

Tôi cần một cái gì đó tương tự cho một dự án mà tôi đang thực hiện và sử dụng phương thức numpy.diff đã đề cập ở trên, tôi nghĩ rằng có thể hữu ích khi đề cập rằng đối với dữ liệu của tôi, mã trên đã bỏ lỡ một vài cực đại và cực tiểu, bằng cách thay đổi thuật ngữ giữa trong cả hai nếu các câu lệnh tương ứng <= và> =, tôi có thể bắt được tất cả các điểm.

5

Trong khi câu hỏi này thực sự cũ. Tôi tin rằng có một cách tiếp cận đơn giản hơn nhiều trong numpy (một lót).

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

Để tìm một max hoặc min cục bộ, về cơ bản chúng tôi muốn tìm khi sự khác biệt giữa các giá trị trong danh sách (3-1, 9-3 ...) thay đổi từ dương sang âm (tối đa) hoặc âm sang dương (tối thiểu). Do đó, đầu tiên chúng tôi tìm thấy sự khác biệt. Sau đó, chúng tôi tìm thấy dấu hiệu, và sau đó chúng tôi tìm thấy những thay đổi trong dấu hiệu bằng cách lấy lại sự khác biệt. (Sắp xếp giống như một đạo hàm thứ nhất và thứ hai trong phép tính, chỉ chúng ta có dữ liệu rời rạc và không có chức năng liên tục.)

Đầu ra trong ví dụ của tôi không chứa extrema (giá trị đầu tiên và cuối cùng trong danh sách). Ngoài ra, giống như phép tính, nếu đạo hàm thứ hai là âm, bạn có max và nếu nó dương thì bạn có một phút.

Vì vậy, chúng tôi có trận đấu sau:

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

1
Tôi nghĩ rằng câu trả lời này (tốt!) Có giống với câu trả lời của RC từ năm 2012 không? Anh ta cung cấp ba giải pháp một dòng, tùy thuộc vào việc người gọi muốn phút, tối đa hay cả hai, nếu tôi đọc chính xác giải pháp của mình.
Brandon Rhodes

3

Không có giải pháp nào trong số này làm việc cho tôi vì tôi cũng muốn tìm các đỉnh ở trung tâm của các giá trị lặp lại. ví dụ: trong

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

câu trả lời nên là

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

Tôi đã làm điều này bằng cách sử dụng một vòng lặp. Tôi biết nó không siêu sạch, nhưng nó hoàn thành công việc.

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

minmmaxmchứa các chỉ số của cực tiểu và cực đại tương ứng. Đối với một tập dữ liệu khổng lồ, nó sẽ đưa ra rất nhiều maximas / minimas để trong trường hợp đó làm mịn đường cong trước và sau đó áp dụng thuật toán này.


Điều này có vẻ thú vị. Không có thư viện. Làm thế nào nó hoạt động?
john ktejik 18/03/18

1
đi qua đường cong từ điểm bắt đầu và xem bạn đang đi lên hay xuống liên tục, một khi bạn thay đổi từ lên xuống có nghĩa là bạn có cực đại, nếu bạn đi xuống, bạn có cực tiểu.
prtkp

1

Một giải pháp khác sử dụng về cơ bản là toán tử giãn:

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

và cho cực tiểu:

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

Ngoài ra, từ scipy.ndimagebạn có thể thay thế rank_filter(x, -1, size=3)bằng grey_dilationrank_filter(x, 0, size=3)với grey_erosion. Điều này sẽ không yêu cầu một loại địa phương, vì vậy nó sẽ nhanh hơn một chút.


nó hoạt động đúng cho vấn đề này Đây là giải pháp hoàn hảo (+1)
Leos313

0

Một số khác:


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
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.