在一维Numpy数组中使用Numpy查找局部最大值/最小值


116

您能否建议使用numpy / scipy中的模块函数在一维numpy数组中找到局部最大值/最小值?显然,最简单的方法是看一下最近的邻居,但我想拥有一个被接受的解决方案,它是numpy发行版的一部分。



1
不,这是2D模式(我在谈论1D模式),并且涉及自定义功能。我有自己的简单实现,但我想知道是否有Numpy / Scipy模块附带的更好的实现。
Navi

也许您可以更新问题以包括(1)您有一个1d数组和(2)您正在寻找哪种局部最小值。只是一个条目小于两个相邻条目?
Sven Marnach 2011年

1
如果您正在谈论有噪声的数据,可以看看scipy.signal.find_peaks_cwt
Lakshay Garg 2015年

Answers:


66

如果您要查找一维数组中所有a小于其邻居的条目,则可以尝试

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

您还可以在使用此步骤之前使数组平滑numpy.convolve()

我认为没有专用的功能。


嗯,我为什么要弄平滑?要消除噪音?听起来很有趣。在我看来,我可以在示例代码中使用另一个整数代替1。我也在考虑计算梯度。无论如何,如果没有功能,那太糟糕了。
Navi

1
@Navi:问题在于“局部最小值”的概念因用例而异,因此很难为此目的提供“标准”功能。平滑不仅要考虑最近的邻居,还需要考虑更多。使用不同的整数而不是1(例如3)会很奇怪,因为它将只考虑两个方向上的第三个下一个元素,而不考虑直接的neihgbors。
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的意义是什么?
棉花糖

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

5
为了避免这种+1代替np.diff()使用np.gradient()
ankostis 2015年

我知道这个线程已经使用多年了,但是值得一提的是,如果您的曲线过于嘈杂,您总是可以先尝试使用低通滤波进行平滑处理。至少对我来说,我大部分的本地最大/最小值使用都是针对某个局部区域内的全局最大/最小值(例如,大的
峰谷

25

另一种方法(更多的单词,更少的代码)可能会有所帮助:

局部最大值和最小值的位置也是一阶导数的零交叉的位置。通常,找到零交叉比直接找到局部最大值和最小值要容易得多。

不幸的是,一阶导数往往会“放大”噪声,因此,如果原始数据中存在明显的噪声,则仅在对原始数据进行一定程度的平滑处理后,才最好使用一阶导数。

因为从最简单的意义上讲,平滑是一个低通滤波器,所以平滑通常是最好的(很好,最容易),它是使用卷积内核完成的,并且“整形”内核可以提供惊人数量的特征保留/增强功能。查找最佳内核的过程可以使用多种方法实现自动化,但最好的方法可能是简单的蛮力操作(查找小内核的速度非常快)。一个好的内核将(按预期的方式)使原始数据大量失真,但不会影响目标峰/谷的位置。

幸运的是,通常可以通过简单的SWAG(“有根据的猜测”)创建合适的内核。平滑内核的宽度应比原始数据中最宽的预期“有趣”峰稍宽一些,并且其形状将类似于该峰(单刻度小波)。对于保留均值的内核(应该有任何良好的平滑滤波器),内核元素的总和应精确等于1.00,并且内核应关于其中心对称(这意味着它将具有奇数个元素)。

给定最佳平滑内核(或为不同数据内容优化的少量内核),平滑程度就成为卷积内核(“卷积”)的缩放因子。

甚至可以自动确定“正确的”(最佳)平滑度(卷积核增益):将一阶导数数据的标准偏差与平滑数据的标准偏差进行比较。两个标准偏差的比率如何随平滑度的变化而变化,可用于预测有效的平滑值。只需要一些手动数据运行(真正具有代表性)。

上面发布的所有现有解决方案均计算一阶导数,但它们并未将其视为统计量,上述解决方案也未尝试执行特征保留/增强平滑(以帮助微妙的峰值“跨越”噪声)。

最后,一个坏消息是:当噪声还具有看起来像真实峰值(重叠带宽)的特征时,找到“真实”峰值变得很痛苦。下一个更复杂的解决方案通常是使用更长的卷积核(“更大的核孔径”),该卷积核考虑了相邻“真实”峰之间的关系(例如峰出现的最小或最大速率),或使用多个卷积使用具有不同宽度的内核传递(但前提是速度更快:这是一个基本的数学真理,按顺序执行的线性卷积始终可以一起卷积为单个卷积)。但是,通常要比一个步骤直接找到最终内核要容易得多,首先找到一系列有用的内核(宽度可变)并将它们卷积在一起。

希望这可以提供足够的信息,以使Google(也许还有不错的统计信息)能够填补空白。我真的希望我有时间提供一个可行的示例或一个示例的链接。如果有人在网上碰到过,请在此处发布!


21

从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',但随后引发错误,您能否分享如何实现方法?
STEFANI

也许是因为我们不想要求最终用户额外安装scipy。
Damian Yerrick '19

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

你知道这个梯度是如何计算的吗?如果您有嘈杂的数据,则梯度可能会发生很大变化,但这并不一定意味着存在最大值/最小值。
Navi

是的,我知道,但是嘈杂的数据是另一个问题。为此,我想使用卷积。
Mike Vella

对于我正在处理的项目,我需要类似的东西,并且使用了上面提到的numpy.diff方法,我认为提及上述代码可能会有所帮助,因为上面的代码都通过更改两个代码的中间项而错过了一些最大值和最小值如果分别对<=和> =的if语句,我能够抓住所有要点。

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

要找到局部最大值或最小值,我们本质上是想查找列表中值(3-1、9-3 ...)之间的差值从正变为负(最大值)或从负变为正(最小值)。因此,首先我们发现差异。然后我们找到符号,然后通过再次求和以找到符号的变化。(类似于微积分中的一阶和二阶导数,只有我们有离散的数据,没有连续的函数。)

在我的示例中,输出不包含极值(列表中的第一个和最后一个值)。同样,与微积分一样,如果二阶导数为负,则表示最大值,如果其为正,则表示最小值。

因此,我们有以下比赛:

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

1
我认为这个(好!)答案与RC从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包含最小值和最大值的索引。对于庞大的数据集,它将提供很多最大值/最小值,因此在这种情况下,请先平滑曲线,然后再应用此算法。


这看起来很有趣。没有图书馆。它是如何工作的?
约翰·克特吉克

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
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.