Fit an arbitrary number of parameters when calling curve_fit

Closest I found to this question was here: Fitting only one parameter of a function with many parameters in python. I have a multi-parameter function that I want to be able to call with a different subset of parameters being optimised in different parts of the code (useful because for some datasets, I may be able to fix some parameters based on ancillary data). Simplified demonstration of the problem below.

from scipy.optimize import curve_fit
import numpy as np

def wrapper_func(**kwargs):
    a = kwargs['a'] if 'a' in kwargs else None
    b = kwargs['b'] if 'b' in kwargs else None
    c = kwargs['c'] if 'c' in kwargs else None
return lambda x, a, c: func(x, a, b, c)

def func(x, a, b, c):
    return a * x**2 + b * x + c

# Set parameters    
a = 0.3
b = 5
c = 17 

# Make some fake data
x_vals = np.arange(100)
y_vals = a * x_vals**2 + b * x_vals + c
noise = np.random.randn(100) * 20

# Get fit
popt, pcov = curve_fit(lambda x, a_, c_: func(x, a_, b, c_), 
                       x_vals, y_vals + noise)

# Get fit using separate function
alt_popt, alt_cov = curve_fit(wrapper_func(b=5), x_vals, y_vals + noise)

So this works, but I want to be able to pass any combination of parameters to be fixed. So here parameters a and c are optimised, and b is fixed, but if I want to fix a and optimise b and c (or any other combination), is there a way to do this neatly? I made a start with wrapper_func() above, but the same problem arises: there seems to be no way to vary which parameters are optimised, except by writing multiple lambdas (conditional on what fixed parameter values are passed). This gets ugly quickly because the equations I am working with have 4-6 parameters. I can make a version work using eval, but gather this is not recommended. As it stands I have been groping around trying to use *args with lambda, but haven't managed to get it to work. Any tips greatly appreciated!


Asked by: David378 | Posted: 30-11-2021






Answer 1

