Aperiodic Activity

Changes in aperiodic activity can influence measures of oscillations.

Issue

Neural activity contains both periodic and aperiodic activity. Aperiodic neural activity is both ubiquitously present, and also dynamic, which can be a confounding factor for many analyses. Measured changes at a particular frequency range could reflect a change or either periodic or aperiodic activity, and cannot be assumed to necessarily reflect changes in periodic activity.

Solution

Both periodic and aperiodic activity can be explicitly measured and compared, allowing for careful adjudication of which aspect of the data is changing.

Settings

import seaborn as sns
sns.set_context('poster')
# Set random seed
set_random_seed(808)
# Define general simulation settings
n_seconds = 50
fs = 1000
times = create_times(n_seconds, fs)
# Define parameters for the simulations
cf = 10
exp = -1.0
ap_filt = (1.5, 100)
# Define the components of the combined signal
comps = {'sim_powerlaw' : {'exponent' : exp, 'f_range' : ap_filt},
         'sim_oscillation' : {'freq' : cf}}

# Define relative power of the signal components
comp_vars = [1, 0.6]
# Analysis settings
nperseg = 1. * fs
# Initialize spectral parameterization object
fm = FOOOF(verbose=False)
# Set range for power spectra
psd_range = [3, 50]
# Plot settings
plt_kwargs = {'alpha' : [0.6, 0.6], 'xlabel' : '', 'ylabel' : ''}
colors = ['black', 'red']
shade_colors = [BAND_COLORS[band] for band in BANDS.labels]
# Set whether to save out figures
SAVE_FIG = False

Simulate Aperiodic Signals

First, we will simulate some examples of purely aperiodic signals, with no periodic activity.

For these example signals, we will simulate ‘colored noise’, including white noise and pink noise.

# Simulate white and pink noise aperiodic time series
sig_wn = sim_powerlaw(n_seconds, fs, exponent=-0.)
sig_pn = sim_powerlaw(n_seconds, fs, exponent=-1.)
# Plot white noise time series
_, ax = plt.subplots(figsize=(8, 3))
plot_time_series(times, sig_wn, xlim=[4, 5], colors='black', alpha=0.8, lw=1.5, ax=ax)
plt.xticks([]); plt.yticks([]); plt.axis('off');
savefig(SAVE_FIG, '03-ts_white_noise')
../_images/03-AperiodicActivity_16_0.png
# Plot pink noise time series
_, ax = plt.subplots(figsize=(8, 3))
plot_time_series(times, sig_pn, xlim=[4, 5], colors='red', alpha=0.8, lw=1.5, ax=ax)
plt.xticks([]); plt.yticks([]); plt.axis('off');
savefig(SAVE_FIG, '03-ts_pink_noise')
../_images/03-AperiodicActivity_17_0.png
# Plot overlapping time series of the white and pink noise, to compare
_, ax = plt.subplots(figsize=(8, 3))
plot_time_series(times, [sig_wn, sig_pn], xlim=[4, 8], **plt_kwargs, ax=ax)
plt.xticks([]); plt.yticks([]);
savefig(SAVE_FIG, '03-ts_noise')
../_images/03-AperiodicActivity_18_0.png

Examine Power Spectra & Filtered Traces

Using these aperiodic signals, lets examine their power spectra and some filtered traces.

# Compute power spectra for the aperiodic time series
freqs, powers_wn = trim_spectrum(*compute_spectrum(sig_wn, fs, nperseg=nperseg), psd_range)
freqs, powers_pn = trim_spectrum(*compute_spectrum(sig_pn, fs, nperseg=nperseg), psd_range)
# Plot power spectra of aperiodic signals
plot_spectra_shading(freqs, [powers_wn, powers_pn], ALPHA_RANGE,
                     log_powers=True, lw=4, shade_colors=ALPHA_COLOR)
style_psd(plt.gca(), line_colors=colors, line_alpha=0.8)
savefig(SAVE_FIG, '03-psd_noise')
../_images/03-AperiodicActivity_21_0.png

As we can see in the above, aperiodic signals have power across all frequencies, though without any prominent peaks.

# Filter aperiodic signals to alpha range
sig_filt_wn = filter_signal(sig_wn, fs, 'bandpass', ALPHA_RANGE)
sig_filt_pn = filter_signal(sig_pn, fs, 'bandpass', ALPHA_RANGE)
# Plot alpha filtered aperiodic signals
_, ax = plt.subplots(figsize=(8, 3))
plot_time_series(times, [sig_filt_wn, sig_filt_pn], 
                 xlim=[4, 8], **plt_kwargs, ax=ax)
plt.xticks([]); plt.yticks([]);
savefig(SAVE_FIG, '03-ts_noise_filt')
../_images/03-AperiodicActivity_24_0.png

