Using Custom functions to model aperiodic signals#

Aperiodic changes in a power spectrum can be modeled using a variety of different linear models. This can be easily accomplished using scipy’s curve_fit function. PyRASA aims to give users both some preset models that are similar to what has been implemented in specparam, as well as ability to fit custom models. The preset models are called fixed and knee and can be entered to the compute_slope method in the IrasaSpectrum returned by the pyrasa.irasa function. However, users can also import the class AbstractFitFun from pyrasa.utils.fit_funcs and inherit from it. Below we will illustrate how this is done and how the resultant child class can be used as a Custom fit function to model aperiodic signals.

First we simulated a signal with a spectral exponent of 1.5

[1]:
import scipy.signal as dsp
from pyrasa.utils.aperiodic_utils import compute_aperiodic_model
from pyrasa.utils.fit_funcs import AbstractFitFun
from pyrasa import irasa
import numpy as np
from neurodsp.sim import sim_powerlaw
import pandas as pd

n_secs = 60
fs=1000
f_range = [1.5, 150]
exponent = -1.5

sig = sim_powerlaw(n_seconds=n_secs, fs=fs, exponent=exponent)
freqs, psd = dsp.welch(sig, fs, nperseg=int(4 * fs))
freq_logical = np.logical_and(freqs >= f_range[0], freqs <= f_range[1])
psd, freqs = psd[freq_logical], freqs[freq_logical]

Now we need to simply overwrite the func method and enter it as a fit_func to either the compute_slope function imported fromfrom pyrasa.utils.aperiodic_utils. Or we can use the method of the IrasaSpectrum returned by the pyrasa.irasa function.

[2]:

class CustomFitFun(AbstractFitFun): log10_aperiodic = True log10_freq = True def func(self, x: np.ndarray, a: float, b: float) -> np.ndarray: """ Fixed fitting function. Use this to model aperiodic activity without a spectral knee """ y_hat = a - b * x return y_hat slope_fit = compute_aperiodic_model(psd, freqs, fit_func=CustomFitFun)
[3]:
slope_fit.aperiodic_params
[3]:
a b fit_type ch_name
0 -1.246405 1.480257 custom 0
[4]:
slope_fit_2 = irasa(sig, fs=fs, band=f_range, psd_kwargs={'nperseg': 4 * fs}).fit_aperiodic_model(fit_func=CustomFitFun)
[5]:
slope_fit_2.aperiodic_params
[5]:
a b fit_type ch_name
0 -1.237573 1.483932 custom 0
[6]:
pd.concat([slope_fit.gof,
           slope_fit_2.gof])
[6]:
mse R2 R2_adj. BIC BIC_adj. AIC fit_type ch_name
0 0.006865 0.979519 0.979450 -19.046324 -25.395720 -27.823447 custom 0
0 0.000361 0.998907 0.998903 -37.861877 -44.211273 -46.638999 custom 0
[ ]: