Skip to content

Commit a08e1bd

Browse files
committed
fix merge
2 parents 7f2bca4 + 006c93a commit a08e1bd

File tree

5 files changed

+257
-20
lines changed

5 files changed

+257
-20
lines changed

examples/models/plot_data_components.py

Lines changed: 109 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,8 @@
2929
fm.fit(freqs, powers)
3030

3131
###################################################################################################
32-
# Data Components
33-
# ~~~~~~~~~~~~~~~
32+
# Data & Model Components
33+
# -----------------------
3434
#
3535
# The model fit process includes procedures for isolating aperiodic and periodic components in
3636
# the data, fitting each of these components separately, and then combining the model components
@@ -39,6 +39,11 @@
3939
# In doing this process, the model fit procedure computes and stores isolated data components,
4040
# which are available in the model.
4141
#
42+
43+
###################################################################################################
44+
# Full Data & Model Components
45+
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
46+
#
4247
# Before diving into the isolated data components, let's check the data (`power_spectrum`)
4348
# and full model fit of a model object (`modeled_spectrum`).
4449
#
@@ -53,25 +58,39 @@
5358
# Plot the power spectrum model from the object
5459
plot_spectra(fm.freqs, fm.modeled_spectrum_, color='red')
5560

61+
###################################################################################################
62+
# Isolated Components
63+
# -------------------
64+
#
65+
# As well as the 'full' data & model components above, the model fitting procedure includes
66+
# steps that result in isolated periodic and aperiodic components, in both the
67+
# data and model. These isolated components are stored internally in the model.
68+
#
69+
# To access these components, we can use the following `getter` methods:
70+
#
71+
# - :meth:`~specparam.SpectralModel.get_data`: allows for accessing data components
72+
# - :meth:`~specparam.SpectralModel.get_model`: allows for accessing model components
73+
#
74+
5675
###################################################################################################
5776
# Aperiodic Component
5877
# ~~~~~~~~~~~~~~~~~~~
5978
#
6079
# To fit the aperiodic component, the model fit procedure includes a peak removal process.
6180
#
62-
# The resulting 'peak-removed' data component is stored in the model object, in the
63-
# `_spectrum_peak_rm` attribute.
81+
# The resulting 'peak-removed' data component is stored in the model object, as well as the
82+
# isolated aperiodic component model fit.
6483
#
6584

6685
###################################################################################################
6786

6887
# Plot the peak removed spectrum data component
69-
plot_spectra(fm.freqs, fm._spectrum_peak_rm, color='black')
88+
plot_spectra(fm.freqs, fm.get_data('aperiodic'), color='black')
7089

7190
###################################################################################################
7291

