1D numpy 배열에서 Numpy를 사용하여 로컬 최대 값 / 최소값 찾기


116

1D numpy 배열에서 로컬 최대 값 / 최소값을 찾을 수있는 numpy / scipy의 모듈 함수를 제안 할 수 있습니까? 분명히 가장 간단한 접근 방식은 가장 가까운 이웃을 보는 것입니다. 그러나 저는 numpy 배포판의 일부인 수용된 솔루션을 갖고 싶습니다.



1
아니 그것은 2D (나는 1D에 대해 이야기하고 있음)이고 사용자 정의 기능을 포함합니다. 나만의 간단한 구현이 있지만 Numpy / Scipy 모듈과 함께 제공되는 더 나은 구현이 있는지 궁금합니다.
Navi 2011 년

질문을 업데이트하여 (1) 1d 배열이 있고 (2) 어떤 종류의 로컬 최소값을 찾고 있는지 포함 할 수 있습니다. 인접한 두 항목보다 작은 항목이 있습니까?
Sven Marnach 2011 년

1
당신이 소음 데이터의 이야기 경우 scipy.signal.find_peaks_cwt 살펴 수 있습니다
Lakshay Garg를

답변:


66

a이웃보다 작은 1d 배열의 모든 항목을 찾고 있다면 시도해 볼 수 있습니다.

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

당신은 또한 수 부드럽게 사용이 단계 전에 배열을 numpy.convolve().

나는 이것에 대한 전용 기능이 없다고 생각합니다.


음, 왜 부드럽게해야합니까? 소음을 제거하려면? 흥미롭게 들리 네요. 예제 코드에서 1 대신 다른 정수를 사용할 수있는 것 같습니다. 그래디언트 계산도 생각하고있었습니다. 어쨌든 그 이상의 기능이 없다면 너무 나쁘다.
Navi 2011 년

1
@Navi : 문제는 "로컬 최소값"이라는 개념이 사용 사례마다 크게 다르기 때문에이 목적을위한 "표준"기능을 제공하기가 어렵다는 것입니다. 평활화는 가장 가까운 이웃보다 더 많은 것을 고려하는 데 도움이됩니다. 1 대신 다른 정수 (예 : 3)를 사용하는 것은 양방향에서 세 번째 다음 요소 만 고려하고 직접적인 neihgbor는 고려하지 않기 때문에 이상 할 것입니다.
Sven Marnach 2011 년

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의 인덱스입니다. 값을 얻으려면 다음을 시도하십시오.

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

scipy.signal또한 제공 argrelmax하고 argrelmin각각 최대 값과 최소값을 찾기위한.


1
12의 의미는 무엇입니까?
marshmallow

7
@marshmallow : np.random.random(12)12 개의 임의의 값을 생성하며 함수를 설명하는 데 사용됩니다 argrelextrema.
sebix

2
입력이 test02=np.array([10,4,4,4,5,6,7,6])이면 작동하지 않습니다. 연속 된 값을 로컬 최소값으로 인식하지 않습니다.
Leos313

1
감사합니다, @Cleb. 다른 문제를 지적하고 싶습니다. 어레이의 극한 지점은 어떻습니까? 배열의 마지막 요소도 로컬 최소값이므로 첫 번째 요소도 로컬 최대 값입니다. 또한 발견 된 연속 값 수를 반환하지 않습니다. 그러나 나는 여기이 질문의 코드에서 해결책을 제안했습니다 . 감사합니다!!
Leos313

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에서 최소값을 얻습니다.
Korem 2013 년

5
이를 방지하려면 +1대신에 np.diff()사용 np.gradient().
ankostis

이 스레드가 오래되었다는 것을 알고 있지만 곡선이 너무 시끄러 우면 항상 저역 통과 필터링을 먼저 시도하여 평활화를 시도 할 수 있습니다. 적어도 저에게 대부분의 로컬 최대 / 최소 사용은 일부 로컬 영역 (예 : 데이터의 모든 변동이 아닌 큰 봉우리 및 계곡) 내 전역 최대 / 최소입니다.
marcman

25

도움이 될 수있는 또 다른 접근 방식 (더 많은 단어, 적은 코드) :

국소 최댓값과 최솟값의 위치는 1 차 미분의 제로 교차점의 위치이기도합니다. 일반적으로 로컬 최대 값과 최소값을 직접 찾는 것보다 제로 교차점을 찾는 것이 훨씬 쉽습니다.

불행히도 1 차 미분은 노이즈를 "증폭"하는 경향이 있으므로 원본 데이터에 상당한 노이즈가있는 경우 1 차 미분은 원본 데이터에 어느 정도의 평활화가 적용된 후에 만 ​​사용하는 것이 가장 좋습니다.

평활화는 가장 단순한 의미에서 저역 통과 필터이기 때문에 평활화는 종종 컨볼 루션 커널을 사용하여 가장 잘 (가장 쉽게) 수행되며 커널이 놀라운 양의 기능 보존 / 향상 기능을 제공 할 수 있도록 "형성"합니다. . 최적의 커널을 찾는 프로세스는 다양한 수단을 사용하여 자동화 할 수 있지만 가장 좋은 방법은 단순한 무차별 대입 (작은 커널을 찾는 데 매우 빠름) 일 수 있습니다. 좋은 커널은 (의도 한대로) 원본 데이터를 크게 왜곡하지만 관심있는 피크 / 밸리의 위치에는 영향을주지 않습니다.

