You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.10 13:35:21