1D numpy配列でNumpyを使用して極大/極小を見つける


116

1D numpy配列で極大/極小を見つけることができるnumpy / scipyのモジュール関数を提案できますか?明らかに、これまでで最も単純なアプローチは、最も近い隣人を調べることですが、私は厄介なディストリビューションの一部である受け入れられた解決策が欲しいです。



1
いいえ、それは2D(私は1Dについて話している)であり、カスタム関数を含みます。私は自分の単純な実装を持っていますが、Numpy / Scipyモジュールに付属しているより良い実装があるかどうか疑問に思っていました。
ナビ

おそらく、質問を更新して、(1)1d配列があり、(2)探しているローカル最小値の種類を含めることができます。隣接する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の代わりに別の整数を使用できるように思えます。勾配の計算も考えていました。とにかくそれ以上の機能がないといけない。
ナビ

1
@ナビ:問題は、「ローカルミニマム」の概念がユースケースごとに大きく異なるため、この目的で「標準」関数を提供するのが難しいことです。平滑化は、最近傍だけでなく、それ以上のことを考慮するのに役立ちます。1の代わりに3のような別の整数を使用することは、両方向の3番目の次の要素のみを考慮し、直接の隣接要素は考慮しないため、奇妙です。
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またargrelmaxargrelminそれぞれ最大値と最小値を提供します。


1
12の意味は何ですか?
マシュマロ2014年

7
@marshmallow:np.random.random(12)12個のランダムな値を生成します。これらは関数のデモに使用されますargrelextrema
sebix 2015年

2
入力がの場合、機能test02=np.array([10,4,4,4,5,6,7,6])しません。連続する値は極小値として認識されません。
Leos313 2018年

1
ありがとう、@ Cleb。他の問題を指摘したいのですが、配列の極端な点はどうですか?配列の最後の要素も極小値なので、最初の要素も極大値です。また、見つかった連続する値の数も返しません。しかし、私はこの質問のコードでここに解決策を提案しました。ありがとうございました!!
Leos313

1
ありがとう、これは私がこれまでに見つけた最高の解決策の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関数の素敵な使い方!ただし、配列の両端で最大値が
欠落

2
これは、反復的な値がある場合にも奇妙な働きをします。たとえば、配列を取る場合[1, 2, 2, 3, 3, 3, 2, 2, 1]、極大値は明らかに3の中間にあります。しかし、提供した関数を実行すると、インデックス2、6で最大値、インデックス1、3、5、7で最小値が得られますが、私にはあまり意味がありません。
Korem

5
使用する+1代わりにこれを回避するには。np.diff()np.gradient()
ankostis

このスレッドが古くなっていることは知っていますが、曲線にノイズが多すぎる場合は、いつでも最初にローパスフィルタリングを試してスムージングすることができます。少なくとも私にとって、ローカルの最大/最小の使用のほとんどは、ローカルエリア内のグローバルな最大/最小(たとえば、大きな山と谷、データのすべての変動ではない)のためのものです
marcman

25

役立つ別のアプローチ(より多くの単語、より少ないコード):

極大と極小の位置は、一次導関数のゼロ交差の位置でもあります。一般に、極大値と極小値を直接見つけるよりも、ゼロクロッシングを見つける方がはるかに簡単です。

残念ながら、一次導関数はノイズを「増幅」する傾向があるため、元のデータに大きなノイズが存在する場合、一次導関数は、元のデータにある程度の平滑化が適用された後でのみ使用するのが最適です。

平滑化は、最も単純な意味ではローパスフィルターであるため、畳み込みカーネルを使用することで平滑化が最もよく(よく、最も簡単に)行われることが多く、そのカーネルを使用して驚くべき量の機能保持/強化機能を提供できます。 。最適なカーネルを見つけるプロセスは、さまざまな手段を使用して自動化できますが、最善の方法は、単純なブルートフォースです(小さなカーネルを見つけるにはかなり高速です)。良いカーネルは(意図したとおりに)元のデータを大幅に歪めますが、対象のピーク/バレーの場所には影響しません。

