HOWTO · Python

在 Python 中查找峰值

本教程演示了 Python 中的寻峰算法。

本页内容

局部最大值是一个大于附近样本的值;它不一定是整个序列中的最大值。对于一维序列,逐个比较内部值与相邻值即可简单地找出峰值;如果需要筛选候选峰值,也可以使用 scipy.signal.find_peaks()。这些函数返回原始数据中的零基索引,可用这些索引提取峰值。

使用 NumPy 查找简单的局部最大值

这个简短示例使用一维数组,并输出每个检测到的索引及其对应值。

import numpy as np

data = np.array([1, 3, 7, 1, 2, 6, 0, 5, 0, 4])
local_maxima = (data[1:-1] > data[:-2]) & (data[1:-1] > data[2:])
indices = np.flatnonzero(local_maxima) + 1
print("indices:", indices.tolist())
print("values:", data[indices].tolist())
indices: [2, 5, 7]
values: [7, 6, 5]

这两项比较要求样本严格大于左右两个直接相邻值。切片只比较内部样本,因此首尾元素不会作为候选值;值相同的平台也会被排除。索引偏移量用于将较短的比较数组映射回 data 中的位置。如果数据适用这条明确的规则,这种 NumPy 方法就很实用。对于此输入,检测到的索引是 [2, 5, 7],对应的值是 [7, 6, 5]。

使用 scipy.signal.find_peaks() 筛选峰值

对于常见的一维峰值搜索,SciPy 的 scipy.signal.find_peaks() 会返回局部最大值的索引,并可应用可选条件。下面的示例会筛选高度至少为 3 的峰值,并输出索引及其对应值:

import numpy as np
from scipy.signal import find_peaks

data = np.array([1, 3, 7, 1, 2, 6, 0, 5, 0, 4])
indices, properties = find_peaks(data, height=3, distance=2, prominence=1)
print("indices:", indices.tolist())
print("values:", data[indices].tolist())
indices: [2, 5, 7]
values: [7, 6, 5]

height 按峰值大小筛选,distance 要求保留的峰值之间至少相隔指定数量的样本,prominence 则衡量峰值相对于周围区域的突出程度。distance 会影响哪些峰值被保留,但不会作为属性返回。其他可选条件还包括 width 和 threshold。SciPy 会返回索引和属性字典;字典包含所请求条件适用的属性。对于此输入和这些参数,返回的索引是 [2, 5, 7],对应的值是 [7, 6, 5]。应根据信号特征选择条件,而不要假设每个局部最大值都有意义。

使用 scipy.signal.argrelextrema() 查找相对极值

如果要在指定的邻域内比较样本,可以使用 scipy.signal.argrelextrema()。传入 np.greater 这样的比较函数来查找最大值,并将 order 设为比较时每侧纳入的样本数。它的结果是一个索引数组元组,每个输入维度对应一个数组;对于一维数组,可通过 [0] 获取索引。

import numpy as np
from scipy.signal import argrelextrema

data = np.array([1, 3, 7, 1, 2, 6, 0, 5, 0, 4])
indices = argrelextrema(data, np.greater, order=1)[0]
print("indices:", indices.tolist())
print("values:", data[indices].tolist())
indices: [2, 5, 7]
values: [7, 6, 5]

当 order=1 时,每个候选值会与左右各一个样本进行比较。增大 order 会扩大比较邻域,因此可能得到不同的相对极值。对于此输入,在 order=1 时返回的索引是 [2, 5, 7],对应的值是 [7, 6, 5]。

使用 detecta.detect_peaks() 作为替代方案

detecta.detect_peaks() 是用于一维序列的第三方替代方案。请在项目环境中运行 python -m pip install detecta 安装此依赖,然后将示例数据传给 detect_peaks():

data = [1, 3, 7, 1, 2, 6, 0, 5, 0, 4]
try:
    from detecta import detect_peaks
except ImportError:
    print("Install detecta with: python -m pip install detecta")
else:
    indices = detect_peaks(data)
    values = [data[index] for index in indices]
    print("indices:", list(indices))
    print("values:", values)

对于示例序列,预期的零基峰值索引是 [2, 5, 7],对应的值是 [7, 6, 5]。提供的运行时证据只确认了缺少依赖时显示的消息,因此这里的 detecta 结果是预期对照值,并非实际运行观察到的输出。请保留导入保护:如果无法使用 detecta,示例会打印安装命令,而不会尝试检测峰值。

选择峰值检测方法

如果只需严格比较直接相邻样本来查找最大值,而且希望直接了解判断逻辑,可以使用 NumPy 比较法。如果需要按高度、间距、突出程度或宽度等条件筛选,请选择 scipy.signal.find_peaks()。如果极值的定义主要取决于比较邻域和比较函数,请使用 scipy.signal.argrelextrema()。如果第三方 API 适合你的项目,并且你愿意添加相应依赖,也可以考虑 detecta.detect_peaks()。

所有返回的位置都是输入序列中的零基索引。解释结果之前,请先确认方法的定义是否适用于你的数据,因为端点处理、平台处理和筛选条件各不相同。以上示例针对一维数值数据中的局部最大值;多维峰值检测需要使用适用于相应数据形状的方法。