Scipy.signal peak_widths无法适配图表的问题及替代方案咨询
解决scipy.signal.peak_widths图表适配问题及替代方案
一、修复peak_widths的图表适配问题
find_peaks能正常识别峰位,但peak_widths的结果错位,大多是因为对返回值的单位理解有误,或者没有做坐标转换。
1. 明确返回值的含义
scipy.signal.peak_widths默认返回的宽度、左右边界都是样本点的索引值(浮点型,包含插值结果),如果你的x轴是自定义坐标(比如时间、频率等非连续整数),直接用这些索引值绘图必然错位。
2. 转换为实际x轴坐标的代码示例
import numpy as np from scipy.signal import find_peaks, peak_widths import matplotlib.pyplot as plt # 示例数据(自定义x轴) x = np.linspace(0, 15, 150) y = np.sin(x) + np.random.normal(0, 0.15, 150) peaks, _ = find_peaks(y, height=0.6) # 获取半高宽数据(rel_height=0.5指定半高位置) widths, width_heights, left_ips, right_ips = peak_widths(y, peaks, rel_height=0.5) # 将浮点索引转换为实际x轴坐标(适配均匀/非均匀x轴) left_x = np.interp(left_ips, np.arange(len(x)), x) right_x = np.interp(right_ips, np.arange(len(x)), x) peak_x = x[peaks] # 绘图验证 plt.plot(x, y, label="原始数据") plt.plot(peak_x, y[peaks], "x", color="orange", label="峰位") # 绘制半高宽线段 for lx, rx, h in zip(left_x, right_x, width_heights): plt.hlines(h, lx, rx, color="red", label="半高宽" if lx == left_x[0] else "") plt.legend() plt.show()
3. 常见错误排查
- 未指定
rel_height:默认值为1.0(峰值高度),此时宽度为0,无法显示,必须指定合理值(如0.5对应半高宽) - 直接用整数索引取x值:忽略了
left_ips/right_ips的浮点插值特性,导致峰宽位置偏移 - x轴非均匀间隔:必须用
np.interp做坐标映射,不能直接用步长计算
二、替代peak_widths的峰宽计算方法
如果peak_widths仍不符合需求,可以尝试以下几种方式:
1. 手动计算半高宽(适用于简单峰型)
def get_half_width(x, y, peak_idx): peak_y = y[peak_idx] half_y = peak_y * 0.5 # 找峰值左侧第一个低于半高的点 left_idx = np.argmax(y[:peak_idx] < half_y) # 找峰值右侧第一个低于半高的点 right_idx = peak_idx + np.argmax(y[peak_idx:] < half_y) # 线性插值得到精确半高位置 left_x = np.interp(half_y, y[left_idx:left_idx+2][::-1], x[left_idx:left_idx+2][::-1]) right_x = np.interp(half_y, y[right_idx-1:right_idx+1], x[right_idx-1:right_idx+1]) return right_x - left_x, left_x, right_x # 使用示例 width, lx, rx = get_half_width(x, y, peaks[0]) plt.hlines(y[peaks[0]]*0.5, lx, rx, color="green", label="手动计算半高宽")
2. 结合峰突出度计算宽度
利用peak_prominences获取峰的基座边界,以此计算宽度:
from scipy.signal import peak_prominences prominences, left_bases, right_bases = peak_prominences(y, peaks) # 基座宽度(从左基座到右基座的x轴距离) base_widths = x[right_bases] - x[left_bases] # 绘图 for peak, lb, rb in zip(peaks, left_bases, right_bases): plt.hlines(y[peak]-prominences[np.where(peaks==peak)[0][0]], x[lb], x[rb], color="blue", linestyle="--")
3. 曲线拟合计算宽度(适用于已知峰型)
如果峰符合高斯、洛伦兹等分布,拟合后直接从参数推导宽度:
from scipy.optimize import curve_fit def gaussian(x, amp, mean, std): return amp * np.exp(-(x - mean)**2 / (2*std**2)) # 拟合单个峰 target_peak_idx = peaks[0] fit_x = x[target_peak_idx-15:target_peak_idx+15] fit_y = y[target_peak_idx-15:target_peak_idx+15] # 初始参数猜测(峰值幅度、峰位、标准差) popt, _ = curve_fit(gaussian, fit_x, fit_y, p0=[y[target_peak_idx], x[target_peak_idx], 1]) # 高斯峰半高宽≈2.3548*标准差 half_width = 2.3548 * popt[2]
内容的提问来源于stack exchange,提问作者Ray Chiang
相关产品推荐
相关产品推荐

