在numpy数组中找到局部最大值

14
我希望找到一些高斯平滑数据中的峰值。我看了一些可用的峰值检测方法,但它们需要一个搜索范围作为输入,而我想要更自动化的方式。这些方法也是设计用于非平滑数据。由于我的数据已经平滑了,所以我需要一种更简单的方法来检索峰值。我的原始数据和平滑数据在下面的图表中。

enter image description here

基本上,有没有一种 Pythonic 的方法可以从平滑数据的数组中检索最大值,例如一个数组:

    a = [1,2,3,4,5,4,3,2,1,2,3,2,1,2,3,4,5,6,5,4,3,2,1]

会返回:

    r = [5,3,6]

1
你的图表数据和数组a之间的差异很明显。对于图表数据,我倾向于从数据中减去平滑版本,并通过使用类似于中位数绝对偏差的统计显着峰值进行阈值处理。 - ebarr
请参考此处所提出的类似问题:https://dev59.com/P10Z5IYBdhLWcg3wyyx1?noredirect=1&lq=1 - Nathan majicvr.com
1
这回答解决了你的问题吗?如何在 NumPy 的 1D 数组中找到局部最大/最小值 - Mr. T
4个回答

31

有一个内置函数argrelextrema可以完成这项任务:

import numpy as np
from scipy.signal import argrelextrema
    
a = np.array([1,2,3,4,5,4,3,2,1,2,3,2,1,2,3,4,5,6,5,4,3,2,1])

# determine the indices of the local maxima
max_ind = argrelextrema(a, np.greater)

# get the actual values using these indices
r = a[max_ind]  # array([5, 3, 6])

这将为r提供所需的输出。

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

enter image description here

另一个非常有用的参数是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()

在这里输入图片描述


3
@NathanThomas:很高兴能帮到你!顺便说一句,如果你对极小值感兴趣,可以把 np.greater 替换为 np.less - Cleb

2

如果您的原始数据存在噪声,那么使用统计方法是更可取的,因为不是所有的峰值都具有显著性。对于您的a数组,一个可能的解决方案是使用双微分:

peaks = a[1:-1][np.diff(np.diff(a)) < 0]
# peaks = array([5, 3, 6])

我遵循了这篇文章(https://dev59.com/WF8e5IYBdhLWcg3wnbVj)中的一些建议,进行了一般性的高斯滤波以去除噪声。这些数据来自光谱仪,因此我正在尝试在带宽上找到峰值响应。我认为高斯滤波会给出一个很好的近似值(不需要是精确的峰值)。 - GeoMonkey
你是在寻找上面图表中红线或绿线的峰值吗? - ebarr
我正在寻找红线的峰值。我应该讲得更清楚。每个峰值都是一个带宽,所以我想获得该带宽的最大值。 - GeoMonkey
2
我认为这里的代码应该可以解决你的问题:https://gist.github.com/endolith/250860 - ebarr

1
>> import numpy as np
>> from scipy.signal import argrelextrema
>> a = np.array([1,2,3,4,5,4,3,2,1,2,3,2,1,2,3,4,5,6,5,4,3,2,1])
>> argrelextrema(a, np.greater)
array([ 4, 10, 17]),)
>> a[argrelextrema(a, np.greater)]
array([5, 3, 6])

如果您的输入代表了一个嘈杂的分布,您可以尝试使用NumPy convolve函数对其进行平滑处理

0

如果您可以排除数组边缘的最大值,您总是可以通过检查以下内容来检查一个元素是否比其每个邻居都大:

import numpy as np
array = np.array([1,2,3,4,5,4,3,2,1,2,3,2,1,2,3,4,5,6,5,4,3,2,1])
# Check that it is bigger than either of it's neighbors exluding edges:
max = (array[1:-1] > array[:-2]) & (array[1:-1] > array[2:])
# Print these values
print(array[1:-1][max])
# Locations of the maxima
print(np.arange(1, array.size-1)[max])

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接