It looks like the method used to do the fits is stochastic. From the pwlf source code, fit calls the function scipy.optimize.differential_evolution. The Scipy docs for this function describe it as a stochastic method to determine a global minimum of a function.
It appears that with the default settings used by pwlf, its not quite converged or at least its approaching convergence from different directions each time. You maybe able to edit the keywords so that the optimization will do a more stringent search for the true minimum. For example, atol sets the absolute tolerance for convergence. You also set seed to a consistent value to at least get the same result every time, even if it may not have found the "true" minimum. Just be aware that if you change one of the keywords, you need to explicitly write out all the others, as the **kwargs passed into fit appear to overwrite the defaults that would be passed to differential_evolution.
» pip install pwlf
You can use numpy.piecewise() to create the piecewise function and then use curve_fit(), Here is the code
from scipy import optimize
import matplotlib.pyplot as plt
import numpy as np
%matplotlib inline
x = np.array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ,11, 12, 13, 14, 15], dtype=float)
y = np.array([5, 7, 9, 11, 13, 15, 28.92, 42.81, 56.7, 70.59, 84.47, 98.36, 112.25, 126.14, 140.03])
def piecewise_linear(x, x0, y0, k1, k2):
return np.piecewise(x, [x < x0], [lambda x:k1*x + y0-k1*x0, lambda x:k2*x + y0-k2*x0])
p , e = optimize.curve_fit(piecewise_linear, x, y)
xd = np.linspace(0, 15, 100)
plt.plot(x, y, "o")
plt.plot(xd, piecewise_linear(xd, *p))
the output:

For an N parts fitting, please reference segments_fit.ipynb
You can use pwlf to perform continuous piecewise linear regression in Python. This library can be installed using pip.
There are two approaches in pwlf to perform your fit:
- You can fit for a specified number of line segments.
- You can specify the x locations where the continuous piecewise lines should terminate.
Let's go with approach 1 since it's easier, and will recognize the 'gradient change point' that you are interested in.
I notice two distinct regions when looking at the data. Thus it makes sense to find the best possible continuous piecewise line using two line segments. This is approach 1.
import numpy as np
import matplotlib.pyplot as plt
import pwlf
x = np.array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15])
y = np.array([5, 7, 9, 11, 13, 15, 28.92, 42.81, 56.7, 70.59,
84.47, 98.36, 112.25, 126.14, 140.03])
my_pwlf = pwlf.PiecewiseLinFit(x, y)
breaks = my_pwlf.fit(2)
print(breaks)
[ 1. 5.99819559 15. ]
The first line segment runs from [1., 5.99819559], while the second line segment runs from [5.99819559, 15.]. Thus the gradient change point you asked for would be 5.99819559.
We can plot these results using the predict function.
x_hat = np.linspace(x.min(), x.max(), 100)
y_hat = my_pwlf.predict(x_hat)
plt.figure()
plt.plot(x, y, 'o')
plt.plot(x_hat, y_hat, '-')
plt.show()
