Skip to content

Commit f70a12f

Browse files
authored
Merge pull request #199 from fooof-tools/shade
[ENH] Group shading
2 parents 9b2e589 + 849d9db commit f70a12f

File tree

3 files changed

+116
-0
lines changed

3 files changed

+116
-0
lines changed

doc/api.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,7 @@ Plots for plotting power spectra with shaded regions.
257257
:toctree: generated/
258258

259259
plot_spectra_shading
260+
plot_spectra_yshade
260261

261262
Plot Model Properties & Parameters
262263
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

fooof/plts/spectra.py

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,11 @@
55
This file contains functions for plotting power spectra, that take in data directly.
66
"""
77

8+
from inspect import isfunction
89
from itertools import repeat, cycle
910

1011
import numpy as np
12+
from scipy.stats import sem
1113

1214
from fooof.core.modutils import safe_import, check_dependency
1315
from fooof.plts.settings import PLT_FIGSIZES
@@ -113,3 +115,80 @@ def plot_spectra_shading(freqs, power_spectra, shades, shade_colors='r',
113115

114116
style_spectrum_plot(ax, plot_kwargs.get('log_freqs', False),
115117
plot_kwargs.get('log_powers', False))
118+
119+
120+
@savefig
121+
@style_plot
122+
@check_dependency(plt, 'matplotlib')
123+
def plot_spectra_yshade(freqs, power_spectra, shade='std', average='mean', scale=1,
124+
log_freqs=False, log_powers=False, color=None, label=None,
125+
ax=None, **plot_kwargs):
126+
"""Plot standard deviation or error as a shaded region around the mean spectrum.
127+
128+
Parameters
129+
----------
130+
freqs : 1d array
131+
Frequency values, to be plotted on the x-axis.
132+
power_spectra : 1d or 2d array
133+
Power values, to be plotted on the y-axis. ``shade`` must be provided if 1d.
134+
shade : 'std', 'sem', 1d array or callable, optional, default: 'std'
135+
Approach for shading above/below the mean spectrum.
136+
average : 'mean', 'median' or callable, optional, default: 'mean'
137+
Averaging approach for the average spectrum to plot. Only used if power_spectra is 2d.
138+
scale : int, optional, default: 1
139+
Factor to multiply the plotted shade by.
140+
log_freqs : bool, optional, default: False
141+
Whether to plot the frequency axis in log spacing.
142+
log_powers : bool, optional, default: False
143+
Whether to plot the power axis in log spacing.
144+
color : str, optional, default: None
145+
Line color of the spectrum.
146+
label : str, optional, default: None
147+
Legend label for the spectrum.
148+
ax : matplotlib.Axes, optional
149+
Figure axes upon which to plot.
150+
**plot_kwargs
151+
Keyword arguments to be passed to `plot_spectra` or to the plot call.
152+
"""
153+
154+
if (isinstance(shade, str) or isfunction(shade)) and power_spectra.ndim != 2:
155+
raise ValueError('Power spectra must be 2d if shade is not given.')
156+
157+
ax = check_ax(ax, plot_kwargs.pop('figsize', PLT_FIGSIZES['spectral']))
158+
159+
# Set plot data & labels, logging if requested
160+
plt_freqs = np.log10(freqs) if log_freqs else freqs
161+
plt_powers = np.log10(power_spectra) if log_powers else power_spectra
162+
163+
# Organize mean spectrum to plot
164+
avg_funcs = {'mean' : np.mean, 'median' : np.median}
165+
166+
if isinstance(average, str) and plt_powers.ndim == 2:
167+
avg_powers = avg_funcs[average](plt_powers, axis=0)
168+
elif isfunction(average) and plt_powers.ndim == 2:
169+
avg_powers = average(plt_powers)
170+
else:
171+
avg_powers = plt_powers
172+
173+
# Plot average power spectrum
174+
ax.plot(plt_freqs, avg_powers, linewidth=2.0, color=color, label=label)
175+
176+
# Organize shading to plot
177+
shade_funcs = {'std' : np.std, 'sem' : sem}
178+
179+
if isinstance(shade, str):
180+
shade_vals = scale * shade_funcs[shade](plt_powers, axis=0)
181+
elif isfunction(shade):
182+
shade_vals = scale * shade(plt_powers)
183+
else:
184+
shade_vals = scale * shade
185+
186+
upper_shade = avg_powers + shade_vals
187+
lower_shade = avg_powers - shade_vals
188+
189+
# Plot +/- yshading around spectrum
190+
alpha = plot_kwargs.pop('alpha', 0.25)
191+
ax.fill_between(plt_freqs, lower_shade, upper_shade,
192+
alpha=alpha, color=color, **plot_kwargs)
193+
194+
style_spectrum_plot(ax, log_freqs, log_powers)

fooof/tests/plts/test_spectra.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
"""Tests for fooof.plts.spectra."""
22

3+
from pytest import raises
4+
35
import numpy as np
46

57
from fooof.tests.tutils import plot_test
@@ -51,3 +53,37 @@ def test_plot_spectra_shading(tfm, tfg, skip_if_no_mpl):
5153
shades=[8, 12], add_center=True, log_freqs=True, log_powers=True,
5254
labels=['A', 'B'], save_fig=True, file_path=TEST_PLOTS_PATH,
5355
file_name='test_plot_spectra_shading_kwargs.png')
56+
57+
@plot_test
58+
def test_plot_spectra_yshade(skip_if_no_mpl, tfg):
59+
60+
freqs = tfg.freqs
61+
powers = tfg.power_spectra
62+
63+
# Invalid 1d array, without shade
64+
with raises(ValueError):
65+
plot_spectra_yshade(freqs, powers[0])
66+
67+
# Plot with 2d array
68+
plot_spectra_yshade(freqs, powers, shade='std',
69+
save_fig=True, file_path=TEST_PLOTS_PATH,
70+
file_name='test_plot_spectra_yshade1.png')
71+
72+
# Plot shade with given 1d array
73+
plot_spectra_yshade(freqs, np.mean(powers, axis=0),
74+
shade=np.std(powers, axis=0),
75+
save_fig=True, file_path=TEST_PLOTS_PATH,
76+
file_name='test_plot_spectra_yshade2.png')
77+
78+
# Plot shade with different average and shade approaches
79+
plot_spectra_yshade(freqs, powers, shade='sem', average='median',
80+
save_fig=True, file_path=TEST_PLOTS_PATH,
81+
file_name='test_plot_spectra_yshade3.png')
82+
83+
# Plot shade with custom average and shade callables
84+
def _average_callable(powers): return np.mean(powers, axis=0)
85+
def _shade_callable(powers): return np.std(powers, axis=0)
86+
87+
plot_spectra_yshade(freqs, powers, shade=_shade_callable, average=_average_callable,
88+
log_powers=True, save_fig=True, file_path=TEST_PLOTS_PATH,
89+
file_name='test_plot_spectra_yshade4.png')

0 commit comments

Comments
 (0)