74

Python's curve_fit calculates the best-fit parameters for a function with a single independent variable, but is there a way, using curve_fit or something else, to fit for a function with multiple independent variables? For example:

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

where x and y are the independent variable and we would like to fit for a, b, and c.

1

5 Answers 5

97

You can pass curve_fit a multi-dimensional array for the independent variables, but then your func must accept the same thing. For example, calling this array X and unpacking it to x, y for clarity:

import numpy as np from scipy.optimize import curve_fit def func(X, a, b, c): x,y = X return np.log(a) + b*np.log(x) + c*np.log(y) # some artificially noisy data to fit x = np.linspace(0.1,1.1,101) y = np.linspace(1.,2., 101) a, b, c = 10., 4., 6. z = func((x,y), a, b, c) * 1 + np.random.random(101) / 100 # initial guesses for a,b,c: p0 = 8., 2., 7. print(curve_fit(func, (x,y), z, p0)) 

Gives the fit:

(array([ 9.99933937, 3.99710083, 6.00875164]), array([[ 1.75295644e-03, 9.34724308e-05, -2.90150983e-04], [ 9.34724308e-05, 5.09079478e-06, -1.53939905e-05], [ -2.90150983e-04, -1.53939905e-05, 4.84935731e-05]])) 
Sign up to request clarification or add additional context in comments.

4 Comments

If the x and y are not the same size is there a way to modify the solution to use curve fit. For example, x = linspace(0.1,1.1,101) and y = np.array([1.0,2.0])?
I'm not sure I follow you: func represents a bivariate function (taking two independent variables), so for the fit it should be defined as giving a result f(x_i,y_i) for any provided input values x_i and y_i. If x and y don't have the same size then you're trying to evaluate it e.g. at some x but with y undefined which surely can't be done.
I just want to share the solution I had for the same problem as @ScottG. Let there be 20 samples in the x direction and 30 in the y direction, and data for every intersection (20x30 = 600 samples total). I used x,y = np.mesgrid(x,y) and then np.stack((x,y), axis=2).reshape(-1, 2) to get a (600,2) array which will be xdata, with all 600 x and y combinations. Then I flattened by data from the 600 samples to a 1d (600,) array which will be ydata instead of a 2d (20, 30) array. And then you can just unpack your your data in func with x, y = np.hsplit(X, 2). (X is xdata)
Follow-up that I observed w.r.t. a similar dimension problem: What if my array X contains various types of variables. Here, I have x0, x4, y0, mylist. The curve_fit seems to unpack these under the hood into numpy arrays but runs into an error with mylist. I get: "ValueError: setting an array element with a sequence. The requested array has an inhomogeneous shape after 1 dimensions. The detected shape was (4,) + inhomogeneous part.". Any idea on how this can be treated? Surprisingly, also, leastsq seems to work & I thought curve_fit is simply a wrapper around this?
5

optimizing a function with multiple input dimensions and a variable number of parameters

This example shows how to fit a polynomial with a two dimensional input (R^2 -> R) by an increasing number of coefficients. The design is very flexible so that the callable f from curve_fit is defined once for any number of non-keyword arguments.

enter image description here

minimal reproducible example

import numpy as np import matplotlib.pyplot as plt from scipy.optimize import curve_fit def poly2d(xy, *coefficients): x = xy[:, 0] y = xy[:, 1] proj = x + y res = 0 for order, coef in enumerate(coefficients): res += coef * proj ** order return res nx = 31 ny = 21 range_x = [-1.5, 1.5] range_y = [-1, 1] target_coefficients = (3, 0, -19, 7) xs = np.linspace(*range_x, nx) ys = np.linspace(*range_y, ny) im_x, im_y = np.meshgrid(xs, ys) xdata = np.c_[im_x.flatten(), im_y.flatten()] im_target = poly2d(xdata, *target_coefficients).reshape(ny, nx) fig, axs = plt.subplots(2, 3, figsize=(29.7, 21)) axs = axs.flatten() ax = axs[0] ax.set_title('Unknown polynomial P(x+y)\n[secret coefficients: ' + str(target_coefficients) + ']') sm = ax.imshow( im_target, cmap = plt.get_cmap('coolwarm'), origin='lower' ) fig.colorbar(sm, ax=ax) for order in range(5): ydata=im_target.flatten() popt, pcov = curve_fit(poly2d, xdata=xdata, ydata=ydata, p0=[0]*(order+1) ) im_fit = poly2d(xdata, *popt).reshape(ny, nx) ax = axs[1+order] title = 'Fit O({:d}):'.format(order) for o, p in enumerate(popt): if o%2 == 0: title += '\n' if o == 0: title += ' {:=-{w}.1f} (x+y)^{:d}'.format(p, o, w=int(np.log10(max(abs(p), 1))) + 5) else: title += ' {:=+{w}.1f} (x+y)^{:d}'.format(p, o, w=int(np.log10(max(abs(p), 1))) + 5) title += '\nrms: {:.1f}'.format( np.mean((im_fit-im_target)**2)**.5 ) ax.set_title(title) sm = ax.imshow( im_fit, cmap = plt.get_cmap('coolwarm'), origin='lower' ) fig.colorbar(sm, ax=ax) for ax in axs.flatten(): ax.set_xlabel('x') ax.set_ylabel('y') plt.show() 

