如何对 Python 中具有正斜率和负斜率的直线应用分段线性拟合?

How to apply piecewise linear fit for a line with both positive and negative slopes in Python?

我在代码中提供的数据具有负斜率和正斜率,如图所示:

使用此 post Fit a curve for data made up of two distinct regimes 中应用的代码,我创建了此代码。它适用于相同的斜率,无论是正斜率还是负斜率,但当一个正斜率和另一个负斜率时,它无法正确拟合线条。

from scipy import optimize
from scipy import optimize, interpolate
from scipy.optimize import curve_fit
import matplotlib.pyplot as plt
import numpy as np


x = np.array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20])
y = np.array([4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 22, 20, 18, 16, 14, 12, 10, 8, 6, 4])


def two_lines(x, a, b, c, d):
    one = a*x + b
    two = c*x + d
    return np.maximum(one, two)


'''Compute approximate slope and intercept of the two lines'''
poly_low = np.polyfit(x[0:int(0.5*(len(x) + 1))], y[0:int(0.5*(len(x) + 1))], deg=1)
poly_high = np.polyfit(x[int(0.5*(len(x) + 1)):len(x)], y[int(0.5*(len(x) + 1)):len(x)], deg=1)

# This part of the code credit goes to askewchan
pw0 = (poly_low[0], poly_low[1], poly_high[0], poly_high[1]) # a guess for slope, intercept, slope, intercept
pw, cov = curve_fit(two_lines, x, y, pw0)
crossover = (pw[3] - pw[1]) / (pw[0] - pw[2])


figure = plt.figure(figsize=(5.15, 5.15))
figure.clf()
plot = plt.subplot(111)
plt.plot(x, y, 'o', x, two_lines(x, *pw), '-')
plot.set_ylabel('Y', labelpad = 6)
plot.set_xlabel('X', labelpad = 6)
plt.show()

输出

对于不同的坡度:

对于相同的负斜率(也适用于正斜率):

我有两个问题:

  1. 如何对 Python 中的此类情况应用分段线性拟合?
  2. 如何扩展到三个或更多的政权?

您可以将屏蔽区域用于分段函数:

def two_lines(x, a, b, c, d):
    out = np.empty_like(x)
    mask = x < 10
    out[mask] = a*x[mask] + b
    out[~mask] = c*x[~mask] + d
    return out

第一次测试有两个不同的正斜率:

x = np.array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
              17, 18, 19, 20])
y = np.array([4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 25, 26, 27, 28, 29,
    30, 31, 32, 33, 34])

具有正斜率和负斜率的第二次测试(来自您的示例的数据):

您所拥有的基本上适用于您所呈现的特定问题和数据,但更普遍地解决此问题是错误的方法。它不能轻易扩展到更广泛的情况,例如,多个段、线性段之间具有更复杂转换的数据、需要调整拟合的特定方面的情况等。主要原因是你处理一个以最一般和最困难的方式(高维、多参数拟合)解决简单的特定和高度受限的问题(拟合多个非重叠线段)。除其他问题外,这种泛化将使拟合更难落在参数 space 的正确区域。 这是一个关于简单的局部拟合的问题,您正试图用一个困难的通用全局解决方案来解决。我看到了吸引力,但它不太可能适用于玩具示例。

就是说,只要以正确的斜坡标志开始,您所拥有的就可以用于玩具示例。至少它从 (1,1,-1,1)(10,10,-10,10) 开始有效,但根据您对 two_lines 的定义,您还需要知道这些,所以我并不是真的假设您不知道的任何事情。此外,您需要定义 two_lines 以便它可以匹配(您的原始三角形具有向下三角形而不是向上三角形,这也可能是它适用于两条 "negative sloped" 线的原因 - 不是因为它们是负斜率但是因为它们 可以 与您的原始定义相匹配)。

from scipy import optimize
from scipy import optimize, interpolate
from scipy.optimize import curve_fit
import matplotlib.pyplot as plt
import numpy as np

x = np.array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20])
y = np.array([4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 22, 20, 18, 16, 14, 12, 10, 8, 6, 4])

def two_lines(x, a, b, c, d):
    one = a*x + b
    two = c*x + d
    return np.minimum(one, two)

pw, cov = curve_fit(two_lines, x, y, (10, 10, -10, 10))

figure = plt.figure(figsize=(5.15, 5.15))
figure.clf()
plot = plt.subplot(111)
plt.plot(x, y, 'o')
plt.plot(x, two_lines(x, *pw), '-')
plot.set_ylabel('Y', labelpad = 6)
plot.set_xlabel('X', labelpad = 6)
plt.show()

明显的替代方法是使用独立的分段线性拟合沿着曲线移动。这种方法简单、快速、灵活,可能更符合您的直觉。这种方法也很容易扩展到无限数量的段(而您的全局解决方案可能最多为两个),并且还允许线性部分的拟合不会被不完全线性的曲线部分弄乱(例如,圆角过渡)。