3

I have a problem similar to this previous StackOverflow question. I have a dataset that I would like to fit several piece-wise functions to, then plot the results.

The data is plotted in red below.

To give some context, the y-values represent how many milliseconds a motor takes in order to turn x degrees. I have uploaded the raw values to this Pastebin.

I would now like to fit three functions in a piece-wise manner:

  • A polynomial fit for the start of the data, where the motor is accelerating to max speed.
  • A linear fit for when max speed is reached.
  • A polynomial fit for then the motor is turned off, and decelerates.

So far I have tried to perform a piece-wise fit of two linear functions, using the code shown below. Given what the data look like, I was expecting to see one slope following the data from origin to about ms=550, then from there a second line running parallel to the x-axis.

However, this is not what I get:

Data with fit

Before I try to perform a piece-wise fit using three functions, I would first like to understand why I get this plot and not what I was expecting.

So my questions are:

  1. Can anyone explain how to correct my code to make it fit two linear functions?
  2. How can I expand my code to plot a piece-wise fit using three functions?

The code used to create the plot above is as follows:

from pandas import *
import matplotlib.pyplot as plt
import numpy as np
from scipy import optimize

#Getting data using Pandas
df = read_csv("test_data.csv")
ms = df["ms"].values
degrees = df["Degrees"].values

#A piece wise function taken from the other stackoverflow
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])

#Setting linspace and making the fit
x_new = np.linspace(ms[0], ms[-1])

p , e = optimize.curve_fit(piecewise_linear, ms, degrees)


#Plotting data and fit

fig = plt.figure()
ax = fig.add_subplot(111)

ax.plot(x_new, piecewise_linear(x_new, *p), '.', df)
ax.set_ylim([0, 450])
ax.set_xlim([0, 800])
Community
  • 1
  • 1

1 Answers1

3

2. question: you need to redefine piecewise_linear, now it has three pieces, change them as you like (I just put an example of 2nd degree, 1st degree and 3rd degree polynomials).

#Piecewise function defining 2nd deg, 1st degree and 3rd degree exponentials
def piecewise_linear(x, x0, x1, y0, y1, k1, k2, k3, k4, k5, k6):
    return np.piecewise(x, [x < x0, x>= x0, x> x1], [lambda x:k1*x + k2*x**2, lambda x:k3*x + y0, lambda x: k4*x + k5*x**2 + k6*x**3 + y1])

1. question: apparently in order to use curve_fit() you need to convert to numpy arrays.

#Setting linspace and making the fit, make sure to make you data numpy arrays
x_new = np.linspace(ms[0], ms[-1], dtype=float)
m = np.array(ms, dtype=float)
deg = np.array(degrees, dtype=float)
guess = np.array( [100, 500, -30, 350, -0.1, 0.0051, 1, -0.01, -0.01, -0.01], dtype=float)
p , e = optimize.curve_fit(piecewise_linear, m, deg)
#Plotting data and fit
fig = plt.figure()
ax = fig.add_subplot(111)
ax.plot(x_new, piecewise_linear(x_new, *p), '-', df)
ax.set_ylim([0, 450])
ax.set_xlim([0, 800])

As a side note, I also added some educated guesses for your initial parameters, which gives you a better fit than letting python choose randomly.

Then do

ax.plot(x_new, piecewise_linear(x_new, *p), '-', ms[::20], degrees[::20], 'o')

enter image description here

lhcgeneva
  • 1,981
  • 2
  • 21
  • 29
  • 1
    Thanks, this is exactly what I was looking for. I noticed that I needed to add the guess in curve_fit() to get the flat end of the fit. Otherwise it works like a charm :) – Martin Kristjansen Dec 07 '15 at 08:23
  • 1
    No problem :), that's what I found as well, the 3rd degree somehow doesn't fit very well if left randomly. – lhcgeneva Dec 07 '15 at 09:52