Fitting models to data with curve_fit

SciPy · Practical · 11 min

Turning measurements into model parameters (rates, half-lives, growth curves) is a daily scientific task.

optimize.curve_fit(model, xdata, ydata, p0=...) finds parameters that minimise the squared residuals between model(xdata, *params) and ydata.

The model function takes the independent variable first, then each parameter as a separate argument: def model(t, a, k): return a * np.exp(-k * t).

It returns popt (best parameters) and pcov (their covariance matrix). Standard errors are np.sqrt(np.diag(pcov)); large values or inf mean the parameter is poorly determined by the data.

Practical advice:

  • Give a sensible starting guess p0. The default is all ones, which fails for many non-linear models (exponentials, sigmoids).
  • Use bounds=([lo...], [hi...]) to keep parameters physical, for example a positive rate.
  • If you know measurement errors, pass sigma= and absolute_sigma=True so pcov reflects them.
  • Plot residuals: a pattern means the model shape is wrong, whatever the fit statistics say.

curve_fit wraps optimize.least_squares, which you can call directly for more control: robust losses such as loss="soft_l1" reduce outlier influence, and it works with residual functions of any form. For straight lines, stats.linregress or np.polyfit are simpler.

Example

import numpy as np
from scipy import optimize

def decay(t, a, k):                    # x first, then parameters
    return a * np.exp(-k * t)

rng = np.random.default_rng(0)
t = np.linspace(0, 5, 30)
y = decay(t, 10.0, 0.8) + rng.normal(0, 0.2, t.size)

popt, pcov = optimize.curve_fit(decay, t, y, p0=[5, 1], bounds=([0, 0], [np.inf, np.inf]))
perr = np.sqrt(np.diag(pcov))
print(popt.round(3))                   # [10.094  0.818]
print(perr.round(3))                   # [0.108 0.013] (standard errors)
print(round(np.log(2) / popt[1], 2))   # 0.85 (half-life)

Remember

  • The model signature is f(x, p1, p2, ...)
  • Provide p0 and bounds for non-linear models
  • Standard errors come from sqrt(diag(pcov))

Watch out for

  • Relying on the default p0 of all ones for an exponential or logistic model.

Check yourself

  1. How do you get the standard error of each fitted parameter from curve_fit?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.curve_fit.html