如何使用Python的find_peaks函数精准识别数据中前7个高突出度峰值?
看起来你遇到的问题是用scipy的find_peaks找高突出度峰时,总是混进一些不符合预期的点,而用滚动百分位平滑数据的方法虽然能拿到正确结果,但速度太慢,对吧?我来帮你梳理一下这个问题,还有你后来想到的那个候选峰筛选方法到底靠不靠谱~
直接用find_peaks的问题
你一开始直接调用:
find_peaks(series, prominence=np.max(series) * 0.1, distance=48)
再取前7个突出度最高的峰,结果混进了一些 undesired 的点,这大概率是因为原始数据里的局部小波动刚好达到了prominence的阈值(最大值得10%),被find_peaks当成了有效峰。尤其是当数据本身有不少小凸起的时候,这种情况很常见。
你的“笨方法”的局限
用滚动窗口计算80分位数再做差的平滑方式,确实能过滤掉那些小波动,把真正的高突出峰凸显出来,但滚动窗口的apply操作(尤其是自定义lambda计算百分位)效率很低——因为它是逐窗口的纯Python循环,对大数据量来说速度会很慢,确实不是最优解。
你后来想到的候选峰筛选方法:非常靠谱!
你自己写的find_significant_peaks函数思路特别好,本质是先捞全候选峰,再基于局部突出度做二次筛选,这个方法比平滑数据快很多,而且能精准拿到你要的峰:
方法的核心逻辑
- 先找全候选峰:用很低的
prominence阈值(最大值得1%)和小的distance(3),把所有可能的峰都先找出来,确保不会漏掉真正的大峰; - 局部突出度双筛选:
- 先检查每个峰是不是局部范围内(比如前后10个候选峰)的突出度最大值,把那些被周围更高突出度峰掩盖的小峰直接去掉;
- 再检查这个峰和相邻候选峰的突出度差异,只有当差异超过自身突出度的10%时才保留,过滤掉那些和旁边峰“差不多高”的伪峰;
- 最后从筛选后的峰里取前7个突出度最高的,就是你要的结果。
这个方法的效率比平滑数据高太多:find_peaks本身是Scipy优化过的C实现,后续的筛选都是用Numpy的向量/局部操作,比滚动窗口的apply快N倍。
给你的小优化建议
不过这个方法还有可以微调的地方,能让它更快更省内存:
- 缩小局部对比范围:你设置的
compare_num=10可以根据数据的实际峰间隔调整,比如你之前用的distance=48,如果平均每48个点才会有一个真正的峰,那compare_num=5左右就足够,不用到10,能减少计算量; - 避免全量计算突出度差异矩阵:你原来用
np.subtract.outer计算所有峰之间的突出度差异,会浪费不少内存(尤其是候选峰多的时候),可以改成在循环里只计算当前峰和局部窗口内峰的差异,不用提前算全量矩阵:
修改后的核心筛选逻辑如下:
def find_significant_peaks(x, prominence_diff_ratio=0.1, initial_distance=3): # Step 1: 先找所有候选峰 peaks, properties = find_peaks( x, distance=initial_distance, prominence=np.max(x) * 0.01 ) if len(peaks) == 0: return peaks, properties prominences = properties["prominences"] valid_peaks_mask = np.ones(len(peaks), dtype=bool) compare_num = 10 # 可以根据实际数据调整 for i in range(len(peaks)): # 取当前峰前后compare_num个候选峰的范围 start_idx = max(0, i - compare_num) end_idx = min(len(peaks), i + 1 + compare_num) local_prominences = prominences[start_idx:end_idx] current_prominence = prominences[i] # 条件1:是不是局部范围内的突出度最大值 if current_prominence < np.max(local_prominences): valid_peaks_mask[i] = False continue # 条件2:和相邻峰的突出度差异是否达标 # 只计算当前峰和局部窗口内其他峰的差异 neighbor_diffs = np.abs(current_prominence - local_prominences) neighbor_diffs = neighbor_diffs[neighbor_diffs != 0] # 去掉和自己的差异 threshold = current_prominence * prominence_diff_ratio if np.any(neighbor_diffs <= threshold): valid_peaks_mask[i] = False # 过滤有效峰和对应的属性 valid_peaks = peaks[valid_peaks_mask] valid_properties = {key: properties[key][valid_peaks_mask] for key in properties} return valid_peaks, valid_properties
最终效果
用这个优化后的函数处理数据,再取前7个突出度最高的峰,就能拿到你肉眼可见的那些正确峰值,而且速度比平滑数据的方法快很多,完美解决你的问题~
附上你之前的错误检测结果图:
备注:内容来源于stack exchange,提问作者wander