In the filtered traces above, we can see that narrowband filtered traces look to be rhythmic.

Additionally, the amount of rhythmic power varies based on the character of the simulated aperiodic signal.

Simulate signals with oscillations

Next, let’s examine combined signals, simulated with both aperiodic and periodic activity.

# Simulate a combined signal
sig = sim_combined(n_seconds, fs, comps, comp_vars)
# Define settings for rotating the signal
exp_rot = -0.35
f_rotation = 25
# Rotate signal
sig_rot = rotate_sig(sig, fs, exp_rot, f_rotation)
# Plot original signal
_, ax = plt.subplots(figsize=(8, 3))
plot_time_series(times, sig, xlim=[4, 7], colors='black', alpha=0.8, lw=1.5, ax=ax)
plt.axis('off');
savefig(SAVE_FIG, '03-ts_signal')
../_images/03-AperiodicActivity_30_0.png
# Plot rotated signal
_, ax = plt.subplots(figsize=(8, 3))
plot_time_series(times, sig_rot, xlim=[4, 7], colors='red', alpha=0.8, lw=1.5, ax=ax)
plt.axis('off');
savefig(SAVE_FIG, '03-ts_signal_rot')
../_images/03-AperiodicActivity_31_0.png
# Plot both signals overlaid
_, ax = plt.subplots(figsize=(8, 3))
plot_time_series(times, [sig, sig_rot], xlim=[1, 4], **plt_kwargs, ax=ax)
plt.xticks([]); plt.yticks([]);
savefig(SAVE_FIG, '03-ts_full')
../_images/03-AperiodicActivity_32_0.png
# Filter signals to the alpha range
sig_filt = filter_signal(sig, fs, 'bandpass', ALPHA_RANGE)
sig_rot_filt = filter_signal(sig_rot, fs, 'bandpass', ALPHA_RANGE)
# Plot filtered signals
_, ax = plt.subplots(figsize=(4, 2))
plot_time_series(times, [sig_filt, sig_rot_filt],
                 xlim=[1, 4], ylim=[-2.15, 2.15],
                 **plt_kwargs, ax=ax)
plt.xticks([]); plt.yticks([]);
savefig(SAVE_FIG, '03-ts_filt')
../_images/03-AperiodicActivity_34_0.png

Compare power spectra

# Compute power spectra 
freqs1, powers1 = trim_spectrum(*compute_spectrum(sig, fs, nperseg=nperseg), psd_range)
freqs2, powers2 = trim_spectrum(*compute_spectrum(sig_rot, fs, nperseg=nperseg), psd_range)
# Plot power spectra
plot_spectra_shading(freqs1, [powers1, powers2], [8, 12],
                     log_freqs=False, log_powers=True,
                     lw=4, shade_colors=ALPHA_COLOR)
style_psd(plt.gca(), line_colors=colors, line_alpha=0.8)
savefig(SAVE_FIG, '03-psd_osc')
../_images/03-AperiodicActivity_37_0.png

Band-by-Band Comparisons

In the next example, we will compare the results of a band-by-band analysis.

To do so, we will compare data in which the actual change is a change in the aperiodic component of the data (a spectral rotation).

# Define settings for rotating the signal
exp_rot = -0.5
f_rotation = 10
# Simulate a new combined signal
sig1 = sim_combined(n_seconds, fs, comps, comp_vars)
# Create the second signal as a spectrally rotated version of the first
sig2 = rotate_sig(sig1, fs, exp_rot, f_rotation)
# Calculate power spectra
freqs1_pl, powers1_pl = trim_spectrum(*compute_spectrum(sig1, fs, nperseg=nperseg), [2, 50])
freqs2_pl, powers2_pl = trim_spectrum(*compute_spectrum(sig2, fs, nperseg=nperseg), [2, 50])
# Plot power spectra of the two signals
plot_spectra_shading(freqs1_pl, [powers1_pl, powers2_pl], 
                     BANDS.definitions, shade_colors,
                     lw=4, log_powers=True)
style_psd(plt.gca(), line_colors=COND_COLORS, line_alpha=0.8)
plt.xlim([2, 50])
savefig(SAVE_FIG, '03-psd_bands_rotation')
../_images/03-AperiodicActivity_43_0.png
# Compute power spectra for each spectrum
freqs1, powers1 = trim_spectrum(*compute_spectrum(sig1, fs), [2, 80])
freqs2, powers2 = trim_spectrum(*compute_spectrum(sig2, fs), [2, 80])
# Compute the measured differences in band power between spectra
deltas = {}
for label, f_range in BANDS:
    deltas[label] = compute_abs_power(freqs1, powers1, f_range) - \
        compute_abs_power(freqs2, powers2, f_range)
# Plot measured differences in power across bands
plot_band_changes(deltas, colors=shade_colors, ylim=[-0.42, 0.2])
savefig(SAVE_FIG, '03-bands_changes')
../_images/03-AperiodicActivity_46_0.png