P.S. The concept of this answer is identical to my other answer here, but the code example is way more clear. At the time given, I will delete the other answer.

Comments

2

Fitting to an unknown numer of parameters

In this example, we try to reproduce some measured data measData. In this example measData is generated by the function measuredData(x, a=.2, b=-2, c=-.8, d=.1). I practice, we might have measured measData in a way - so we have no idea, how it is described mathematically. Hence the fit.

We fit by a polynomial, which is described by the function polynomFit(inp, *args). As we want to try out different orders of polynomials, it is important to be flexible in the number of input parameters. The independent variables (x and y in your case) are encoded in the 'columns'/second dimension of inp.

import numpy as np import matplotlib import matplotlib.pyplot as plt from scipy.optimize import curve_fit def measuredData(inp, a=.2, b=-2, c=-.8, d=.1): x=inp[:,0] y=inp[:,1] return a+b*x+c*x**2+d*x**3 +y def polynomFit(inp, *args): x=inp[:,0] y=inp[:,1] res=0 for order in range(len(args)): print(14,order,args[order],x) res+=args[order] * x**order return res +y inpData=np.linspace(0,10,20).reshape(-1,2) inpDataStr=['({:.1f},{:.1f})'.format(a,b) for a,b in inpData] measData=measuredData(inpData) fig, ax = plt.subplots() ax.plot(np.arange(inpData.shape[0]), measData, label='measuered', marker='o', linestyle='none' ) for order in range(5): print(27,inpData) print(28,measData) popt, pcov = curve_fit(polynomFit, xdata=inpData, ydata=measData, p0=[0]*(order+1) ) fitData=polynomFit(inpData,*popt) ax.plot(np.arange(inpData.shape[0]), fitData, label='polyn. fit, order '+str(order), linestyle='--' ) ax.legend( loc='upper left', bbox_to_anchor=(1.05, 1)) print(order, popt) ax.set_xticklabels(inpDataStr, rotation=90) 

Result:

enter image description here

Comments

1

Yes. We can pass multiple variables for curve_fit. I have written a piece of code:

import numpy as np x = np.random.randn(2,100) w = np.array([1.5,0.5]).reshape(1,2) esp = np.random.randn(1,100) y = np.dot(w,x)+esp y = y.reshape(100,) 

In the above code I have generated x a 2D data set in shape of (2,100) i.e, there are two variables with 100 data points. I have fit the dependent variable y with independent variables x with some noise.

def model_func(x,w1,w2,b): w = np.array([w1,w2]).reshape(1,2) b = np.array([b]).reshape(1,1) y_p = np.dot(w,x)+b return y_p.reshape(100,) 

We have defined a model function that establishes relation between y & x.
Note: The shape of output of the model function or predicted y should be (length of x,)

popt, pcov = curve_fit(model_func,x,y) 

The popt is an 1D numpy array containing predicted parameters. In our case there are 3 parameters.

Comments

-1

Yes, there is: simply give curve_fit a multi-dimensional array for xData.

1 Comment

I've been trying to lump x and y into a single array z=[x,y], so that x=z[0] and y=z[1]. But curve_fit doesn't seem to like that and gives me an error: TypeError: unsupported operand type(s) for /: 'list' and 'float'

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.