7392
# Plot the peak removed spectrum, with the model aperiodic fit
74-
plot_spectra(fm.freqs, [fm._spectrum_peak_rm, fm._ap_fit],
93+
plot_spectra(fm.freqs, [fm.get_data('aperiodic'), fm.get_model('aperiodic')],
7594
colors=['black', 'blue'], linestyle=['-', '--'])
7695

7796
###################################################################################################
@@ -81,19 +100,20 @@
81100
# To fit the periodic component, the model fit procedure removes the fit peaks from the power
82101
# spectrum.
83102
#
84-
# The resulting 'flattened' data component is stored in the model object, in the
85-
# `_spectrum_flat` attribute.
103+
# The resulting 'flattened' data component is stored in the model object, as well as the
104+
# isolated periodic component model fit.
86105
#
87106

88107
###################################################################################################
89108

90109
# Plot the flattened spectrum data component
91-
plot_spectra(fm.freqs, fm._spectrum_flat, color='black')
110+
plot_spectra(fm.freqs, fm.get_data('peak'), color='black')
92111

93112
###################################################################################################
94113

95114
# Plot the flattened spectrum data with the model peak fit
96-
plot_spectra(fm.freqs, [fm._spectrum_flat, fm._peak_fit], colors=['black', 'green'])
115+
plot_spectra(fm.freqs, [fm.get_data('peak'), fm.get_model('peak')],
116+
colors=['black', 'green'], linestyle=['-', '--'])
97117

98118
###################################################################################################
99119
# Full Model Fit
@@ -106,18 +126,87 @@
106126
###################################################################################################
107127

108128
# Plot the full model fit, as the combination of the aperiodic and peak model components
109-
plot_spectra(fm.freqs, [fm._ap_fit + fm._peak_fit], color='red')
129+
plot_spectra(fm.freqs, [fm.get_model('aperiodic') + fm.get_model('peak')], color='red')
110130

111131
###################################################################################################
112-
# Notes on Analyzing Data Components
113-
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
132+
# Linear vs Log Spacing
133+
# ---------------------
114134
#
115135
# The above shows data components as they are available on the model object, and used in
116-
# the fitting process. Some analyses may aim to use these isolated components to compute
117-
# certain measures of interest on the data. Note that these data components are stored in
118-
# 'private' attributes (indicated by a leading underscore), meaning in normal function they
119-
# are not expected to be accessed by the user, but as we've seen above they can still be accessed.
120-
# However, analyses derived from these isolated data components is not currently officially
121-
# supported by the module, and so users who wish to do so should consider the benefits and
122-
# limitations of any such analyses.
136+
# the fitting process - notable, in log10 spacing.
137+
#
138+
# Some analyses may aim to use these isolated components to compute certain measures of
139+
# interest on the data. However, when doing so, one may often want the linear power
140+
# representations of these components.
141+
#
142+
# Both the `get_data` and `get_model` methods accept a 'space' argument, whereby the user
143+
# can specify whether the return the components in log10 or linear spacing.
144+
#
145+
146+
###################################################################################################
147+
# Aperiodic Components in Linear Space
148+
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
149+
#
150+
# First we can examine the aperiodic data & model components, in linear space.
151+
#
152+
153+
###################################################################################################
154+
155+
# Plot the peak removed spectrum, with the model aperiodic fit
156+
plot_spectra(fm.freqs, [fm.get_data('aperiodic', 'linear'), fm.get_model('aperiodic', 'linear')],
157+
colors=['black', 'blue'], linestyle=['-', '--'])
158+
159+
###################################################################################################
160+
# Peak Component in Linear Space
161+
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
162+
#
163+
# Next, we can examine the peak data & model components, in linear space.
164+
#
165+
166+
###################################################################################################
167+
168+
# Plot the flattened spectrum data with the model peak fit
169+
plot_spectra(fm.freqs, [fm.get_data('peak', 'linear'), fm.get_model('peak', 'linear')],
170+
colors=['black', 'green'], linestyle=['-', '--'])
171+
172+
###################################################################################################
173+
# Linear Space Additive Model
174+
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~
175+
#
176+
# Note that specifying 'linear' does not simply unlog the data components to return them
177+
# in linear space, but instead defines the space of the additive data definition such that
178+
# `power_spectrum = aperiodic_component + peak_component` (for data and/or model).
179+
#
180+
# We can see this by plotting the linear space data (or model) with the corresponding
181+
# aperiodic and periodic components summed together. Note that if you simply unlog
182+
# the components and sum them, they does not add up to reflecting the full data / model.
183+
#
184+
185+
###################################################################################################
186+
187+
# Plot the linear data, showing the combination of peak + aperiodic matches the full data
188+
plot_spectra(fm.freqs,
189+
[fm.get_data('full', 'linear'),
190+
fm.get_data('aperiodic', 'linear') + fm.get_data('peak', 'linear')],
191+
linestyle=['-', 'dashed'], colors=['black', 'red'], alpha=[0.3, 0.75])
192+
193+
###################################################################################################
194+
195+
# Plot the linear model, showing the combination of peak + aperiodic matches the full model
196+
plot_spectra(fm.freqs,
197+
[fm.get_model('full', 'linear'),
198+
fm.get_model('aperiodic', 'linear') + fm.get_model('peak', 'linear')],
199+
linestyle=['-', 'dashed'], colors=['black', 'red'], alpha=[0.3, 0.75])
200+
201+
###################################################################################################
202+
# Notes on Analyzing Data & Model Components
203+
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
204+
#
205+
# The functionality here allows for accessing the model components in log space (as used by
206+
# the model for fitting), as well as recomputing in linear space.
207+
#
208+
# If you are aiming to analyze these components, it is important to consider which version of
209+
# the data you should analyze for the question at hand, as there are key differences to the
210+
# different representations. Users who wish to do so post-hoc analyses of these data and model
211+
# components should consider the benefits and limitations the different representations.
123212
#

specparam/core/utils.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,25 @@
88
###################################################################################################
99
###################################################################################################
1010

11+
def unlog(arr, base=10):
12+
"""Helper function to unlog an array.
13+
14+
Parameters
15+
----------
16+
arr : ndarray
17+
Array.
18+
base : float
19+
Base of the log to undo.
20+
21+
Returns
22+
-------
23+
ndarray
24+
Unlogged array.
25+
"""
26+
27+
return np.power(base, arr)
28+
29+
1130
def group_three(vec):
1231
"""Group an array of values into threes.
1332

specparam/objs/fit.py

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@
6363
from numpy.linalg import LinAlgError
6464
from scipy.optimize import curve_fit
6565

66+
<<<<<<< HEAD:specparam/objs/fit.py
6667
from specparam.core.items import OBJ_DESC
6768
from specparam.core.info import get_indices
6869
from specparam.core.io import save_model, load_json
@@ -80,6 +81,27 @@
8081
from specparam.data import FitResults, ModelRunModes, ModelSettings, SpectrumMetaData
8182
from specparam.data.conversions import model_to_dataframe
8283
from specparam.sim.gen import gen_freqs, gen_aperiodic, gen_periodic, gen_model
84+
=======
85+
from fooof.core.utils import unlog
86+
from fooof.core.items import OBJ_DESC
87+
from fooof.core.info import get_indices
88+
from fooof.core.io import save_fm, load_json
89+
from fooof.core.reports import save_report_fm
90+
from fooof.core.modutils import copy_doc_func_to_method
91+
from fooof.core.utils import group_three, check_array_dim
92+
from fooof.core.funcs import gaussian_function, get_ap_func, infer_ap_func
93+
from fooof.core.errors import (FitError, NoModelError, DataError,
94+
NoDataError, InconsistentDataError)
95+
from fooof.core.strings import (gen_settings_str, gen_results_fm_str,
96+
gen_issue_str, gen_width_warning_str)
97+
98+
from fooof.plts.fm import plot_fm
99+
from fooof.utils.data import trim_spectrum
100+
from fooof.utils.params import compute_gauss_std
101+
from fooof.data import FOOOFSettings, FOOOFRunModes, FOOOFMetaData, FOOOFResults
102+
from fooof.data.conversions import model_to_dataframe
103+
from fooof.sim.gen import gen_freqs, gen_aperiodic, gen_periodic, gen_model
104+
>>>>>>> main:fooof/objs/fit.py
83105

84106
###################################################################################################
85107
###################################################################################################
@@ -596,6 +618,95 @@ def get_meta_data(self):
596618
for key in OBJ_DESC['meta_data']})
597619

598620

621+
def get_data(self, component='full', space='log'):
622+
"""Get a data component.
623+
624+
Parameters
625+
----------
626+
component : {'full', 'aperiodic', 'peak'}
627+
Which data component to return.
628+
'full' - full power spectrum
629+
'aperiodic' - isolated aperiodic data component
630+
'peak' - isolated peak data component
631+
space : {'log', 'linear'}
632+
Which space to return the data component in.
633+
'log' - returns in log10 space.
634+
'linear' - returns in linear space.
635+
636+
Returns
637+
-------
638+
output : 1d array
639+
Specified data component, in specified spacing.
640+
641+
Notes
642+
-----
643+
The 'space' parameter doesn't just define the spacing of the data component
644+
values, but rather defines the space of the additive data definition such that
645+
`power_spectrum = aperiodic_component + peak_component`.
646+
With space set as 'log', this combination holds in log space.
647+
With space set as 'linear', this combination holds in linear space.
648+
"""
649+
650+
assert space in ['linear', 'log'], "Input for 'space' invalid."
651+
652+
if component == 'full':
653+
output = self.power_spectrum if space == 'log' else unlog(self.power_spectrum)
654+
elif component == 'aperiodic':
655+
output = self._spectrum_peak_rm if space == 'log' else \
656+
unlog(self.power_spectrum) / unlog(self._peak_fit)
657+
elif component == 'peak':
658+
output = self._spectrum_flat if space == 'log' else \
659+
unlog(self.power_spectrum) - unlog(self._ap_fit)
660+
else:
661+
raise ValueError('Input for component invalid.')
662+
663+
return output
664+
665+
666+
def get_model(self, component='full', space='log'):
667+
"""Get a model component.
668+
669+
Parameters
670+
----------
671+
component : {'full', 'aperiodic', 'peak'}
672+
Which model component to return.
673+
'full' - full model
674+
'aperiodic' - isolated aperiodic model component
675+
'peak' - isolated peak model component
676+
space : {'log', 'linear'}
677+
Which space to return the model component in.
678+
'log' - returns in log10 space.
679+
'linear' - returns in linear space.
680+
681+
Returns
682+
-------
683+
output : 1d array
684+
Specified model component, in specified spacing.
685+
686+
Notes
687+
-----
688+
The 'space' parameter doesn't just define the spacing of the model component
689+
values, but rather defines the space of the additive model such that
690+
`model = aperiodic_component + peak_component`.
691+
With space set as 'log', this combination holds in log space.
692+
With space set as 'linear', this combination holds in linear space.
693+
"""
694+
695+
assert space in ['linear', 'log'], "Input for 'space' invalid."
696+
697+
if component == 'full':
698+
output = self.fooofed_spectrum_ if space == 'log' else unlog(self.fooofed_spectrum_)
699+
elif component == 'aperiodic':
700+
output = self._ap_fit if space == 'log' else unlog(self._ap_fit)
701+
elif component == 'peak':
702+
output = self._peak_fit if space == 'log' else \
703+
unlog(self.fooofed_spectrum_) - unlog(self._ap_fit)
704+
else:
705+
raise ValueError('Input for component invalid.')
706+
707+
return output
708+
709+
599710
def get_params(self, name, col=None):
600711
"""Return model fit parameters for specified feature(s).
601712

specparam/tests/core/test_utils.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,13 @@
1212
###################################################################################################
1313
###################################################################################################
1414

15+
def test_unlog():
16+
17+
orig = np.array([1, 2, 3, 4])
18+
logged = np.log10(orig)
19+
unlogged = unlog(logged)
20+
assert np.array_equal(orig, unlogged)
21+
1522
def test_group_three():
1623

1724
dat = [0, 1, 2, 3, 4, 5]

specparam/tests/objs/test_fit.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,17 @@ def test_obj_gets(tfm):
311311
results = tfm.get_results()
312312
assert isinstance(results, FitResults)
313313

314+
def test_get_components(tfm):
315+
316+
# Make sure test object has been fit
317+
tfm.fit()
318+
319+
# Test get data & model components
320+
for comp in ['full', 'aperiodic', 'peak']:
321+
for space in ['log', 'linear']:
322+
assert isinstance(tfm.get_data(comp, space), np.ndarray)
323+
assert isinstance(tfm.get_model(comp, space), np.ndarray)
324+
314325
def test_get_params(tfm):
315326
"""Test the get_params method."""
316327

0 commit comments

Comments
 (0)