Skip to main content
v2026.11,858 entries · CC-BY 4.0

Curve Fit in SciPy: curve_fit Step by Step

How to run a scipy curve fit with curve_fit: model, bounds, weights, reading popt and pcov, and what each error means.

Written and maintained by CASRAI Editorial Board

Last updated

SciPy’s scipy.optimize.curve_fit fits a model function you write to your measured x and y values using non-linear least squares, and returns the best-fit parameters (popt) plus an estimated covariance matrix (pcov) from which you can read parameter uncertainties. This guide covers the whole workflow for “scipy curve fit” tasks: install, a worked fit, bounds and weights, how to read the output, and what each error or warning means. The details follow the SciPy v1.18.0 reference.

What curve_fit does in research data analysis

Curve fitting with SciPy means choosing a model, such as an exponential decay or a dose-response curve, and letting the optimiser find the parameter values that minimise the sum of squared residuals between the model and your data. The SciPy reference describes the returned popt as the parameters that minimise that sum of squared residuals, and pcov as the estimated covariance of popt.

Two points set curve_fit apart from a plain regression routine. First, you supply the model yourself as a Python function, so any parameterised form is allowed. Second, the same call can weight points by their measurement error (sigma) and restrict parameters to a range (bounds). For a laboratory application of the same call, see the CASRAI guide on four-parameter logistic ELISA standard curve fitting. If your relationship is not a straight line and you are still deciding on a model form, start with non-linear relationships in data.

Install SciPy

The official SciPy install page (scipy.org/install) gives these commands:

  • pip, inside an activated virtual environment: python -m pip install scipy
  • conda (via Miniforge): conda install scipy; or with mamba: mamba install scipy

The same page groups the options: project-based tools (uv, pixi) are described as recommended for new users, environment-based tools (pip, conda) as the traditional workflow, system package managers as not recommended, and building from source as meant for debugging and development. To make an environment others can recreate, see sharing a reproducible conda environment.

Fit a curve with curve_fit, step by step

The signature on the v1.18.0 reference page is curve_fit(f, xdata, ydata, p0=None, sigma=None, absolute_sigma=False, check_finite=None, bounds=(-inf, inf), method=None, jac=None, *, full_output=False, nan_policy=None, **kwargs). The steps below follow the official example.

  1. Import the tools. import numpy as np and from scipy.optimize import curve_fit.
  2. Write the model function. The independent variable must be the first argument and the parameters to fit are the remaining arguments: def func(x, a, b, c): return a * np.exp(-b * x) + c.
  3. Prepare the data. The official example builds 50 points with np.linspace(0, 4, 50) (see NumPy linspace) and adds normally distributed noise to the model output. With real data, xdata and ydata are your measured arrays.
  4. Call the fit. popt, pcov = curve_fit(func, xdata, ydata).
  5. Optionally constrain parameters. popt, pcov = curve_fit(func, xdata, ydata, bounds=(0, [3., 1., 0.5])) keeps a between 0 and 3, b between 0 and 1, and c between 0 and 0.5.

A complete version of the official example:

import numpy as np
from scipy.optimize import curve_fit

def func(x, a, b, c):
    return a * np.exp(-b * x) + c

xdata = np.linspace(0, 4, 50)
ydata = func(xdata, 2.5, 1.3, 0.5) + 0.2 * np.random.default_rng().normal(size=xdata.size)

popt, pcov = curve_fit(func, xdata, ydata)
popt_b, pcov_b = curve_fit(func, xdata, ydata, bounds=(0, [3., 1., 0.5]))

Starting values (p0)

If p0 is None, the initial values are all 1 when the number of parameters can be determined from the function. That default is a common reason a fit lands on the wrong solution or fails: pass a p0 list close to your expected values when your parameters differ greatly from 1. When parameters differ by orders of magnitude, the reference page says the x_scale argument can scale them for the 'trf' and 'dogbox' methods.

Weighted fits with sigma

The sigma argument tells the fit how much to trust each point. A scalar or 1-D sigma is read as standard deviations of the errors in ydata, and the quantity minimised is chisq = sum((r / sigma) ** 2), where r are the residuals. A 2-D sigma is read as a covariance matrix, and the minimised quantity is chisq = r.T @ inv(sigma) @ r. Points with a larger sigma therefore pull the fit less.

Choosing a method

The method argument accepts 'lm', 'trf' and 'dogbox'. The default is 'lm' for unconstrained problems and 'trf' if bounds are given. The reference page notes that 'lm' will not work when the number of observations is less than the number of variables. Extra keyword arguments are passed to leastsq for 'lm' and to least_squares otherwise.

Output and how to read it (popt and pcov)

popt is an array of fitted parameters in the same order as the arguments of your model function after x. pcov is a 2-D array, the estimated covariance of popt. To get one-standard-deviation errors on each parameter, the SciPy page gives perr = np.sqrt(np.diag(pcov)).

How much to trust the errors

The covariance estimate relies on a linear approximation. The reference page states that when this approximation becomes inaccurate, pcov may not provide an accurate measure of uncertainty. Report parameter errors from pcov as approximate, and for a model with strong curvature near the optimum do not treat them as exact confidence intervals.

absolute_sigma: True or False

With the default absolute_sigma=False, sigma is used only up to a scale factor, so pcov is rescaled from the fit residuals. With absolute_sigma=True, pcov reflects the sigma values in absolute terms. Use True only when your sigma values are real measurement standard deviations rather than relative weights.

