关于python:在一维numpy数组中使用Numpy查找局部最大值/最小值

Finding local maxima/minima with Numpy in a 1D numpy array

你能从numpy/scipy中推荐一个模块函数,它可以在1d numpy数组中找到局部的maxima/minima吗?显然,最简单的方法是看看最近的邻居,但我想有一个公认的解决方案,它是numpy发行版的一部分。


在scipy中>=0.11

1
2
3
4
5
6
7
8
9
10
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)

生产

1
2
3
4
5
6
7
8
>>> 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的局部最大/最小索引。要获取值,请尝试:

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

scipy.signal还提供argrelmaxargrelmin来分别寻找极大值和极小值。


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

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

在这个步骤之前,您还可以使用numpy.convolve()平滑阵列。

我认为没有专门的功能。


对于噪声不太大的曲线,我建议使用以下小代码段:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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减少了原始的指数。


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

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

不幸的是,一阶导数往往会"放大"噪声,因此,当原始数据中存在显著噪声时,只有在原始数据应用了一定程度的平滑后,才最好使用一阶导数。

因为平滑,在最简单的意义上,是一个低通滤波器,平滑往往是最好的(好,最容易)通过使用卷积核,并"塑造"内核可以提供惊人数量的功能保留/增强能力。找到一个最佳内核的过程可以使用多种方法自动进行,但最好的方法可能是简单的蛮力(足够快的找到小内核)。一个好的内核(如预期的那样)会严重扭曲原始数据,但不会影响感兴趣的峰谷位置。

幸运的是,通常可以通过一个简单的swag("受过教育的猜测")创建一个合适的内核。平滑内核的宽度应该比原始数据中最宽的预期"有趣"峰值稍宽一点,其形状将类似于该峰值(单尺度小波)。对于平均保留内核(任何一个好的平滑过滤器应该是什么),内核元素的和应该精确地等于1.00,并且内核应该围绕其中心对称(意味着它将有奇数个元素)。

给定一个最佳平滑核(或针对不同数据内容优化的少量核),平滑度成为卷积核(增益)的比例因子。

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

上面发布的所有先前的解决方案都计算一阶导数,但它们不会将其视为统计指标,也不会尝试执行功能保留/增强平滑(以帮助细微的峰值"跳过"噪声)。

最后,坏消息是:发现"真实"峰值成为一个皇家痛苦,当噪音也有像真实峰值的特征(重叠带宽)。下一个更复杂的解决方案通常是使用较长的卷积核("较宽的核孔径"),考虑到相邻"真实"峰之间的关系(例如峰出现的最小或最大速率),或使用不同宽度的核(但仅当它更快时:它是一个基金)使用多个卷积过程。顺行的数学真理,即线性卷积总是可以卷积到一个单独的卷积中。但是,首先找到一系列有用的内核(宽度不同)并将它们卷积在一起要比直接在一个步骤中找到最终的内核容易得多。

希望这能提供足够的信息,让谷歌(也许还有一个好的统计文本)填补空白。我真希望我有时间提供一个有效的例子,或者一个链接到一个。如果有人在网上遇到一个,请把它贴在这里!


为什么不使用scipy内置函数信号。找到峰值来完成这项工作?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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]

结果:

1
2
maxima [ 0.9995736]
minima [ 0.09146464]

当做


从scipy 1.1版开始,您还可以使用find_peaks。以下是文档本身的两个示例。

使用height参数,可以选择高于某个阈值的所有最大值(在本例中,所有非负最大值;如果必须处理噪声基线,这非常有用;如果要查找最小值,只需将输入值乘以-1):

1
2
3
4
5
6
7
8
9
10
11
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()

enter image description here

另一个非常有用的论点是distance,它定义了两个峰值之间的最小距离:

1
2
3
4
5
6
7
8
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()

氧化镁


更新:我对梯度不满意,所以我发现使用numpy.diff更可靠。如果它能满足你的要求,请告诉我。

关于噪声问题,数学问题是找到最大值/最小值,如果我们想看噪声,我们可以使用前面提到的卷积。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
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()


虽然这个问题很古老。我相信在numpy中有一个更简单的方法(一行程序)。

1
2
3
4
5
6
7
8
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
3
[1,  3,  9,  5,  2,  5,  6,  9,  7]
    [0, -2,  0,  2,  0,  0, -2]
        Max     Min         Max


这些解决方案都不适合我,因为我也想在重复值的中心找到峰值。例如,在

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

答案应该是

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

我用循环来做这个。我知道它不是超干净的,但它能完成工作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
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分别包含最小和最大指数。对于一个巨大的数据集,它将给出大量的最大值/最小值,因此在这种情况下,首先平滑曲线,然后应用该算法。