I want to fit a curve to my data:
x=[24,25,28,37,58,104,200,235,235]
y=[340,350,370,400,430,460,490,520,550]
xerr=[1.1,1,0.8,1.4,1.4,2.6,3.8,2,2]
def fit_fc(x, a, b, c):
return a*x**b+c
popt, pcov=curve_fit(fit_fc,x,y,maxfev=5000)
plt.plot(x,fit_fc(x,popt[0],popt[1],popt[2]))
plt.errorbar(x,y,xerr=xerr,fmt='-o')
but i want to put some constraints on the a,b and c. For example I want them to be in some range, lets say between 0 and 20. How can i achieve that? I'm new in Python, so any help would be appreciated.
You could use lmfit to constrain you parameters. For the following plot, I constrained your parameters a and b to the range [0,20] (which you mentioned in your post) and c to the range [0, 400]. The parameters you get are:
a: 19.9999991
b: 0.46769173
c: 274.074071
and the corresponding plot looks as follows:
As you can see, the model reproduces the data reasonable well and the parameters are in the given ranges.
Here is the code that reproduces the results with additional comments:
from lmfit import minimize, Parameters, Parameter, report_fit
import numpy as np
x=[24,25,28,37,58,104,200,235,235]
y=[340,350,370,400,430,460,490,520,550]
def fit_fc(params, x, data):
a = params['a'].value
b = params['b'].value
c = params['c'].value
model = np.power(x,b)*a + c
return model - data #that's what you want to minimize
# create a set of Parameters
#'value' is the initial condition
#'min' and 'max' define your boundaries
params = Parameters()
params.add('a', value= 2, min=0, max=20)
params.add('b', value= 0.5, min=0, max=20)
params.add('c', value= 300.0, min=0, max=400)
# do fit, here with leastsq model
result = minimize(fit_fc, params, args=(x, y))
# calculate final result
final = y + result.residual
# write error report
report_fit(params)
#plot results
try:
import pylab
pylab.plot(x, y, 'k+')
pylab.plot(x, final, 'r')
pylab.show()
except:
pass
If you constrain all of your parameters to the range [0,20], the plot looks rather bad:
It depends on what you want to have happen if the variables are out of range. You can use a simple if statement (in this case the program exit()s):
x = 21
if (x not in range(0, 20)):
print("var x is out of range")
exit()
Another way is to assert that the variable must be in the range. In this case, it's wrapped in a try/except block that handles the problem gracefully, and also exit()s like above:
try:
assert(x in range(0, 20))
except AssertionError:
print("variable x is out of range")
exit()
Scipy uses unconstrained least squares in order to fit curve parameters, so it won't be that straightforward: https://github.com/scipy/scipy/blob/v0.16.0/scipy/optimize/minpack.py#L454
What you'd probably like to do is called constrained (non-linear?, giving what you're trying to fit) least squares problem. For instance, take a look at those discussions:
Constrained least-squares estimation in Python ( leastsq_bounds: https://gist.github.com/denis-bz/65da931bdbf92c49e4d0 )
scipy.optimize.leastsq with bound constraints
Related
I have an array of times
times=[58418 58422 58424 58426 58428 58430 58540 58542 58650 58654 58656 58658
58660 58662 58664 58666 58668 58670 58672 58674 58768 58770 58772 58774
58776 58778 58780 58782 58784 58786 58788 58790 58792 58794 58883 58884
58886 58888 58890 58890 58892 58894 58896 58898 58904]
and of correspondent observed quantities
y_obs =[ 0.00393986 0.00522288 0.00820794 0.01102782 0.00411525 0.00297762
0.00463183 0.00602662 0.0114886 0.00176694 0.01241464 0.01316199
0.01108201 0.01056611 0.0107585 0.00723887 0.0082614 0.01239229
0.00148118 0.00407329 0.00626722 0.01026926 0.01408419 0.02638901
0.02284189 0.02142943 0.02274845 0.01315814 0.01155898 0.00985705
0.00476936 0.00130343 0.00350376 0.00463576 -0.00610933 0.00286234
0.00845177 0.00849791 0.0151215 0.0151215 0.00967625 0.00802465
0.00291534 0.00819779 0.00366089]
and relative errors:
y_obs_err = [6.12189334e-05 6.07487598e-05 4.66365096e-05 4.48781264e-05
5.55250430e-05 6.18699105e-05 6.35339947e-05 6.21108524e-05
5.55636135e-05 7.66087180e-05 4.34256323e-05 3.61131000e-05
3.30783270e-05 2.41312040e-05 2.85080015e-05 2.96644612e-05
4.58662869e-05 5.19419065e-05 6.00479888e-05 6.62586953e-05
3.64830945e-05 2.58120956e-05 1.83249104e-05 1.59433858e-05
1.33375408e-05 1.29714326e-05 1.26025166e-05 1.47293107e-05
2.17933175e-05 2.21611713e-05 2.42946630e-05 3.61296843e-05
4.23009806e-05 7.23405476e-05 5.59390368e-05 4.68144974e-05
3.44773949e-05 2.32907036e-05 2.23491451e-05 2.23491451e-05
2.92956472e-05 3.28665479e-05 4.41214301e-05 4.88142073e-05
7.19116984e-05]
I define a function that calculates the value of y as a function of the times,
a number of parameters, and another independent variable
p= [ 2.82890497 3.75014266 5.89347542 7.91821558 2.95484056 2.13799544
3.32575733 4.32724456 8.2490644 1.26870083 8.91397925 9.45059128
7.95712563 7.58669608 7.72483557 5.19766853 5.93186433 8.89793105
1.06351782 2.92471065 4.49999613 7.37354766 10.11275281 18.94787684
16.40097363 15.38679306 16.33387783 9.44782842 8.29959664 7.07757293
3.42450524 0.93588962 2.515773 3.32857547 7.180216 2.05522399
6.06855409 6.1016838 10.8575614 10.8575614 6.94775991 5.76187014
2.09327787 5.88619335 2.62859611]
Here I define the function to calculate the y variable:
import numpy as np
import matplotlib.pyplot as plt
from lmfit import Model
import scipy.integrate as it
import scipy.constants as scc
def new_f_function(t, sum, f0, a, b,c, T0):
n= 2 * np.pi * sum
obs_f = f0 + it.cumtrapz(-a * p**c + b, t-T0, initial=0)
new_f = obs_f*(1+sum/scc.c)
return new_f
I create a model, and initialize my parameters with a first guess:
# Create Model
model = Model(new_f_function, independent_vars=['t'])
# Initialize Parameter
params = model.make_params()
params['sum'].value = 1.483
params['sum'].min = 1.47
params['sum'].max = 1.50
.... # and so on for the others
then I fit the model
`result = model.fit(y, params, weights=(1./y_err)), t=times, scale_covar=False)`
to obtain the best-fit parameters result.best_fit.
Finally I can plot the the best fit with
fig, (ax1, ax2) = plt.subplots(nrows=2, ncols=1, figsize=(3, 6), sharex='all')
ax2.plot(times, result.best_fit, label='best fit')
My question is: how can I plot/define the function at points where the variable p is not defined (e.g. I have no data)?
I guess the question is similar to that here: Scipy curve_fit: how to plot the fitted curve beyond the data points?
but in a case where the function has two independent variables and, although I can define an extra data range for the x-axis variable, that is time, I can not do the same for the observed variable p.
in a case where the function has two independent variables and, although I can define an extra data range for the x-axis variable, that is time, I can not do the same for the observed variable p.
If you have a function f(x, y) = z you have two independent input variables x and y and one output variable (this is your observed variable, if I'm right).
You can plot this by creating a 2d array (as fine as you want, independant of your times from before fitting), passing each single point to the function and plot all the calcalated z's. This cannot be plotted in a normal x-y plot obviously. One way is a 3d plot or a 2d color map plot.
Not sure if I got you right.
Your model object stores the function that you fit, and the best parameters for that fit are stored in the result.best_values property.
To to get that functions output for some set of new values, you can use the following code:
new_times_to_evaluate = np.array([56789, 54321]) # Or whatever values you'd like
new_predictions = model.eval( # evaluate your model
**result.best_values, # unpack the dictionary of best parameters to use for evaluation
t=new_times_to_evaluate # evaluate on these new times
)
ax2.plot(new_times_to_evaluate, new_predictions, label='my new values')
I think that you asking to evaluate the best-fit model at points other than those you used for the fit. FWIW, this is a fine thing to need to do, either to interpolate or extrapolate the model outside of the fit range.
To do that, just do:
result = model.fit(y, params, weights=(1./y_err)), t=times,
scale_covar=False)
predicted = result.eval(t=new_times)
Just read that as "evaluate the result at new times".
I am trying to fit some experimental data to a nonlinear function with one parameter that includes an arcus cosine function which therefore is limited in its area of definition from -1 to 1. I use scipy's curve_fit to find the parameter of the function, but it returns the following error:
RuntimeError: Optimal parameters not found: Number of calls to function has reached maxfev = 400.
The function I want to fit is this one:
def fitfunc(x, a):
y = np.rad2deg(np.arccos(x*np.cos(np.deg2rad(a))))
return y
For the fitting, I provid a numpy array for x and y respectively which contain values in degree (which is why the function contains conversion to and from radians).
param, param_cov = curve_fit(fitfunc, xs, ys)
When I use other fit functions like for example a polynomial, the curve_fit returns some values, the error mentioned above only occurs when I use this function which includes an arcus cosine.
I suspect that it cannot fit the data points because depending on the parameter of the arcus cosine function, some data points do not lie inside the area of definition of the arcus cosine. I have tried raising the number iterations (maxfev) but without success.
Sample data:
ys = np.array([113.46125, 129.4225, 140.88125, 145.80375, 145.4425,
146.97125, 97.8025, 112.91125, 114.4325, 119.16125,
130.13875, 134.63125, 129.4375, 141.99, 139.86,
138.77875, 137.91875, 140.71375])
xs = np.array([2.786427013, 3.325624466, 3.473013087, 3.598247534, 4.304280248,
4.958273121, 2.679526725, 2.409388637, 2.606306639, 3.661558062,
4.569923009, 4.836843789, 3.377013596, 3.664550526, 4.335401233,
3.064199519, 3.97155254, 4.100567011])
As HS-nebula mentioned in his comments, you need to define an initial value a0 of a as a start guess for the curve-fitting. Moreover, you need to be careful when choosing a0 as your np.arcos() is only defined in [-1,1] and choosing the wrong a0 results in error.
import numpy as np
from scipy.optimize import curve_fit
ys = np.array([113.46125, 129.4225, 140.88125, 145.80375, 145.4425, 146.97125,
97.8025, 112.91125, 114.4325, 119.16125, 130.13875, 134.63125,
129.4375, 141.99, 139.86, 138.77875, 137.91875, 140.71375])
xs = np.array([2.786427013, 3.325624466, 3.473013087, 3.598247534, 4.304280248, 4.958273121,
2.679526725, 2.409388637, 2.606306639, 3.661558062, 4.569923009, 4.836843789,
3.377013596, 3.664550526, 4.335401233, 3.064199519, 3.97155254, 4.100567011])
def fit_func(x, a):
a_in_rad = np.deg2rad(a)
cos_a_in_rad = np.cos(a_in_rad)
arcos_xa_product = np.arccos( x * cos_a_in_rad )
return np.rad2deg(arcos_xa_product)
a0 = 80
param, param_cov = curve_fit(fit_func, xs, ys, a0, bounds = (0, 360))
print('Using curve we retrieve a value of a = ', param[0])
Output:
Using curve we retrieve a value of a = 100.05275506147824
However if you choose a0=60, you get the following error:
ValueError: Residuals are not finite in the initial point.
To be able to use the data with all possible values of a, a normalization as HS-nebula suggested is good idea.
I have a set of data made up of (2-dimensional) observations of multiple objects. The observations can be described by a general function plus an offset that is unique to each object. I want to use curve_fit to simultaneously recover the general function and the offsets for each object (with associated errors). I do not know in advance how many objects the data-set will be made up of, only that there are likely to be multiple observations of each.
So a generalised data set of 7 observations might look like this:
[[x[0], y1[0], y2[0], lab='A'],
[x[1], y1[1], y2[1], lab='B'],
[x[2], y1[2], y2[2], lab='A'],
[x[3], y1[3], y2[3], lab='A'],
[x[4], y1[4], y2[4], lab='B'],
[x[5], y1[5], y2[5], lab='C'],
[x[6], y1[6], y2[6], lab='A']]
I could do the task by passing the parameters of the general function (say g = [g0, g1, g2]) and the object offsets offsets = n x [o1, o2] to fit_func and then using an object label to decide which of the n offsets needs to be added to the general function, except that I can't figure out how to pass the label.
def fit_func(x, g, offsets, lab):
y1 = g[0] * cos(2*(x - g[1])) + offsets['lab',0] + g[2]
y2 = g[0] * sin(2*(x - g[1])) + offsets['lab',1] + g[2]
return [y1, y2]
The problem is that lab is not a float to be fit, so I can't figure out how to pass it. From reading some other threads I believe I will need a wrapper function, but I can't figure out what form it should take, and then how to call it in such a way that I can specify sigma and p0.
Can anyone point me in the right direction?
Edit: I managed to produce a function that I thought would work. It used a global parameter call to choose options within the function call. So, for example I interleaved the y1 and y2 arrays, and had the function call the second equation every second run with a global getEven() and setEven(bool) call. However curve_fit really didn't like that. The fit values were nonsensical.
At the moment I am fitting the equation for y1 and the equation for y2 separately and taking the rms to determine g0 and g1 (this also gives me offsets['A',0] and offsets['A',1] respectively. I could just do this multiple times with each different object in the set, but I can't fit the g2 parameter this way, since in any given call to the y1 or y2 function it is degenerate with the corresponding offset.
Here is example code that fits two different equations with a shared parameter using 'A' or 'B' decoding. It appears to work as you need for decoding the lab type, but I personally have never done this before and while it appears to function per your post the "text-to-float" conversion inside the function seems klunky to me. But it works.
import numpy
import matplotlib
import matplotlib.pyplot as plt
from scipy.optimize import curve_fit
# single array with all "X" data to pass around
num = numpy.array([1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0])
ids = numpy.array(['A', 'A', 'A', 'A', 'B', 'B', 'B', 'B'])
xdata = numpy.array([num, ids]) # combine data, numpy auto-converts to 'text' type
# ydata is numeric single array
ydata = [9.0,8.0,7.0,6.0,4.0,3.0,2.0,1.0]
def fitFunction(data, commonParameter, pA, pB):
numericDataAsText = data[0]
textData = data[1]
returnArray = []
for i in range(len(textData)):
x = float(numericDataAsText[i])
if textData[i] == 'A':
val = commonParameter + x * pA
elif textData[i] == 'B':
val = commonParameter + x * pB
else:
raise(Exception('Error: must use A or B'))
returnArray.append(val)
return returnArray
initialParameters = [1.0, 1.0, 1.0]
# curve fit the equations individually to their respective data
params, pcov = curve_fit(fitFunction, xdata, ydata, initialParameters)
# values for display of fitted function
commonParameter, pA, pB = params
# for plotting the fitting results
y_fit = fitFunction(xdata, commonParameter, pA, pB)
plt.plot(xdata[0], ydata, 'D') # plot the raw data as a scatterplot
plt.plot(xdata[0][:4], y_fit[:4])
plt.plot(xdata[0][4:], y_fit[4:])
plt.show()
print('fittedparameters:', params)
It would be helpful to show a more complete example of what you are trying, including the call to scipy.optimize.curve_fit. But, if I understand the question correctly, you want to have an argument for your model function that is not treated as a variable in the fit. I believe that curve_fit cannot do this, and treats all arguments after the first as variables.
In fact, I think that your model function will not work for curve_fit because you expect g to be a sequence of values. With curve_fit, each argument after the first will get a single float value. So you probably want something like
def func(x, g0, g1, g2, offsets):
y1 = g0 * cos(2*(x - g1)) + offsets['lab', 0] + g2
...
Anyway, I have two suggestions to work around this limitation of curve_fit:
First, you could overload x. Now, curve_fit will internally apply numpy.asarray() to the x you pass in, but it will otherwise just pass it along to your model function. So, if you turn x into a list containing your real x and your lab, you should be able to unpack this in your model function, say like
xhack = [x, offsets]
def func(x, g0, g1, g2):
x, offsets = x
....
out = curve_fit(func, xhack, ...)
Personally, I think that's kind of ugly, but it might work.
Second, you could use lmfit (https://lmfit.github.io/lmfit-py/), which provides a higher level interface to curve fitting and fixes many of the shortcomings of curve_fit. For your question in particular, lmfit's Model class for curve fitting examines the model function more carefully to turn function arguments into fitting parameters. Specifically:
keyword arguments with non-numerical defaults will not be turned into fit parameters.
you can specify more than 1 "independent variable", and they do not have to be the first argument of the function.
That is, you could either write:
from lmfit import Model
def func(x, g0, g1, g2, offsets=None):
y1 = g0 * cos(2*(x - g1)) + offsets['lab', 0] + g2
mymodel = Model(func)
or explicitly tell Model what the independent variables are:
from lmfit import Model
def func(x, g0, g1, g2, offsets):
y1 = g0 * cos(2*(x - g1)) + offsets['lab', 0] + g2
mymodel = Model(func, independent_vars=['x', 'offsets'])
Either way, offsets can be any complex objects, and you would use this mymodel for curve fitting with:
# create parameter objects for this model, with initial values:
params = mymodel.make_params(g0=0, g1=0.5, g2=2.0)
# run the fit
result = mymodel.fit(ydata, params, x=x, offsets=offsets)
There are lots of other conveniences we added to lmfit (I am one of the developers) for building curve fitting models and working with parameters as high-level objects, but this might be enough to get you started.
I have a function Imaginary which describes a physics process and I want to fit this to a dataset x_interpolate, y_interpolate. The function is a form of a Lorentzian peak function and I have some initial values that are user given, except for f_peak (the peak location) which I find using a peak finding algorithm. All of the fit parameters, except for the offset, are expected to be positive and thus I have set bounds_I accordingly.
def Imaginary(freq, alpha, res, Ms, off):
numerator = (2*alpha*freq*res**2)
denominator = (4*(alpha*res*freq)**2) + (res**2 - freq**2)**2
Im = Ms*(numerator/denominator) + off
return Im
pI = np.array([alpha_init, f_peak, Ms_init, 0])
bounds_I = ([0,0,0,0, -np.inf], [np.inf,np.inf,np.inf, np.inf])
poptI, pcovI = curve_fit(Imaginary, x_interpolate, y_interpolate, pI, bounds=bounds_I)
In some situations I want to keep the parameter f_peak fixed during the fitting process. I tried an easy solution by changing bounds_I to:
bounds_I = ([0,f_peak+0.001,0,0, -np.inf], [np.inf,f_peak-0.001,np.inf, np.inf])
This is for many reasons not an optimal way of doing this so I was wondering if there is a more Pythonic way of doing this? Thank you for your help
If a parameter is fixed, it is not really a parameter, so it should be removed from the list of parameters. Define a model that has that parameter replaced by a fixed value, and fit that. Example below, simplified for brevity and to be self-contained:
x = np.arange(10)
y = np.sqrt(x)
def parabola(x, a, b, c):
return a*x**2 + b*x + c
fit1 = curve_fit(parabola, x, y) # [-0.02989396, 0.56204598, 0.25337086]
b_fixed = 0.5
fit2 = curve_fit(lambda x, a, c: parabola(x, a, b_fixed, c), x, y)
The second call to fit returns [-0.02350478, 0.35048631], which are the optimal values of a and c. The value of b was fixed at 0.5.
Of course, the parameter should be removed from the initial vector pI and the bounds as well.
You might find lmfit (https://lmfit.github.io/lmfit-py/) helpful. This library adds a higher-level interface to the scipy optimization routines, aiming for a more Pythonic approach to optimization and curve fitting. For example, it uses Parameter objects to allow setting bounds and fixing parameters without having to modify the objective or model function. For curve-fitting, it defines high level Model functions that can be used.
For you example, you could use your Imaginary function as you've written it with
from lmfit import Model
lmodel = Model(Imaginary)
and then create Parameters (lmfit will name the Parameter objects according to your function signature), providing initial values:
params = lmodel.make_params(alpha=alpha_init, res=f_peak, Ms=Ms_init, off=0)
By default all Parameters are unbound and will vary in the fit, but you can modify these attributes (without rewriting the model function):
params['alpha'].min = 0
params['res'].min = 0
params['Ms'].min = 0
You can set one (or more) of the parameters to not vary in the fit as with:
params['res'].vary = False
To be clear: this does not require altering the model function, making it much easier to change with is fixed, what bounds might be imposed, and so forth.
You would then perform the fit with the model and these parameters:
result = lmodel.fit(y_interpolate, params, freq=x_interpolate)
you can get a report of fit statistics, best-fit values and uncertainties for parameters with
print(result.fit_report())
The best fit Parameters will be held in result.params.
FWIW, lmfit also has builtin Models for many common forms, including Lorentzian and a Constant offset. So, you could construct this model as
from lmfit.models import LorentzianModel, ConstantModel
mymodel = LorentzianModel(prefix='l_') + ConstantModel()
params = mymodel.make_params()
which will have Parameters named l_amplitude, l_center, l_sigma, and c (where c is the constant) and the model will use the name x for the independent variable (your freq). This approach can become very convenient when you may want to change the functional form of the peaks or background, or when fitting multiple peaks to a spectrum.
I was able to solve this issue regarding arbitrary number of parameters and arbitrary positioning of the fixed parameters:
def d_fit(x, y, param, boundMi, boundMx, listparam):
Sparam, SboundMi, SboundMx = asarray([]), asarray([]), asarray([])
Nparam, NboundMi, NboundMx = asarray([]), asarray([]), asarray([])
for i in range(len(param)):
if(listparam[i] == 1):
Sparam = append(Sparam,asarray(param[i]))
SboundMi = append(SboundMi,asarray(boundMi[i]))
SboundMx = append(SboundMx,asarray(boundMx[i]))
else:
Nparam = append(Nparam,asarray(param[i]))
def funF(x, Sparam):
j = 0
for i in range(len(param)):
if(listparam[i] == 1):
param[i] = Sparam[i-j]
else:
param[i] = Nparam[j]
j = j + 1
return fun(x, param)
return curve_fit(lambda x, *Sparam: funF(x, Sparam), x, y, p0 = Sparam, bounds = (SboundMi,SboundMx))
In this case:
param = [a,b,c,...] # parameters array (any size)
boundMi = [min_a, min_b, min_c,...] # minimum allowable value of each parameter
boundMx = [max_a, max_b, max_c,...] # maximum allowable value of each parameter
listparam = [0,1,1,0,...] # 1 = fit and 0 = fix the corresponding parameter in the fit routine
and the root function is define as
def fun(x, param):
a,b,c,d.... = param
return a*b/c... # any function of the params a,b,c,d...
This way, you can change the root function and the number of parameters without changing the fit routine.
And, at any time, you can fix or let fit any parameter by changing "listparam".
Use like this:
popt, pcov = d_fit(x, y, param, boundMi, boundMx, listparam)
"popt" and "pcov" are 1D arrays of the size of the number of "1" in "listparam" bringing the results of the fitted parameters (best value and err matrix)
"param" will ramain an 1D array of the same size of the original (input) "param", HOWEVER IT WILL BE UPDATED AUTOMATICALLY TO THE FITTED VALUES (same as "popt") for the fitted values, keeping the fixed values according to "listparam"
Hope can be usefull!
Obs1: x = 1D-array independent values and y = 1D-array dependent values
Obs2: This is my first post. Please let me know if I can improove it!
How would i fit a straight line and a quadratic to the data set below using the leastsq function from scipy.optimize? I know how to use polyfit to do it. But i need to use leastsq function.
Here are the x and y data sets:
x: 1.0,2.5,3.5,4.0,1.1,1.8,2.2,3.7
y: 6.008,15.722,27.130,33.772,5.257,9.549,11.098,28.828
Can someone help me out please?
The leastsq() method finds the set of parameters that minimize the error function ( difference between yExperimental and yFit).
I used a tuple to pass the parameters and lambda functions for the linear and quadratic fits.
leastsq starts from a first guess ( initial Tuple of parameters) and tries to minimize the error function. At the end, if leastsq succeeds, it returns the list of parameters that best fit the data. ( I printed to see it).
I hope it works
best regards
from scipy.optimize import leastsq
import numpy as np
import matplotlib.pyplot as plt
def main():
# data provided
x=np.array([1.0,2.5,3.5,4.0,1.1,1.8,2.2,3.7])
y=np.array([6.008,15.722,27.130,33.772,5.257,9.549,11.098,28.828])
# here, create lambda functions for Line, Quadratic fit
# tpl is a tuple that contains the parameters of the fit
funcLine=lambda tpl,x : tpl[0]*x+tpl[1]
funcQuad=lambda tpl,x : tpl[0]*x**2+tpl[1]*x+tpl[2]
# func is going to be a placeholder for funcLine,funcQuad or whatever
# function we would like to fit
func=funcLine
# ErrorFunc is the diference between the func and the y "experimental" data
ErrorFunc=lambda tpl,x,y: func(tpl,x)-y
#tplInitial contains the "first guess" of the parameters
tplInitial1=(1.0,2.0)
# leastsq finds the set of parameters in the tuple tpl that minimizes
# ErrorFunc=yfit-yExperimental
tplFinal1,success=leastsq(ErrorFunc,tplInitial1[:],args=(x,y))
print " linear fit ",tplFinal1
xx1=np.linspace(x.min(),x.max(),50)
yy1=func(tplFinal1,xx1)
#------------------------------------------------
# now the quadratic fit
#-------------------------------------------------
func=funcQuad
tplInitial2=(1.0,2.0,3.0)
tplFinal2,success=leastsq(ErrorFunc,tplInitial2[:],args=(x,y))
print "quadratic fit" ,tplFinal2
xx2=xx1
yy2=func(tplFinal2,xx2)
plt.plot(xx1,yy1,'r-',x,y,'bo',xx2,yy2,'g-')
plt.show()
if __name__=="__main__":
main()
from scipy.optimize import leastsq
import scipy as sc
import numpy as np
import matplotlib.pyplot as plt
with optimize.curve_fit the code is simpler, there is no need to define the residual(error) function.
fig, ax = plt.subplots ()
# data
x=np.array([1.0,2.5,3.5,4.0,1.1,1.8,2.2,3.7])
y=np.array([6.008,15.722,27.130,33.772,5.257,9.549,11.098,28.828])
# modeling functions
def funcLine(x, a,b):
return a*x+b
def funcQuad(x, a, b, c):
return a*x**2+b*x+c
# optimize constants for the linear function
constantsLine, _ = sc.optimize.curve_fit (funcLine, x, y)
X=np.linspace(x.min(),x.max(),50)
Y1=funcLine(X, *constantsLine)
# optimize constants for the quadratic function
constantsQuad, _ = sc.optimize.curve_fit (funcQuad, x, y)
Y2=funcQuad(X,*constantsQuad)
plt.plot(X,Y1,'r-',label='linear approximation')
plt.plot(x,y,'bo',label='data points')
plt.plot(X,Y2,'g-', label='quadratic approximation')
matplotlib.pylab.legend ()
ax.set_title("Nonlinear Least Square Problems", fontsize=18)
plt.show()
Here's a super simple example. Picture a paraboloid, so like a bowl with sides growing like a parabola. If we put the bottom at coordinates (x, y) = (a, b) and then minimize the height of the paraboloid over all values of x and y - we would expect the minimum to be x=a and y=b. Here's code that would do this.
import random
from scipy.optimize import least_squares
a, b = random.randint(1, 1000), random.randint(1, 1000)
print("Expect", [a, b])
def f(args):
x, y = args
return (x-a)**2 + (y-b)**2
x0 = [-1, -3]
result = least_squares(fun=f, x0=x0)
print(result.x)