幸いなことに、単純なSWAG(「教育的推測」)を使用して適切なカーネルを作成できることがよくあります。平滑化カーネルの幅は、元のデータで予想される最も広い「興味深い」ピークよりも少し広くする必要があり、その形状はそのピーク(単一スケールのウェーブレット)に似ています。平均を維持するカーネル(適切な平滑化フィルターが何であるべきか)の場合、カーネル要素の合計は正確に1.00に等しく、カーネルはその中心に対して対称である必要があります(つまり、要素の数が奇数になります)。

最適なスムージングカーネル(または異なるデータコンテンツ用に最適化された少数のカーネル)が与えられると、スムージングの程度がたたみ込みカーネル(の「ゲイン」)のスケーリング係数になります。

"正しい"(最適な)平滑化の程度(畳み込みカーネルゲイン)の決定は、自動化することもできます。一次導関数データの標準偏差と平滑化データの標準偏差を比較します。2つの標準偏差の比率がスムージングカムの度合いの変化とともにどのように変化するかを使用して、効果的なスムージング値を予測します。いくつかの手動のデータ実行(真に代表的なもの)で十分です。

上記のすべての以前のソリューションは一次導関数を計算しますが、それらは統計的測度としては扱いません。また、上記のソリューションは、特徴を維持/平滑化することを試みません(ノイズを「飛び越えて」微妙なピークを助けるため)。

最後に、悪いニュース:「実際の」ピークを見つけることは、ノイズが実際のピーク(帯域幅のオーバーラップ)のように見える機能も持っている場合、王室の苦痛になります。次のより複雑なソリューションは、一般に、隣接する「実際の」ピーク間の関係(ピーク発生の最小または最大レートなど)を考慮した、より長い畳み込みカーネル(「より広いカーネルアパーチャ」)を使用するか、複数の幅の異なるカーネルを使用してたたみ込みが通過します(ただし、より高速な場合のみ:シーケンスで実行される線形たたみ込みは常に単一のたたみ込みにたたみ込みできるというのは、基本的な数学的真理です)。しかし、単一のステップで最終的なカーネルを直接見つけるよりも、最初に(幅が変化する)有用なカーネルのシーケンスを見つけてそれらを畳み込む方がはるかに簡単です。

うまくいけば、これは、Google(そしておそらく良い統計テキスト)がギャップを埋めるのに十分な情報を提供します。実際に動作する例、または例へのリンクを提供する時間をいただければ幸いです。誰かがオンラインで見つけた場合は、ここに投稿してください!


24

SciPyバージョン1.1以降は、find_peaksも使用できます。以下は、ドキュメント自体から取った2つの例です。

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。これは、2つのピーク間の最小距離を定義します。

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 2017年

「1 / data」を「data * -1」に変更しようとしましたが、エラーが発生しました。メソッドの実装方法を共有できますか?
STEFANI

おそらく、エンドユーザーが追加でscipyをインストールする必要がないようにするためです。
Damian Yerrick

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

この勾配の計算方法を知っていますか?ノイズの多いデータがある場合、おそらく勾配は大きく変化しますが、それは最大/最小があることを意味する必要はありません。
ナビ

はい、わかっていますが、ノイズの多いデータは別の問題です。そのためには、たたみ込みを使用すると思います。
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の回答と同じだと思いますか?私が彼のソリューションを正しく読んでいる場合、発信者が最小値、最大値、またはその両方を必要としているかに応じて、彼は3つの1行のソリューションを提供しています。
ブランドンロードス

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 2018年

1
曲線を開始点からトラバースし、上方向または下方向に連続しているかどうかを確認します。上から下に変更すると、最大値が得られ、上に下がると最小値が得られます。
prtkp 2018年

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
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.