다행히도 간단한 SWAG ( "교육 된 추측")를 통해 적절한 커널을 만들 수 있습니다. 평활화 커널의 너비는 원래 데이터에서 예상되는 가장 넓은 "흥미로운"피크보다 약간 더 넓어야하며 그 모양은 해당 피크 (단일 스케일 웨이블릿)와 유사합니다. 평균 보존 커널 (좋은 평활 필터가 있어야하는 것)의 경우 커널 요소의 합은 정확히 1.00이어야하며 커널은 중심에 대해 대칭이어야합니다 (즉, 요소 ​​수가 홀수임을 의미합니다.

최적의 평활화 커널 (또는 다른 데이터 콘텐츠에 최적화 된 적은 수의 커널)이 주어지면 평활화 정도는 컨볼 루션 커널 (의 "이득")에 대한 스케일링 인자가됩니다.

평활화 (컨볼 루션 커널 이득)의 "올바른"(최적) 정도를 결정하는 것도 자동화 할 수 있습니다. 1 차 도함수 데이터의 표준 편차를 평활 데이터의 표준 편차와 비교합니다. 스무딩 캠 정도의 변화에 ​​따라 두 표준 편차의 비율이 어떻게 변하는지를 사용하여 효과적인 스무딩 값을 예측합니다. 몇 가지 수동 데이터 실행 (정말로 대표적 임) 만 있으면됩니다.

위에 게시 된 모든 이전 솔루션은 1 차 도함수를 계산하지만이를 통계적 측정으로 취급하지 않으며, 위의 솔루션은 기능 유지 / 향상 평활화를 수행하려고 시도하지 않습니다 (미묘한 피크가 노이즈 "위로"도약하는 데 도움이 됨).

마지막으로, 나쁜 소식 : "실제"피크를 찾는 것은 노이즈가 실제 피크 (중복 대역폭)처럼 보이는 기능도 포함 할 때 왕실의 고통이됩니다. 다음으로 더 복잡한 솔루션은 일반적으로 인접한 "실제"피크 (예 : 피크 발생에 대한 최소 또는 최대 속도) 간의 관계를 고려하는 더 긴 컨볼 루션 커널 ( "더 넓은 커널 애 퍼처")을 사용하거나 다중을 사용하는 것입니다. 컨볼 루션은 너비가 다른 커널을 사용하여 전달됩니다 (그러나 더 빠를 경우에만 : 순차적으로 수행되는 선형 컨볼 루션이 항상 단일 컨볼 루션으로 함께 컨볼 루션 될 수 있다는 것이 기본적인 수학적 사실입니다). 그러나 한 단계에서 최종 커널을 직접 찾는 것보다 먼저 유용한 커널 시퀀스 (다양한 너비)를 찾아서 함께 연결하는 것이 훨씬 쉽습니다.

이 정보가 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을 곱하여 최대 값에서 최소값으로 이동하는 것이 어떻습니까?
Livius

'1 / data'를 'data * -1'로 변경하려고했지만 오류가 발생합니다. 방법을 구현하는 방법을 공유 할 수 있습니까?
A STEFANI 2011

최종 사용자가 scipy를 추가로 설치하도록 요구하지 않기 때문일 수 있습니다.
Damian Yerrick 19-06-14

5

업데이트 : 그래디언트가 마음에 들지 않아 사용하는 것이 더 안정적이라는 것을 알았습니다 numpy.diff. 원하는대로 작동하는지 알려주세요.

노이즈 문제와 관련하여 수학적 문제는 앞서 언급 한 convolve와 같은 것을 사용할 수있는 노이즈를보고 싶다면 최대 / 최소를 찾는 것입니다.

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

이 기울기가 어떻게 계산되는지 아십니까? 노이즈가 많은 데이터가있는 경우 그래디언트가 많이 변경되지만 최대 / 최소가 있음을 의미 할 필요는 없습니다.
Navi

예, 압니다. 그러나 시끄러운 데이터는 다른 문제입니다. 이를 위해 convolve를 사용하십시오.
Mike Vella

나는 내가 작업하고 있던 프로젝트에 비슷한 것이 필요했고 위에서 언급 한 numpy.diff 메서드를 사용했습니다. 두 가지 모두에서 중간 용어를 변경하여 내 데이터의 경우 위 코드가 몇 가지 최대 값과 최소값을 놓쳤다는 것을 언급하는 것이 도움이 될 것이라고 생각했습니다. if 문을 <= 및> = 각각에 대해 모든 포인트를 잡을 수있었습니다.

5

이 질문은 정말 오래되었습니다. numpy (1 라이너)에는 훨씬 더 간단한 접근 방식이 있다고 생각합니다.

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 차 및 2 차 도함수처럼, 우리는 이산 데이터를 가지고 있고 연속 함수가 없습니다.)

내 예제의 출력에는 극값 (목록의 첫 번째 및 마지막 값)이 포함되어 있지 않습니다. 또한 미적분과 마찬가지로 2 차 도함수가 음수이면 최대 값이고 양수이면 최소값입니다.

따라서 다음과 같은 매치업이 있습니다.

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

1
이 (좋은!) 대답이 2012 년 RC의 대답과 같은 것 같아요? 그는 내가 그의 솔루션을 올바르게 읽고 있다면 호출자가 최소값, 최대 값 또는 둘 다를 원하는지 여부에 따라 세 가지 단선 솔루션을 제공합니다.
Brandon Rhodes

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

minmmaxm각각 최소값과 최대 값의 인덱스를 포함한다. 거대한 데이터 세트의 경우 많은 최대 / 최소값을 제공하므로이 경우 먼저 곡선을 매끄럽게 만든 다음이 알고리즘을 적용합니다.


흥미로워 보입니다. 도서관이 없습니다. 어떻게 작동합니까?
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_dilationrank_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
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.