Relative Power Comparisons

Next, we will examine a relative power analysis on the same data from above.

# Define 'full range' of the PSD to use for power measures
full_range = [2, 50]
# Compute absolute power of alpha
abs1 = compute_abs_power(freqs1, powers1, ALPHA_RANGE)
abs2 = compute_abs_power(freqs2, powers2, ALPHA_RANGE)
# Print out absolute alpha measures
print('Absolute alpha - sig1 : {:8.4f}'.format(abs1))
print('Absolute alpha - sig2 : {:8.4f}'.format(abs2))
Absolute alpha - sig1 :   0.4598
Absolute alpha - sig2 :   0.4640
# Plot the absolute power of alpha
plot_bar(abs1, abs2, color=COND_COLORS, alpha=0.85)
savefig(SAVE_FIG, '03-pow_abs_alpha')
../_images/03-AperiodicActivity_51_0.png
# Compute total power for each signal
tot1 = compute_abs_power(freqs1, powers1, full_range)
tot2 = compute_abs_power(freqs2, powers2, full_range)
# Print out total power measures
print('Total power - sig1 : {:8.4f}'.format(tot1))
print('Total power - sig2 : {:8.4f}'.format(tot2))
Total power - sig1 :   0.9017
Total power - sig2 :   1.2030
# Plot the total power
plot_bar(tot1, tot2, color=COND_COLORS, alpha=0.85)
savefig(SAVE_FIG, '03-pow_tot')
../_images/03-AperiodicActivity_54_0.png
# Compute relative power of alpha for each signal
rel1 = compute_rel_power(freqs1, powers1, ALPHA_RANGE, norm_range=full_range)
rel2 = compute_rel_power(freqs2, powers2, ALPHA_RANGE, norm_range=full_range)
# Print out relative alpha measures
print('Relative alpha - sig1 : {:8.4f}'.format(rel1))
print('Relative alpha - sig2 : {:8.4f}'.format(rel2))
Relative alpha - sig1 :  50.9946
Relative alpha - sig2 :  38.5705
# Plot the relative power of alpha
plot_bar(rel1, rel2, color=COND_COLORS, alpha=0.85)
savefig(SAVE_FIG, '03-pow_rel_alphs')
../_images/03-AperiodicActivity_57_0.png

Spectral Parameterization

As we’ve seen above, band-by-band or relative power analyses can give confounded results when there are aperiodic changes in the data.

An alternative analysis approach, that specifically characterizes periodic and aperiodic changes in the data is spectral parameterization.

# Initialize model objects for spectral parameterization
fm1 = FOOOF(verbose=False)
fm2 = FOOOF(verbose=False)

# Parameterize the spectra for each signal
fm1.fit(freqs1_pl, powers1_pl, psd_range)
fm2.fit(freqs2_pl, powers2_pl, psd_range)
# Plot the power spectra with the model results for each signal
_, axes = plt.subplots(1, 2, figsize=(14, 6))
fm1.plot(ax=axes[0])
fm2.plot(ax=axes[1])
plt.tight_layout()
../_images/03-AperiodicActivity_60_0.png
# Get the measured alpha parameters
alpha1 = get_band_peak_fm(fm1, ALPHA_RANGE)
alpha2 = get_band_peak_fm(fm2, ALPHA_RANGE)
# Compare the measured alpha peaks between signals
print('Alpha peak - signal 1:  CF: {:4.2f},  PW: {:4.2f},  BW: {:4.2f}'.format(*alpha1))
print('Alpha peak - signal 2:  CF: {:4.2f},  PW: {:4.2f},  BW: {:4.2f}'.format(*alpha2))
Alpha peak - signal 1:  CF: 10.06,  PW: 1.26,  BW: 1.73
Alpha peak - signal 2:  CF: 10.10,  PW: 1.26,  BW: 1.71
# Get the measured aperiodic exponent parameters
exp1 = fm1.get_params('aperiodic', 'exponent')
exp2 = fm2.get_params('aperiodic', 'exponent')
# Compare the measured aperiodic exponent between signals
print('Aperiodic exponent - signal 1: {:8.4f}'.format(exp1))
print('Aperiodic exponent - signal 2: {:8.4f}'.format(exp2))
Aperiodic exponent - signal 1:   0.9756
Aperiodic exponent - signal 2:   2.0171

With spectral parameterization, we are able to see that the change in the data is in the aperiodic exponent, and that there is no change in periodic activity.

More information on spectral parameterization is available here.

Conclusion

Neural recordings contains aperiodic activity, which is itself dynamic.

Analysis of neural data needs to consider variability of aperiodic activity, and employ robust measures that reflect when changes are in periodic and/or aperiodic activity.