Data Analysis for Engineers/Module 3

Module 3 · Section 7 of 11

Lesson 3.6 - Curve Fitting

Target: ~10 min read - 20 min hands-on

Overview

scipy.optimize.curve_fit fits a chosen model (power, exponential, or any custom function) to noisy data by finding the parameters that minimize the gap between model and observations. We'll fit a first-order step response - the shape you see when a temperature sensor, an RC circuit, a tank level, or a thermal mass settles toward a new steady value.

Why This Matters (Engineering Context)

Extracting a time constant tau from step-test data is exactly how you characterize a sensor's lag, tune a control loop, or size a thermal system - curve_fit automates the fit.

Code-Along

from scipy.optimize import curve_fit
import numpy as np

np.random.seed(11)

# Generate synthetic step-response data:  y(t) = y_inf * (1 - exp(-t / tau))
t_s = np.array([0, 1, 2, 4, 7, 12, 18, 25, 35, 50])
y_inf_true, tau_true = 42.0, 9.0
y_true = y_inf_true * (1 - np.exp(-t_s / tau_true))
y_obs = y_true + np.random.normal(0, 1.2, size=len(t_s))    # add measurement noise


# The model: first arg is the x data, the rest are the parameters to fit
def first_order(t, y_inf, tau):
    return y_inf * (1 - np.exp(-t / tau))


# curve_fit finds the (y_inf, tau) that best match y_obs. p0 = starting guess.
# It returns (best-fit params, covariance matrix); "_" discards the covariance.
params, _ = curve_fit(first_order, t_s, y_obs, p0=[40, 10])
y_inf_fit, tau_fit = params
print(f"Fitted y_inf: {y_inf_fit:.2f}  (true {y_inf_true})")
print(f"Fitted tau:   {tau_fit:.2f} s  (true {tau_true})")

t_query = 90
# *params unpacks the tuple into first_order's (y_inf, tau) arguments
print(f"\nPredicted response at {t_query} s: {first_order(t_query, *params):.2f}")

# R-squared = 1 - (sum of squared residuals) / (total variance of y); 1.0 = perfect
resid = y_obs - first_order(t_s, *params)
r2 = 1 - np.sum(resid ** 2) / np.sum((y_obs - np.mean(y_obs)) ** 2)
print(f"R-squared: {r2:.4f}")

Run it: the fitted y_inf and tau land close to the true generating values (42.0 and 9.0) despite the added noise - that recovery is the whole point of curve fitting. R-squared near 1.0 indicates a strong fit; a low value would mean the wrong model form.

Practice Exercises

  1. Fit a power-law model y = a * x**b to a synthetic dataset (x = np.linspace(1, 20, 15), y = 3.5 * x**0.6 + noise) and recover a and b.
  2. Increase the noise (scale=3.0 instead of 1.2) and observe how far the fitted parameters drift from the true values.
  3. Use the fitted tau to estimate how long it takes to reach 95% of y_inf: solve 0.95 = 1 - exp(-t/tau).
# Try the practice exercises here

Knowledge Check

  1. What does curve_fit return?
  2. Why is an initial guess (p0) sometimes necessary for curve_fit to converge correctly?
  3. What does an R^2 near 1.0 indicate about a fitted curve?
Answer key
  1. The best-fit parameter values (and a covariance matrix describing their uncertainty)
  2. Non-linear optimization can converge to a wrong local solution, or fail, without a reasonable start
  3. The model explains most of the variance in the observed data - a strong fit

29 / 63 sections · Course home · Join the coaching cohort