非均匀X网格下scipy.signal.find_peaks峰值检测问题及方案问询
我用scipy.signal.find_peaks检测峰值的坐标和特征(峰宽、突出度等),数据满足:
Yi = f(Xi)
其中i是数据点编号,Xi是X坐标,Yi是对应信号值。
处理均匀X网格(相邻点间距恒定)时,这个函数表现很好,能把实际X轴宽度转换成样本数来计算;但处理非均匀X网格(相邻点间距不等,即x(i)-x(i-1)≠x(i+1)-x(i))时,检测结果不理想。
我试过用width参数(要求以样本数指定峰的最小/最大宽度):
all_peaks_indexes = signal.find_peaks(signal_y, prominence=search_peak_prominence, width=1, height=1)
但width的单位是样本数,对非均匀X网格不适用。根据参数说明:
WIDTH - Required width of peaks in samples.
Either a number, None, an array matching x, or a 2-element sequence of the former.
The first element is always interpreted as the minimal and the second, if supplied, as
the maximal required width.
核心问题是find_peaks只使用索引(样本编号),不考虑实际X坐标,没法直接用实际宽度来筛选。
请问有没有支持基于实际信号维度(比如时间)设置峰宽的非均匀X网格峰值检测工具?或者有什么解决办法?
1. 用scipy.signal.peak_widths配合实际X坐标后过滤
先调用find_peaks找到候选峰值,再用peak_widths传入实际X数组计算真实宽度,最后过滤不符合要求的峰值。示例代码:
import scipy.signal as signal # 先获取候选峰值 peaks, props = signal.find_peaks(signal_y, prominence=search_peak_prominence, height=1) # 传入实际X坐标,计算半高宽(可通过rel_height调整高度阈值) widths, _, _, _ = signal.peak_widths(signal_y, peaks, rel_height=0.5, x=signal_x) # 按实际宽度范围筛选峰值 min_width = 2.0 # 实际X维度的最小宽度 max_width = 10.0 # 实际X维度的最大宽度 valid_peaks = peaks[(widths >= min_width) & (widths <= max_width)] valid_props = {k: v[(widths >= min_width) & (widths <= max_width)] for k, v in props.items()}
这种方法利用scipy原生工具,既保留find_peaks的检测能力,又能基于实际X维度筛选宽度。
2. 自定义峰值宽度判断逻辑
如果需要更灵活的宽度计算规则(比如用特定高度阈值),可以手动遍历候选峰值计算实际宽度:
peaks, props = signal.find_peaks(signal_y, prominence=search_peak_prominence, height=1) valid_peaks = [] min_width = 2.0 max_width = 10.0 half_height_ratio = 0.5 # 用峰值的半高计算宽度 for peak_idx in peaks: peak_height = signal_y[peak_idx] threshold = peak_height * half_height_ratio # 找左侧第一个低于阈值的点 left_idx = peak_idx while left_idx > 0 and signal_y[left_idx] >= threshold: left_idx -= 1 # 找右侧第一个低于阈值的点 right_idx = peak_idx while right_idx < len(signal_y)-1 and signal_y[right_idx] >= threshold: right_idx += 1 # 计算实际X宽度并判断 actual_width = signal_x[right_idx] - signal_x[left_idx] if min_width <= actual_width <= max_width: valid_peaks.append(peak_idx)
这种方法完全自定义规则,适合特殊场景,但效率略低于原生工具。
3. 重采样为均匀X网格
如果数据允许损失部分细节,可以把非均匀数据插值成均匀网格,再用find_peaks的width参数:
import numpy as np # 生成均匀X网格,num控制分辨率 uniform_x = np.linspace(signal_x.min(), signal_x.max(), num=1000) # 插值得到对应Y值 uniform_y = np.interp(uniform_x, signal_x, signal_y) # 转换实际宽度为样本数 grid_spacing = uniform_x[1] - uniform_x[0] width_samples = int(min_width / grid_spacing) # 用find_peaks检测 peaks_uniform, props_uniform = signal.find_peaks(uniform_y, prominence=search_peak_prominence, width=width_samples, height=1) # 转换回原X坐标 actual_peaks_x = uniform_x[peaks_uniform]
注意:重采样可能丢失峰值附近的精细结构,需要根据数据情况调整分辨率。
4. 其他替代方案
- 用
pandas结合滚动窗口实现自定义峰值检测,但需要自己编写峰值判断和宽度计算逻辑。 - 完全自定义峰值检测函数:遍历数据点判断峰值,同时基于实际X坐标计算宽度,适合高度定制化的场景。
内容的提问来源于stack exchange,提问作者twistfire

