fitwrap module
How to install
The fitwrap module is available on github and can be installed via pip:
This module provides a wrapper for the scipy function optimize.curve_fit()
. The full fitting procedure is reduced in one-line command, moreover the initial fitting parameters and boundaries are set by the keyword arguments of the model fitting function. The package comes with the following functions:
fit
: non linear 1D fitfit2d
: non linear 2D fitfit_sin
: sinusoidal fit with automatic fit function and initial guess.
Usage examples
Import the dependencies we need:
import matplotlib.pyplot as plt
import numpy as np
import fitwrap as fw
Simple fit
Lets start with a simple fit of a parabolic function.
Generate data with black box function
np.random.seed(42)
xx = np.linspace(-2,10, 30)
yy = 0.1*(xx-5)**2 + 0.5*(xx-5) + np.random.normal(size=xx.shape)*0.5
fig = plt.figure(figsize=(5,3))
ax = fig.add_subplot(111, xlabel="x", ylabel="y")
ax.scatter(xx, yy)
#fig.savefig('img/simple_data.svg')
Define model function and fit
The first variable of the model function is the independent variable (in our case x
). The following arguments will be used as fit parameters and will be initialized to 1 is not specified.
To fit the data with the model function it is sufficient to call the fit method: fw.fit(model_function, xx, yy)
.
The function will print the fit results, specifying the best fitted value, the absolute error, the relative error and the initial guess for each of the fit parameters. It will plot the input data in green, the best fit curve and the confidence interval corresponding to the confidence probability of 0.95.
def model_function(x, x0, a2, a1):
return a2*(x-x0)**2 + a1*(x-x0)
fit_out = fw.fit(model_function, xx, yy)
x0: 0.18 +/- 0.191 (106.1%) initial:1
a2: 0.11061 +/- 0.00668 (6.0%) initial:1
a1: -0.5875 +/- 0.0555 (-9.4%) initial:1
Initial guess for the fitting parameters
In order to set an initial guess for the initial parameters, pass the initial guess as default parameters in the model function. Keep in mind to place the initial guess to the most right arguments of the model function.
# Good
def model_function(x, x0, a2=0.5, a1=0.2):
return a2*(x-x0)**2 + a1*(x-x0)
# Syntax Error
def model_function(x, x0=3, a2=0.5, a1):
return a2*(x-x0)**2 + a1*(x-x0)
File "<ipython-input-130-3f5a2423c6cd>", line 6
def model_function(x, x0=3, a2=0.5, a1):
^
SyntaxError: non-default argument follows default argument
Fixed arguments
It is possible to fix an agument to exclude it from the fitting parameters using the special keyword fixed_args. Set it as the last parameter of the model function and pass the list of the name of the fitting parameters you want to fix.
Generate a new black box model
np.random.seed(42)
t_decay = np.linspace(0,20, 30)
y_decay = 0.1 + np.exp(-t_decay/2) + np.random.normal(size=t_decay.shape)*0.02
fig = plt.figure(figsize=(5,3))
ax = fig.add_subplot(111, xlabel="time", ylabel="y")
ax.scatter(t_decay, y_decay)
Lets define the model function by fixing the offset of the exponential decay to 0. We expect the fit to be bad since our black box model has an offset of 0.1
.
def model_function(t, off=0, tau=5, amp=1, fixed_args=['off']):
return off + amp*np.exp(-t/tau)
fit_out_fixed = fw.fit(model_function, t_decay, y_decay)
off: 0 Fixed
tau: 3.055 +/- 0.247 (8.1%) initial:5
amp: 1.0271 +/- 0.0527 (5.1%) initial:1
Bounded parameters
To fix the boundaries of a fitting parameter one can set its default value in the model definition as the tuple: (initial guess, lower bound, upper bound)
Lets set the offset to be bounded between 0.2
and 0.5
. We expect again the fit function to fail.
def model_function(t, off=(0.4, 0.2, 0.5), tau=5, amp=1):
return off + amp*np.exp(-t/tau)
fit_out_bound = fw.fit(model_function, t_decay, y_decay)
off: 0.2 +/- 0.0207 (10.3%) initial:(0.4, 0.2, 0.5)
tau: 1.505 +/- 0.27 (17.9%) initial:5
amp: 0.9371 +/- 0.0882 (9.4%) initial:1
Consider errors on data points
It is possible to consider errors on the y
variable by passing an array of sigmas to the fit function.
Generate a new black box model
Let us now consider two different types of errors: amplitude error (left plot) and offset error (right plot).
np.random.seed(42)
t_decay = np.linspace(0,10, 30)
y_decay = 0.1 + np.exp(-t_decay/2) + np.random.normal(size=t_decay.shape)*0.03
y_decay_err_1 = (0.01 + np.exp(-t_decay/2))/2
y_decay_err_2 = 1/(y_decay_err_1*1e3)
fig = plt.figure(figsize=(10,3))
ax1 = fig.add_subplot(121, xlabel="time", ylabel="y")
ax1.errorbar(t_decay, y_decay, y_decay_err_1, fmt='o')
ax2 = fig.add_subplot(122, xlabel="time", ylabel="y")
ax2.errorbar(t_decay, y_decay, y_decay_err_2, fmt='o')
Lets fit the two datasets including the different errors.
def model_function(t, off=0.1, tau=5, amp=1):
return off + amp*np.exp(-t/tau)
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(10,3))
print('Error on amplitude:')
fit_out_err_1 = fw.fit(model_function, t_decay, y_decay,
sigma=y_decay_err_1, fig_ax=(fig, ax1))
print('\nError on offset:')
fit_out_err_2 = fw.fit(model_function, t_decay, y_decay,
sigma=y_decay_err_2, fig_ax=(fig, ax2))
Example of automatic sinusoidal fit
Fitting function model: y = off + amp * sin(2 * pi * freq * x + phase)
off: 0.5493 +/- 0.0661 (12.0%) initial:0.6485271451564831
amp: 1.969 +/- 0.222 (11.3%) initial:4.236288465011205
freq: 3.9981 +/- 0.026 (0.7%) initial:4.043991443019413
phase: 0.73 +/- 0.149 (20.4%) initial:0.39269908169872414
<?xml version=”1.0” encoding=”utf-8” standalone=”no”?>
Example of 2D fit
x0: 20.0004 +/- 0.0459 (0.2%) initial:21
y0: 30.0435 +/- 0.0455 (0.2%) initial:32
sx: 5.0078 +/- 0.0398 (0.8%) initial:8
sy: 4.9602 +/- 0.0394 (0.8%) initial:5
<?xml version=”1.0” encoding=”utf-8” standalone=”no”?>
Example of lomb spectrum
<?xml version=”1.0” encoding=”utf-8” standalone=”no”?>