lmfit (https://lmfit.github.io/lmfit-py/) does exactly this. Instead of creating an array of floating point values for the parameters in the fit, one creates a Parameters object -- an ordered dictionary of Parameter objects that are used to parametrize the model for the data. Each Parameter can be fixed or varied in the fit, can have max/min bounds, or can be defined as a simple mathematical expression in terms of other Parameters in the fit.

That is, with lmfit (and its Model class that is especially useful for curve-fitting), one creates Parameters and can then decide which will be optimized and which will be held fixed.

As an example, here is a variation on the problem you pose:

import numpy as np
from lmfit import Model
import matplotlib.pylab as plt

# starting parameters
a, b, c = 0.3, 5, 17
x_vals = np.arange(100)
noise = np.random.normal(size=100, scale=0.25)
y_vals = a * x_vals**2 + b * x_vals + c + noise

def func(x, a, b, c):
    return a * x**2 + b * x + c

# create a Model from this function
model = Model(func)

# create parameters with initial values. Model will know to 
# turn function args `a`, `b`, and `c` into Parameters:
params = model.make_params(a=0.25, b=4, c=10)

# you can alter each parameter, for example, fix b or put bounds on a
params['b'].vary = False
params['b'].value = 5.3
params['a'].min = -1
params['a'].max =  1

# run fit
result = model.fit(y_vals, params, x=x_vals)

# print and plot results
print(result.fit_report())
result.plot(datafmt='--')
plt.show()

will print out:

[[Model]]
    Model(func)
[[Fit Statistics]]
    # function evals   = 12
    # data points      = 100
    # variables        = 2
    chi-square         = 475.843
    reduced chi-square = 4.856
    Akaike info crit   = 159.992
    Bayesian info crit = 165.202
[[Variables]]
    a:   0.29716481 +/- 7.46e-05 (0.03%) (init= 0.25)
    b:   5.3 (fixed)
    c:   11.4708897 +/- 0.329508 (2.87%) (init= 10)
[[Correlations]] (unreported correlations are <  0.100)
    C(a, c)                      = -0.744 

(You will find that b and c are highly and negatively correlated) and show a plot like enter image description here

Furthermore, the fit results including the parameters are held in result, so if you want to change what parameters are fixed, you can simply change the starting values (which have not been updated by the fit):

params['b'].vary = True
params['a'].value = 0.285
params['a'].vary = False

newresult = model.fit(y_vals, params, x=x_vals)

and then compare/contrast the two results.

Answered by: Arthur633 | Posted: 01-01-2022



Answer 2

Here my solution. I am not sure how to do it with curve_fit, but it works with leastsq. It has a wrapper function that takes the free and fixed parameters as well as a list of the free parameter positions. As leastsq calls the function with the free parameters first, hence, the wrapper has to rearrange the order.

from matplotlib import pyplot as plt
import numpy as np
from scipy.optimize import leastsq

def func(x,a,b,c,d,e):
    return a+b*x+c*x**2+d*x**3+e*x**4

#takes x, the 5 parameters and a list
# the first n parameters are free
# the list of length n gives there position, e.g. 2  parameters, 1st and 3rd order ->[1,3]
# the remaining parameters are in order, i.e. in this example it would be f(x,b,d,a,c,e)
def expand_parameters(*args):
    callArgs=args[1:6]
    freeList=args[-1]
    fixedList=range(5)
    for item in freeList:
        fixedList.remove(item)
    callList=[0,0,0,0,0]
    for val,pos in zip(callArgs, freeList+fixedList):
        callList[pos]=val
    return func(args[0],*callList)

def residuals(parameters,dataPoint,fixedParameterValues=None,freeParametersPosition=None):
    if fixedParameterValues is None:
        a,b,c,d,e = parameters
        dist = [y -func(x,a,b,c,d,e) for x,y in dataPoint] 
    else:
        assert len(fixedParameterValues)==5-len(freeParametersPosition)
        assert len(fixedParameterValues)>0
        assert len(fixedParameterValues)<5 # doesn't make sense to fix all
        extraIn=list(parameters)+list(fixedParameterValues)+[freeParametersPosition]
        dist = [y -expand_parameters(x,*extraIn) for x,y in dataPoint]
    return dist


if __name__=="__main__":
    xList=np.linspace(-1,3,15)
    fList=np.fromiter( (func(s,1.1,-.9,-.7,.5,.1) for s in xList), np.float)

    fig=plt.figure()
    ax=fig.add_subplot(1,1,1)

    dataTupel=zip(xList,fList)

    ###some test
    print residuals([1.1,-.9,-.7,.5,.1],dataTupel)
    print residuals([1.1,-.9,-.7,.5],dataTupel,fixedParameterValues=[.1],freeParametersPosition=[0,1,2,3])

    #exact fit
    bestFitValuesAll, ier = leastsq(residuals, [1,1,1,1,1],args=(dataTupel))
    print bestFitValuesAll

    ###Only a constant
    guess=[1]
    bestFitValuesConstOnly, ier = leastsq(residuals, guess,args=(dataTupel,[0,0,0,0],[0]))
    print bestFitValuesConstOnly
    fConstList=np.fromiter(( func(x,*np.append(bestFitValuesConstOnly,[0,0,0,0])) for x in xList),np.float)

    ###Only 2nd and 4th
    guess=[1,1]
    bestFitValues_1_3, ier = leastsq(residuals, guess,args=(dataTupel,[0,0,0],[2,4]))
    print bestFitValues_1_3
    f_1_3_List=np.fromiter(( expand_parameters(x, *(list(bestFitValues_1_3)+[0,0,0]+[[2,4]] ) )  for x in xList),np.float)


    ###Only 2nd and 4th with closer values
    guess=[1,1]
    bestFitValues_1_3_closer, ier = leastsq(residuals, guess,args=(dataTupel,[1.2,-.8,0],[2,4]))
    print bestFitValues_1_3_closer
    f_1_3_closer_List=np.fromiter(( expand_parameters(x, *(list(bestFitValues_1_3_closer)+[1.2,-.8,0]+[[2,4]] ) )  for x in xList),np.float)


    ax.plot(xList,fList,linestyle='',marker='o',label='orig')
    ax.plot(xList,fConstList,linestyle='',marker='o',label='0')
    ax.plot(xList,f_1_3_List,linestyle='',marker='o',label='1,3')
    ax.plot(xList,f_1_3_closer_List,linestyle='',marker='o',label='1,3 c')

    ax.legend(loc=0)

    plt.show()

Providing:

>>[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
>>[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
>>[ 1.1 -0.9 -0.7  0.5  0.1]
>>[ 2.64880466]
>>[-0.14065838  0.18305123]
>>[-0.31708629  0.2227272 ]

enter image description here

Answered by: Darcy519 | Posted: 01-01-2022



Similar questions

python - Django: Arbitrary number of unnamed urls.py parameters

I have a Django model with a large number of fields and 20000+ table rows. To facilitate human readable URLs and the ability to break down the large list into arbitrary sublists, I would like to have a URL that looks like this: /browse/&lt;name1&gt;/&lt;value1&gt;/&lt;name2&gt;/&lt;value2&gt;/ .... etc .... where 'name' maps to a model attribute and 'value' is the search criteria for that...


python - How do I call a function with arbitrary parameters from another function?

I'd like to know how I would go about doing something like this (in python 2): def myFunction(fn, params): return ("You called " + fn.__name__ + "(" + str(params) ")" + " and got " + str(fn(params))) Say I have a couple functions: def doSomething(s): # manipulation return s def doAnotherThing(someInt, someOtherInt, someString): # do something with ...


curve fitting - Creating a python lmfit Model with arbitrary number of parameters

Is there a way to construct a an lmfit Model based on a function with an arbitrary number of dependent variables? For example: from lmfit import Model def my_poly(x, *params): func = 0 for i in range(len(params)): func+= params[i]*z**i return func #note: below does not work my_model = Model(my_poly, independent_vars = ['x'], param_names = ['A','B','C']) Something similar to th...


How do Python methods handle arbitrary parameters?

I will use the range function as the main example in this question. I use range a lot to generate a list of numbers. However, I use it often with different parameters, specifically a different NUMBER of parameters. It makes me wonder how range easily handles different numbers of parameters and assigns them to their respective value (if they represent the highest value, or the ...


python - Allowing for an arbitrary number of parameters in a function?

This question already has answers here:


scipy - Python: Finding number of fitting parameters for arbitrary curve

Is there a way to return the number of arguments an arbitrary function has which is defined in some other file? I have tried using the Signature class in inspect as follows: from foo import func1, func2, func3 from inspect import signature from scipy.optimize import curve_fit func_list = [ func1, func2, func3 ] n, bins, patches = hist( array ) for f in func_list: sig = signature(f) args = sig.par...


python - How to write a wrapper to fix arbitrary parameters of the jacobian of a function

This is an extension of a previous stack-exchange question I posted. link Context: My goal is to fit data to a function f(t, *p) using the scipy.optimize.curve_fit function. I happen to know some parameters pfix = {p_j, ..., p_k} a...


python - How to create a function with arbitrary parameters based on an arbitrary string

My end goal is: I want to create a set of truth tables, where each truth table corresponds to an arbitrarily defined boolean expression, which is originally stored as a string (like: "(var_1 and not var_2) or var_3" ). The string could have any number of operators. This is easily achievable if I have a particular boolean expression in mind: def evaluator(var_1,var_2,var_3): return (var_1 and no...


python - How to allow any arbitrary query parameters using FastAPI and Swagger?

Note: This question is different from the one here, in that I need it to work with Swagger. Given a FastAPI GET endpoint, I want to allow any arbitrary set of URL parameters, while maintaining Swagger support. My use case is that I want to support a JSON API-like set of query parameters s...


Python function that counts negative integers using arbitrary number of parameters

Python newbie here, I need help with working with function with arbitrary number of parameters. I want to implement a function that can take an arbitrary number of parameters, and counts negative integers. I want to try the negative_counter function on the following list of numbers 4,-3,5,6,-7 See my attempt (not sure what I am doing wrong) def negative_counter(*args):...


python - Django: Arbitrary number of unnamed urls.py parameters

I have a Django model with a large number of fields and 20000+ table rows. To facilitate human readable URLs and the ability to break down the large list into arbitrary sublists, I would like to have a URL that looks like this: /browse/&lt;name1&gt;/&lt;value1&gt;/&lt;name2&gt;/&lt;value2&gt;/ .... etc .... where 'name' maps to a model attribute and 'value' is the search criteria for that...


python - variables as parameters in field options

I want to create a model, that will set editable=False on creation, and editable=True on editing item. I thought it should be something like this: home = models.ForeignKey(Team, editable=lambda self: True if self.id else False) But it doesn't work. Maybe something with overriding the init can help me, but i don't sure what can do the trick. I know i can check for self.id...


c# - How to analyse .exe parameters inside the program?

I have a program that can have a lot of parameters (we have over +30 differents options). Example: myProgram.exe -t alpha 1 -prod 1 2 -sleep 200 This is 3 Commands (from command pattern object at the end) that each contain some parameters. Inside the code we parse all command (start with -) and get a list of string (split all space) for the parameters. So in fact, we have : string-->Collection ...


python - Default parameters to actions with Django

Is there a way to have a default parameter passed to a action in the case where the regex didnt match anything using django? urlpatterns = patterns('',(r'^test/(?P&lt;name&gt;.*)?$','myview.displayName')) #myview.py def displayName(request,name): # write name to response or something I have tried setting the third parameter in the urlpatterns to a dictionary containing ' and giving...


python - Loop function parameters for sanity check

I have a Python function in which I am doing some sanitisation of the input parameters: def func(param1, param2, param3): param1 = param1 or '' param2 = param2 or '' param3 = param3 or '' This caters for the arguments being passed as None rather than empty strings. Is there an easier/more concise way to loop round the function parameters to apply such an expression to ...


python - How can I pass all the parameters to a decorator?

I tried to trace the execution of some methods using a decorator. Here is the decorator code: def trace(func): def ofunc(*args): func_name = func.__name__ xargs = args print "entering %s with args %s" % (func_name,xargs) ret_val = func(args) print "return value %s" % ret_val print "exiting %s" % (func_nam...


parameters - Python Newbie: Returning Multiple Int/String Results in Python

I have a function that has several outputs, all of which "native", i.e. integers and strings. For example, let's say I have a function that analyzes a string, and finds both the number of words and the average length of a word. In C/C++ I would use @ to pass 2 parameters to the function. In Python I'm not sure what's the right solution, because integers and strings are not passed by reference but by value (at leas...


Print out list of function parameters in Python

Is there a way to print out a function's parameter list? For example: def func(a, b, c): pass print_func_parametes(func) Which will produce something like: ["a", "b", "c"]


python - How to create a decorator that can be used either with or without parameters?

I'd like to create a Python decorator that can be used either with parameters: @redirect_output("somewhere.log") def foo(): .... or without them (for instance to redirect the output to stderr by default): @redirect_output def foo(): .... Is that at all possible? Note that I'm not looking for a different solution to the problem of redirectin...


python - Scope of lambda functions and their parameters?

This question already has answers here:






Still can't find your answer? Check out these communities...



PySlackers | Full Stack Python | NHS Python | Pythonist Cafe | Hacker Earth | Discord Python



top