Spotting an overparameterised model

The reference page notes that np.linalg.cond(pcov) can flag an overparameterised model: use it as a check on whether the model has more parameters than the data support. To also get the raw optimiser output, set full_output=True, which additionally returns infodict, mesg and ier. To inspect residuals, see how to calculate residuals.

Troubleshooting curve_fit errors and warnings

The documentation lists four distinct failure signals. Each points to a different fix:

Signal What the docs say it means What to try
ValueError Raised if either ydata or xdata contain NaNs, or if incompatible options are used. Remove or handle NaNs before fitting; check the combination of options you passed.
RuntimeError Raised if the least-squares minimisation fails. Give a better p0, add bounds so parameters stay in a plausible range, or simplify the model.
TypeError Raised if the number of data points is fewer than the number of parameters. Collect more points or fit a model with fewer parameters.
OptimizeWarning Raised if the covariance of the parameters cannot be estimated. Do not report parameter errors from this fit; try a simpler model or better starting values.

Also note that bounds change the solver: with bounds set, the default method switches from 'lm' to 'trf', and the reference page states 'lm' cannot handle fewer observations than variables.

Free alternatives to curve_fit

The closest alternative lives in the same SciPy library you have already installed. scipy.optimize.least_squares is described in its documentation as solving a nonlinear least-squares problem with bounds on the variables, and the curve_fit page itself routes non-'lm' methods to it.

Function Input you provide Notable options (from the SciPy docs)
scipy.optimize.curve_fit Model f(x, ...), xdata, ydata, optional sigma Returns popt and pcov; methods 'lm', 'trf', 'dogbox'; bounds
scipy.optimize.least_squares A residual function and starting values (not model plus data) loss of 'linear' (default), 'soft_l1' (described as usually a good choice for robust fitting), 'huber', 'cauchy', 'arctan'; f_scale sets the inlier/outlier margin; method 'trf' by default, 'dogbox' for small problems with bounds, 'lm' does not handle bounds and supports only the linear loss

In practice this means: if outliers are distorting your fit, move from curve_fit to least_squares with a robust loss such as 'soft_l1'. The least_squares page lists curve_fit in its See also section as “Least-squares minimization applied to a curve-fitting problem”. For other SciPy tools used in the same analyses, see scipy.stats.

FAQ

What does sigma do in scipy curve fit?

It weights the points. A scalar or 1-D sigma holds standard deviations of the errors in ydata and the fit minimises sum((r / sigma) ** 2); a 2-D sigma is treated as a covariance matrix. Whether pcov reflects sigma in absolute terms is controlled by absolute_sigma.

How do I set bounds in curve fitting with SciPy?

Pass bounds as a 2-tuple of lower and upper limits, for example bounds=(0, [3., 1., 0.5]). Use np.inf (or -np.inf) for an unbounded side. Setting bounds switches the default method to 'trf'.

How do I get parameter errors from curve_fit?

Compute perr = np.sqrt(np.diag(pcov)). These are one-standard-deviation errors based on a linear approximation, so they can be inaccurate for strongly non-linear models.

Why does curve_fit warn that the covariance cannot be estimated?

That is the OptimizeWarning, which SciPy raises when the covariance of the parameters cannot be estimated. The docs separately suggest np.linalg.cond(pcov) as a check for an overparameterised model.

What initial values does curve_fit use?

If p0 is None, the initial values are all 1. Supply your own p0 when you can estimate the parameters.

Sources

Follow CASRAI

Research-administration guidance, standards updates and independent tool reviews.

Ask CASRAI · free to try

Ask about Curve Fit in SciPy: curve_fit Step by Step

Ask your first 2 questions free below. Subscribers get 150 a day for $29 a month.

An AI assistant specialized in research administration. It cites the sources behind every answer, labels web answers and says when it can't answer.

Answers draw on CASRAI's guides and dictionary plus the federal and funder documents we index: Federal Register, Grants.gov, Regulations.gov and UKRI.

Works on this site and inside Claude, Cursor and the AI tools you already use.

Everything CASRAI publishes — this page, the dictionary, the guides and the news — stays free to read, with no account and no card.

Referenced across the research world

University of Cambridge logoColumbia University logoCrossref logoUniversity of Edinburgh logoHarvard University logoUniversity of Oxford logoPrinceton University logoStanford School of Medicine logoUniversity College London logoORCID logoUniversity of Cambridge logoColumbia University logoCrossref logoUniversity of Edinburgh logoHarvard University logoUniversity of Oxford logoPrinceton University logoStanford School of Medicine logoUniversity College London logoORCID logo
  • University of Cambridge logo
  • Columbia University logo
  • Crossref logo
  • University of Edinburgh logo
  • Harvard University logo
  • University of Oxford logo
  • Princeton University logo
  • Stanford School of Medicine logo
  • University College London logo
  • ORCID logo

View CASRAI adoption →

Ask CASRAI · Regulatory Radar

Research-admin question? Get an answer that links its sources.

An AI assistant specialized in research administration. Every answer links its sources to check before you act. 2 questions free, no account. $29/month after.

  • Answers draw on CASRAI's guides and dictionary plus the federal and funder documents we index: Federal Register, Grants.gov, Regulations.gov and UKRI.
  • Every answer numbers its sources and links each one, so you can check the source yourself.