Python曲线拐点检测问题:现有算法检测拐点过多求优化方案
曲线拐点检测优化问题
我需要绘制类似示例图的曲线拐点可视化图,现有一条相似曲线,用Python写了拐点检测代码,但检测出的拐点数量远超预期。附上相关代码、数据,求修改方案或其他实现思路。
现有检测代码
def find_inflection_points(df, n=1): raw = df['consumption'].to_numpy() infls = [] dx = 0 for i, x in enumerate(np.diff(raw, n)): if x >= dx and i > 0: infls.append(i*n) dx = x # plot results plt.plot(raw, label='Input Data') for i, infl in enumerate(infls, 1): plt.axvline(x=infl, color='k', label=f'Inflection Point {i}') plt.legend(bbox_to_anchor=(1.55, 1.0)) return infls
测试数据
raw = np.array([52.33, 50.154444444444444, 48.69222222222223, 46.49111111111111, 44.01444444444444, 43.30555555555556, 43.034444444444446, 40.62888888888889, 40.38111111111111, 39.07666666666667, 38.339999999999996, 36.41444444444445, 36.37888888888889, 36.17111111111111, 35.666666666666664, 33.827777777777776, 29.35222222222222, 28.60888888888889, 24.43, 22.078888888888887, 21.756666666666664, 20.345555555555556, 19.874444444444446, 19.763333333333335])
问题分析与优化方案
原代码核心问题
原代码逻辑错误:拐点的数学定义是二阶导数由正变负或负变正的点,对应一阶导数的极值点。但原代码通过比较一阶差分的大小变化来标记,会把所有一阶差分上升的点都判定为拐点,导致数量远超预期。
优化方案1:基于二阶差分的标准拐点检测
直接计算二阶差分(近似二阶导数),寻找符号变化的位置,完全符合拐点定义:
import numpy as np import matplotlib.pyplot as plt def find_inflection_points(raw): # 计算二阶差分(对应二阶导数近似值) second_diff = np.diff(raw, n=2) # 找到二阶差分符号变化的索引 sign_changes = np.where(np.diff(np.sign(second_diff)) != 0)[0] # 转换为原始数据的索引(二阶差分比原始数据少2个点,需+1) infl_points = sign_changes + 1 # 可视化 plt.plot(raw, label='Input Data') for idx, infl in enumerate(infl_points, 1): plt.axvline(x=infl, color='r', linestyle='--', label=f'Inflection Point {idx}') plt.legend(bbox_to_anchor=(1.05, 1), loc='upper left') plt.show() return infl_points # 测试调用 find_inflection_points(raw)
优化方案2:平滑数据后检测(应对小波动误检)
如果数据存在微小噪声导致误判,先做移动平均平滑再检测:
def smooth_data(raw, window_size=3): # 移动平均平滑 return np.convolve(raw, np.ones(window_size)/window_size, mode='valid') def find_inflection_points_smoothed(raw, window_size=3): smoothed = smooth_data(raw, window_size) # 对平滑后的数据计算二阶差分 second_diff = np.diff(smoothed, n=2) sign_changes = np.where(np.diff(np.sign(second_diff)) != 0)[0] # 转换为原始数据索引(平滑会损失前后各(window_size-1)//2个点) offset = (window_size - 1) // 2 infl_points = sign_changes + 1 + offset # 可视化 plt.plot(raw, label='Original Data', alpha=0.5) plt.plot(range(offset, len(raw)-offset), smoothed, label='Smoothed Data', color='orange') for idx, infl in enumerate(infl_points, 1): plt.axvline(x=infl, color='r', linestyle='--', label=f'Inflection Point {idx}') plt.legend(bbox_to_anchor=(1.05, 1), loc='upper left') plt.show() return infl_points # 测试调用 find_inflection_points_smoothed(raw, window_size=3)
优化方案3:基于一阶导数极值的检测
拐点对应一阶导数的极值点,所以可以先计算一阶差分,再找一阶差分的极值点:
def find_inflection_points_deriv(raw): first_diff = np.diff(raw) # 找一阶差分的极值点(通过二阶差分符号变化) second_diff_deriv = np.diff(first_diff) sign_changes = np.where(np.diff(np.sign(second_diff_deriv)) != 0)[0] infl_points = sign_changes + 1 plt.plot(raw, label='Input Data') plt.plot(first_diff, label='First Difference', color='g', alpha=0.7) for idx, infl in enumerate(infl_points, 1): plt.axvline(x=infl, color='r', linestyle='--', label=f'Inflection Point {idx}') plt.legend(bbox_to_anchor=(1.05, 1), loc='upper left') plt.show() return infl_points # 测试调用 find_inflection_points_deriv(raw)
内容的提问来源于stack exchange,提问作者Kosmylo
相关产品推荐
相关产品推荐

