lincurve requires multiple linear regressions (up to 16-1=15) that have the constraint that all regression lines meet at their ends. For this minimization problem I used the python
piecewise linear fitting library
.
Broken down, you give it the x,y data, and in this case tell it how many breakpoints you want (for lincurve we can go up to 15), and if you have enough datapoints you usually get a globally minimum solution.
Here's the python code:
Code:
from absl import app
from absl import flags
import pandas as pd
from sklearn.linear_model import LinearRegression
import plotly.express as px
import pwlf
_MIN = -74.0
_MAX = 2450.0
_REGRESSION_SECTIONS = 5
FLAGS = flags.FLAGS
flags.DEFINE_string('data_file', '', 'Data file')
flags.DEFINE_string('data_file_comp', None, 'Data file to compare to')
def main(argv):
if FLAGS.data_file is None:
print('ERROR')
df = pd.read_csv(FLAGS.data_file)
# Calculate the total linear regression for plotting
model = LinearRegression()
model.fit(df[['x']], df[['y']])
df['y'] = df['y'] - model.intercept_ - df['x'] * model.coef_[0][0]
df['x'] = df['x'] - df['x'][0]
# initialize piecewise linear fit with your x and y data
my_pwlf = pwlf.PiecewiseLinFit(df['x'].to_numpy(), df['y'].to_numpy())
# Fit the data for line segments
regression = my_pwlf.fit(_REGRESSION_SECTIONS)
slope = my_pwlf.calc_slopes()
intercept = my_pwlf.intercepts
# Data file to compare to
if FLAGS.data_file_comp is not None:
df_comp = pd.read_csv(FLAGS.data_file_comp)
df_comp['y'] = df_comp['y'] - model.intercept_ - df_comp['x'] * model.coef_[0][0]
df_comp['x'] = df_comp['x'] - df_comp['x'][0]
df_comp = df_comp.rename(columns={'y': 'y_comp'})
df = pd.concat([df, df_comp])
df = pd.melt(df, id_vars=['x'], value_vars=['y', 'y_comp'])
else:
df = pd.melt(df, id_vars=['x'], value_vars=['y'])
# Plot
fig = px.line(df, x='x', y='value', color='variable')
regression_i = 0
while regression_i < _REGRESSION_SECTIONS:
fig.add_shape(
type="line",
x0=regression[regression_i],
y0=regression[regression_i] * slope[regression_i] + intercept[regression_i],
x1=regression[regression_i + 1],
y1=regression[regression_i + 1] * slope[regression_i] + intercept[regression_i],
line=dict(width=2, dash="solid", color="Orange",),
)
print(f"setp x_straight.x-val-{regression_i:02d} {regression[regression_i]}")
print(f"setp x_straight.y-val-{regression_i:02d} {regression[regression_i] * slope[regression_i] + intercept[regression_i]}")
regression_i = regression_i + 1
print(f"setp x_straight.x-val-{regression_i:02d} {regression[regression_i]}")
print(f"setp x_straight.y-val-{regression_i:02d} {regression[regression_i] * slope[regression_i - 1] + intercept[regression_i - 1]}")
fig.show()
if __name__ == '__main__':
app.run(main)
Just pip install the dependencies at the top. All my python is for 3, I haven't tested it on 2.
The datafile is generated from an ngc probing routine I wrote, and needs to look like a CSV (cut short for visibility) for the program above to read it correctly:
Code:
x,y
5.000000,1161.072689
10.000000,1161.074890
15.000000,1161.074590
20.000000,1161.074291
25.000000,1161.065659
30.000000,1161.072027