07: Exploring the fit object

What else a fit keeps: its settings, its curves, the classical measures, its history, and how to save it.

Introduction

Tutorial 02 read the bumps, the background, the scores and the table of a fit. A fit keeps more than that. Here we look at the rest of what it holds:

  • fit.settings: every setting that was used;

  • fit.model, fit.residual and fit.window: the fitted curve and what is left;

  • fit.classical: the usual measures, taken directly on the curve;

  • fit.provenance: how the fit was made;

  • fit.save and load: keep a fit on disk and open it again.

import os
import tempfile

import numpy as np

from evoparam import fit_response, gen_response
from evoparam.data import ResponseFit
times, signal = gen_response([[25, -150, 10], [150, -80, 80]], seed=3)
fit = fit_response(times, signal, preset="ccep", delta=20, random_state=0)

The settings

fit.settings is a dictionary with every setting of the fit: those of the preset and those you gave. _changed_from_preset lists the ones you changed, so that they can be found later.

print(len(fit.settings), "settings")
print("fit window:", fit.settings["fit_window"], "s")
print("delta:", fit.settings["delta"])
print("changed from the preset:", fit.settings["_changed_from_preset"])
49 settings
fit window: (0.009, 0.5) s
delta: 20.0
changed from the preset: ['delta']

Here we gave delta=20, so it is listed as changed. The methods text (tutorial 12) reads the same list, so a changed setting is always written in the methods.

The curves

fit.window is the part of the curve that was fitted (9 to 500 ms for "ccep"). fit.model is the fitted curve on that window, and fit.residual what is left (data minus model). They have one value per time point of the window.

in_window = (times >= fit.window[0]) & (times <= fit.window[1])
print("window:", fit.window, "s,", in_window.sum(), "time points")
print("model:", fit.model.shape, " residual:", fit.residual.shape)
print(f"largest leftover: {np.abs(fit.residual).max():.1f} µV")
window: (0.009, 0.5) s, 1006 time points
model: (1006,)  residual: (1006,)
largest leftover: 23.9 µV

With these, you can draw the fit yourself, or compute your own measure of the leftover.

The classical measures

Most papers measure a response directly on the curve: the lowest point in a window (peak amplitude and latency), the mean over a window, the area. fit.classical gives these, measured the usual way, so that evoparam’s numbers can be compared with them.

classical = fit.classical
print(f"lowest point: {1000 * classical['peak_latency']:.1f} ms, ", end="")
print(f"{classical['peak_amplitude']:.0f} µV")
print(
    f"N1 of the model: {fit.components[0].latency_ms:.1f} ms, {fit.components[0].amplitude:.0f} µV"
)
lowest point: 25.9 ms, -128 µV
N1 of the model: 25.0 ms, -152 µV

The lowest point of the curve is at 25.9 ms and -128 µV; the model’s N1 is at 25.0 ms and -152 µV. They differ for two reasons:

  • the lowest point is measured from zero, and the model’s amplitude from the background, which is still above zero at 25 ms (it fades from 60 µV);

  • the lowest point moves with the noise of the samples around it, while the model’s latency uses the whole bump.

classical["definition"] says exactly how each classical measure was taken.

print(classical["definition"]["peak_amplitude"])
local_peak(n_neighbors=3), polarity=negative, window=[0.009, 0.5]

The history

fit.provenance records how the fit was made: the version of evoparam, the date, the function and its settings, the random seed, and the computer (Python and package versions). This is what allows a result to be made again (tutorial 12).

step = fit.provenance.steps[0]
print(step["function"], "| evoparam", fit.provenance.package_version)
print("seed:", step["random_state"], "| python", fit.provenance.environment["python"])
evoparam.models.fit_waveform | evoparam 0.1.0.dev0
seed: 0 | python 3.13.18

Saving and opening a fit

fit.save writes the whole fit (bumps, background, scores, settings, history) in a .evoparam folder, or a single .zip file. ResponseFit.load opens it again, and checks that it was not changed.

folder = tempfile.mkdtemp()
path = os.path.join(folder, "my_fit.zip")
fit.save(path)

again = ResponseFit.load(path)
print(again.components[0].label, f"{again.components[0].latency_ms:.1f} ms")
N1 25.0 ms

fit.to_dict() gives the same content as a plain dictionary, for example to store it in your own format.

Conclusion

Besides the bumps and the scores, a fit keeps its settings (and which ones you changed), its fitted curve and leftover, the classical measures to compare with, and the history of how it was made. It can be saved and opened again with all of it.

Total running time of the script: (0 minutes 0.156 seconds)

Gallery generated by Sphinx-Gallery