Statistical Significance of Power Spectra

Statistical Significance of Power Spectra

Authors
Dr. Atle Rimehaug | Dr. Nicholas Del Grosso

A power spectrum may contain many peaks, but it’s hard to tell which ones are from noise and which are from a real signal. We can use statistical tests on the power spectra to figure out which peaks are unlikely to be just noise.

Setup

Import Libraries

import matplotlib.pyplot as plt
from scipy.stats import chi2
import scipy.optimize as opt
import numpy as np
import xarray as xr
from pathlib import Path
import pandas as pd
import pingouin as pg
from scipy import signal

Utility Functions

Run the cell below, but feel free to ignore this code — it’s some utility functions we’ll use later for convenience.

def plot_signal_and_power_spectrum(time_vec, sig, freqs, psd):
    '''
    Makes figure with two subplots where the signal is plotted in one subplot and the power spectrum in the other

    Args:
        time_vec: array containing time points over which the signal is sampled
        sig: array containing signal
        freqs: frequencies for which the power spectral density is calculated
        psd: power spectral density of the signal
    '''
    fig, ax = plt.subplots(ncols=2, figsize=(7,4))

    ax[0].plot(time_vec, sig, label = 'Signal')
    ax[0].set(xlabel = 'Time (s)', ylabel = 'Amplitude', title = 'Signal')

    ax[1].loglog(freqs, psd, label = 'Power Spectrum')
    ax[1].set(xlabel = 'Frequency (Hz)', ylabel = 'Power spectral density (normalized)', title = 'Power spectrum')
    fig.tight_layout()

def red_noise_spectrum(freqs, A, beta):
    '''
    Estimates the power spectrum values as a function of frequency for a red noise signal

    Args:
        freqs: frequencies for which the power spectrum should be estimated
        A: Amplitude of power spectrum
        beta: Shape parameter for the function
    returns:
        spectrum: red noise power spectrum
    '''

    spectrum = A*(1.-beta**2)/(1.-2.*beta*np.cos(np.pi*(freqs)/freqs[-1])+beta**2)
    return spectrum

def get_significance_threshold_psd(function, freqs, psd_original, n_samples, n_perseg, alpha=0.05,):
    '''
    Gets threshold for statistical significance for a power spectrum given a certain p-value

    Args:
        function: function to fit to create equivalent spectrum for a signal only consisting of noise
        freqs: frequencies for which the power spectrum should has been estimated
        psd_original: power spectrum computed for signal
        n_samples: length of signal the power spectrum was calculated for
        n_perseg: number of points per segment in Welch's method
        alpha: p-value threshold for statistical significance
    Return:
        psd_expected: equivalent power spectrum if signal only contains noise (noise of the same colour as the original signal)
        psd_significance_threshold: threshold for power spectrum peak to be significant
    '''
    # Fit function to red noise equivalent spectrum(excluding very low frequencies)
    valid_idx = freqs > 0
    popt, _ = opt.curve_fit(function, freqs[valid_idx], psd_original[valid_idx])

    # Compute noise spectrum equivalent
    psd_expected = function(freqs, *popt)

    # calculate degrees of freedom (dof). dof = 2*number_of_samples/number_of_spectral_estimates = 2*len(your_signal)/n_perseg
    dof = 2*n_samples/n_perseg

    # Get Chi squared value for the threshold p-value
    chi_sq_threshold = chi2.ppf(1-alpha, dof)

    # Calculate the psd value at the threshold p-value
    psd_significance_threshold = psd_expected*chi_sq_threshold/(dof-1)

    return psd_expected, psd_significance_threshold

def get_p_value(idx_frequencies, psd_original, psd_expected, n_samples, n_perseg):
    """Calculates the p-values of the peaks at the selected frequencies"""

    # calculate degrees of freedom (dof)
    dof = 2*n_samples/n_perseg

    chi2_values = (dof-1)*psd_original[idx_frequencies]/psd_expected[idx_frequencies]

    # Compute p-values from chi-square CDF
    p_values = 1 - chi2.cdf(chi2_values, df=dof)

    return p_values

def plot_spectra_with_significance_threshold(freqs, psd_original, psd_expected, n_samples, n_perseg):
    '''
    Makes loglog plot of power spectrum together with red noise equivalant and thershold for significance. 
    Used to visualize which peaks are above statistically significant in a spectrum with a 1/f relationship

    Args:
        psd_original: power spectrum computed for signal
        psd_expected: equivalent power spectrum if signal only contains noise (noise of the same colour as the original signal)
        psd_significance_threshold: threshold for power spectrum peak to be significant
    '''
    # calculate degrees of freedom (dof)
    dof = 2*n_samples/n_perseg

    # Get Chi squared value for the threshold p-value
    chi_sq_threshold95 = chi2.ppf(0.95, dof)
    chi_sq_threshold99 = chi2.ppf(0.99, dof)

    # Calculate the psd value at the threshold p-value
    psd_significance_threshold95 = psd_expected*chi_sq_threshold95/(dof-1)
    psd_significance_threshold99 = psd_expected*chi_sq_threshold99/(dof-1)

    fig, ax = plt.subplots()

    ax.loglog(freqs, psd_original, label = 'Original PSD')
    ax.loglog(freqs, psd_expected, '--', color = 'r', label = 'Red noise PSD')
    ax.loglog(freqs, psd_significance_threshold95, '--', color = 'g', label = f'95 % threshold')
    ax.loglog(freqs, psd_significance_threshold99, '--', color = 'black', label = f'99 % threshold')
    ax.set(xlabel = 'Frequency (Hz)', ylabel = 'Power spectral density (normalized)', title = 'Power spectrum of signal with\nthreshold for significance')
    fig.legend(bbox_to_anchor = (0.9, 0.9))

class utils:
    plot_signal_and_power_spectrum = plot_signal_and_power_spectrum
    red_noise_spectrum = red_noise_spectrum
    get_significance_threshold_psd = get_significance_threshold_psd
    get_p_value = get_p_value
    plot_spectra_with_significance_threshold = plot_spectra_with_significance_threshold

Download dataset

import owncloud

owncloud.Client.from_public_link('https://uni-bonn.sciebo.de/s/rY4YCPXmkpespKG').get_file('/', 'dataset_session_754312389.nc')
True

Section 1: Primer on statistical testing

T-tests can be used to compare the means of two samples of data from normally-distributed populations and compute the probability that the populations have the same mean.

Code Statistical Test Description
pg.ttest(x, 0) One-Sample T-Test “Is the mean of my data unlikely to be zero?”
pg.ttest(x, y) Independent-Samples T-Test “Do the means of x and y indicate they are unlikely to be from the same distribution?”
expected, observed, stats = pg.chi2_independence(data, x='col_name_A', y='col_name_B') χ2\chi^2 test Is x a predictor of y? expected gives expected values if x is NOT a predictor of y, observed gives actual observed values of y for different values of x, stats gives statistical results of χ2\chi^2 test given these expected and observed values.

Exercises

Generate Data: Run the code below to create the dataset height containing fictional data on the height of a sample of 30 Germans, 30 Italians and 30 Dutch people.

randomizer = np.random.RandomState(17)  # Makes sure the pseudorandom number generators reproduce the same data for us all.
height = pd.DataFrame()
height['german'] = randomizer.normal(0, 15, size=30)+170
height['dutch'] = randomizer.normal(0, 15, size=30)+180
height['italian'] = randomizer.normal(0, 15, size=30)+175
height.plot.box()

Analyze the Data with T-Tests in Pingouin

Example: Independent Samples T-Test: Are the heights recorded for the Germans and the Dutch statistically different with p = .05 as the threshold for statistical significance?

pg.ttest(height['german'], height['dutch'])

T dof alternative p-val CI95% cohen-d BF10 power
T-test -2.86907 58 two-sided 0.005734 [-19.52, -3.48] 0.740791 7.346 0.805465

Exercise: Independent Samples T-Test: Are the heights recorded for the Italians and the Dutch statistically different?

Solution
pg.ttest(height['italian'], height['dutch']) # no

T dof alternative p-val CI95% cohen-d BF10 power
T-test -1.116856 58 two-sided 0.268661 [-13.82, 3.92] 0.288371 0.443 0.195583

Exercise: Independent Samples T-Test: Are the heights recorded for the Germans and the Italians statistically different?

Solution
pg.ttest(height['italian'], height['german']) # no

T dof alternative p-val CI95% cohen-d BF10 power
T-test 1.469208 58 two-sided 0.14718 [-2.37, 15.48] 0.379348 0.646 0.30359

Generate Data: Run the code below to create the dataset treatment containing data from a fictional drug trial study. We’ll use this data in the next couple of exercises.

randomizer = np.random.RandomState(15)
treatment = pd.DataFrame()
treatment['drugA'] = randomizer.normal(0, 15, size=30)+10
treatment['drugB'] = randomizer.normal(0, 15, size=30)+1
treatment['control'] = randomizer.normal(0, 15, size=30)
treatment.plot.box()

Exercise: Perform a t-test to determine whether the treatment effect of drug A is statistically significant by comparing it to the control group.

Solution
pg.ttest(treatment['drugA'], treatment['control'])

T dof alternative p-val CI95% cohen-d BF10 power
T-test 2.221458 58 two-sided 0.030237 [0.85, 16.32] 0.573578 2.0 0.588876

Exercise: Perform a t-test to determine whether the treatment effect of drug B is statistically significant by comparing it to the control group.

Solution
pg.ttest(treatment['drugB'], treatment['control'])

T dof alternative p-val CI95% cohen-d BF10 power
T-test 0.230379 58 two-sided 0.818608 [-7.57, 9.54] 0.059484 0.268 0.055902

Dataset: The Passengers on the Titanic

Below, we load a dataset containing (semi-fictitious) information about the passenger onboard the Titanic. Every row is a passenger, every column is a variable about that passenger. Please run the code and take a look at the dataset.

titanic = pd.read_csv('https://raw.githubusercontent.com/mwaskom/seaborn-data/refs/heads/master/titanic.csv')
titanic

survived pclass sex age sibsp parch fare embarked class who adult_male deck embark_town alive alone
0 0 3 male 22.0 1 0 7.2500 S Third man True NaN Southampton no False
1 1 1 female 38.0 1 0 71.2833 C First woman False C Cherbourg yes False
2 1 3 female 26.0 0 0 7.9250 S Third woman False NaN Southampton yes True
3 1 1 female 35.0 1 0 53.1000 S First woman False C Southampton yes False
4 0 3 male 35.0 0 0 8.0500 S Third man True NaN Southampton no True
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
886 0 2 male 27.0 0 0 13.0000 S Second man True NaN Southampton no True
887 1 1 female 19.0 0 0 30.0000 S First woman False B Southampton yes True
888 0 3 female NaN 1 2 23.4500 S Third woman False NaN Southampton no False
889 1 1 male 26.0 0 0 30.0000 C First man True C Cherbourg yes True
890 0 3 male 32.0 0 0 7.7500 Q Third man True NaN Queenstown no True

891 rows × 15 columns

A χ2\chi^2 test, like the t-test, can be used to test differences between groups. The difference lies in the use-cases - what kind of data the test is applied to. T-tests are applied when the dependent variable (for example height in the exercises above) is continuous. A χ2\chi^2 test is applied when the dependent variable is categorical. In the exercises below, the dependent variable will be categorical. For example, if a passenger on Titanic survived or did not survive is a categorical outcome - there are no degrees of survival, either they survived or they didn’t. In such cases, a χ2\chi^2 test should be used.

Exercise: Use a χ2\chi^2 independence test to determine whether the chance of survival depended on which class a passenger traveled on. (You don’t need to look at more than the first row of the output in these exercises.)

Solution
expected, observed, stats = pg.chi2_independence(titanic, x='class', y='survived')

stats

test lambda chi2 dof pval cramer power
0 pearson 1.000000 102.888989 2.0 4.549252e-23 0.339817 1.0
1 cressie-read 0.666667 102.741005 2.0 4.898625e-23 0.339573 1.0
2 log-likelihood 0.000000 103.547124 2.0 3.273615e-23 0.340902 1.0
3 freeman-tukey -0.500000 105.128509 2.0 1.484685e-23 0.343496 1.0
4 mod-log-likelihood -1.000000 107.580420 2.0 4.357212e-24 0.347478 1.0
5 neyman -2.000000 115.324285 2.0 9.070888e-26 0.359767 1.0

Exercise: Did the chance of survival depend on sex?

Solution
expected, observed, stats = pg.chi2_independence(titanic, x='sex', y='survived')

stats

test lambda chi2 dof pval cramer power
0 pearson 1.000000 260.717020 1.0 1.197357e-58 0.540936 1.0
1 cressie-read 0.666667 260.413906 1.0 1.394102e-58 0.540621 1.0
2 log-likelihood 0.000000 266.344480 1.0 7.106137e-60 0.546743 1.0
3 freeman-tukey -0.500000 276.922737 1.0 3.517208e-62 0.557494 1.0
4 mod-log-likelihood -1.000000 293.475958 1.0 8.693443e-66 0.573915 1.0
5 neyman -2.000000 349.098487 1.0 6.659745e-78 0.625943 1.0

Exercise: Did the likelihood that a passenger was an adult male depend on which town they embarked from?

Solution
expected, observed, stats = pg.chi2_independence(titanic, x='embark_town', y='adult_male')

stats

test lambda chi2 dof pval cramer power
0 pearson 1.000000 11.053778 2.0 0.003978 0.111382 0.854413
1 cressie-read 0.666667 10.998406 2.0 0.004090 0.111103 0.852555
2 log-likelihood 0.000000 10.910445 2.0 0.004274 0.110658 0.849563
3 freeman-tukey -0.500000 10.863804 2.0 0.004375 0.110421 0.847955
4 mod-log-likelihood -1.000000 10.833262 2.0 0.004442 0.110266 0.846894
5 neyman -2.000000 10.819259 2.0 0.004473 0.110194 0.846406

Demo: How to find the p-value of a χ2\chi^2 test by comparing to the χ2\chi^2 distribution

As for the t-test, the p-value from a χ2\chi^2 test is determined by comparing the χ2\chi^2 score with a distribution of χ2\chi^2 values. This distribution will tell you the probability of observing a certain χ2\chi^2 score in a test. Below is an illustration of how this works.

The green line represents the χ2\chi^2 score obtained from the last test above. The orange line is the cumulative probability density function (CDF) of χ2\chi^2 scores (for two degrees of freedom). The black line from the cross section between the CDF and the observed χ2\chi^2 value tells you the probability of observing this χ2\chi^2 value when traced to the y-axis. This probability is the p-value. As could also be seen from the table with the test result in the last exercise above, the probability is only just above 0.

In the following sections, making comparisons to a χ2\chi^2 distribution is how we determine whether peaks in the frequency spectra are statistically significant or not.

from scipy.stats import chi2

expected, observed, stats = pg.chi2_independence(titanic, x='embark_town', y='adult_male')

x = np.linspace(0,20,100)
plt.plot(x, 1-chi2.cdf(x, 2), color='orange', label = r'Cumulative density function of $\chi^2$ distribution')

idx = np.searchsorted(x, stats['chi2'][0])

plt.axhline(1-chi2.cdf(x, 2)[idx], ls = '--', color = 'black', label = r'Percentile of $\chi^2$ score')

plt.axvline(stats['chi2'][0], ls = '--', color = 'green', label = r'$\chi^2$ score of embark town vs adult male test')

plt.xlabel(r'$\chi^2$')
plt.ylabel(r'Probability of observing a $\chi^2$ score (the p-value)')
plt.title(r'Comparing $\chi^2$-value obtained from test to $\chi^2$-distribution to find p-value')

plt.xlim([0,20])

plt.legend(bbox_to_anchor = (1,0.5));

Section 2: Test the statistical significance of peaks in the power spectrum

It’s not clear just from looking at a power spectrum which peaks are due to noise and which represent an oscillation due to a neurological phenomenon. By comparing the power spectrum calculated for the LFP to the power spectrum computed for an equivalent red noise spectrum, we can better assess which peaks are unlikely to only reflect noise.

Code Description
freqs, psd = signal.welch(sig, fs = sampling_frequency, nperseg = some_num) Calculate the power spectral density (psd) of a signal sig using Welch’s method. nperseg sets the number of samples of the signal in each segment, default value is 256.
psd_expected, psd_significance_threshold =
    utils.get_significance_threshold_psd(
    function, freqs, psd, n_perseg, n_samples, alpha)
Get the threshold for a peak in the power spectrum psd to be significant at significance level alpha. For LFP, a function producing a red noise spectrum would typically be passed for function. psd_expected is the expected spectrum if the signal only contains noise.
utils.plot_spectra_with_significance_threshold(
    freqs, psd_signal, psd_expected,
    psd_significance_threshold, n_perseg,
    n_samples, alpha)
Plot the power spectrum together with the threshold for peaks to be significant at significance level alpha.

Extract LFP data

Run cells below to load dataset and extract relevant data

Exercises

loadpath = 'dataset_session_754312389.nc'

dataset_path = Path(loadpath)
dataset = xr.load_dataset(dataset_path)
dataset
<xarray.Dataset> Size: 225MB
Dimensions:                 (channel_depth: 23, trial_nr: 75, time_lfp: 1875,
time_csd: 1875, time_whole_rec_lfp: 373124,
time_whole_rec_ecp: 373124, unit_id_LGN: 27,
time_spikes: 1500, unit_id_V1: 91, unit_id_LM: 13,
stimulus_start_times: 75, stimulus_stop_times: 75,
channel_id: 23)
Coordinates: (12/13)

  • channel_depth (channel_depth) int64 184B 0 -40 -80 … -840 -880

  • trial_nr (trial_nr) int32 300B 0 1 2 3 4 5 … 70 71 72 73 74

  • time_lfp (time_lfp) float64 15kB -1e+03 -999.2 … 499.2

  • time_csd (time_csd) float64 15kB -1e+03 -999.2 … 499.2

  • time_whole_rec_lfp (time_whole_rec_lfp) float64 3MB 1.286e+06 … 1….

  • time_whole_rec_ecp (time_whole_rec_ecp) float64 3MB 1.286e+06 … 1…. … …

  • time_spikes (time_spikes) float64 12kB -999.5 -998.5 … 499.5

  • unit_id_V1 (unit_id_V1) int32 364B 951795075 … 951798053

  • unit_id_LM (unit_id_LM) int32 52B 951791074 … 951792163

  • stimulus_start_times (stimulus_start_times) float64 600B 1.29e+06 … …

  • stimulus_stop_times (stimulus_stop_times) float64 600B 1.29e+06 … 1…

  • channel_id (channel_id) int32 92B 850144538 … 850144362 Data variables: lfp_V1 (channel_depth, trial_nr, time_lfp) float64 26MB … csd_V1 (channel_depth, trial_nr, time_csd) float64 26MB … lfp_whole_recording_V1 (channel_depth, time_whole_rec_lfp) float64 69MB … ecp_whole_recording_V1 (channel_depth, time_whole_rec_ecp) float64 69MB … spike_counts_LGN (unit_id_LGN, trial_nr, time_spikes) int16 6MB 0 … spike_counts_V1 (unit_id_V1, trial_nr, time_spikes) int16 20MB 0 … spike_counts_LM (unit_id_LM, trial_nr, time_spikes) int16 3MB 0 …. pupil_width (trial_nr) float64 600B 39.12 40.53 … 46.57 44.31 run_speed (trial_nr) float64 600B 1.155 1.597 … 1.251 1.711 Attributes: time_unit: millisecond lfp_unit: Volt channel_depth_unit: micrometer note_channel_depth: Measured in distance from electrode closest t… sampling_frequency_lfp: 1250 sampling_frequency_spikes: 1000 sampling_frequency_unit: Hz stimulus_onset: 1000 stimulus_offset: 1250

xarray.Dataset
    • channel_depth: 23
    • trial_nr: 75
    • time_lfp: 1875
    • time_csd: 1875
    • time_whole_rec_lfp: 373124
    • time_whole_rec_ecp: 373124
    • unit_id_LGN: 27
    • time_spikes: 1500
    • unit_id_V1: 91
    • unit_id_LM: 13
    • stimulus_start_times: 75
    • stimulus_stop_times: 75
    • channel_id: 23
    • channel_depth
      (channel_depth)
      int64
      0 -40 -80 -120 … -800 -840 -880
      array([   0,  -40,  -80, -120, -160, -200, -240, -280, -320, -360, -400, -440,
      -480, -520, -560, -600, -640, -680, -720, -760, -800, -840, -880])
    • trial_nr
      (trial_nr)
      int32
      0 1 2 3 4 5 6 … 69 70 71 72 73 74
      array([ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14, 15, 16, 17,
      18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35,
      36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53,
      54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71,
      72, 73, 74], dtype=int32)
    • time_lfp
      (time_lfp)
      float64
      -1e+03 -999.2 … 498.4 499.2
      array([-1000. ,  -999.2,  -998.4, …,   497.6,   498.4,   499.2])
    • time_csd
      (time_csd)
      float64
      -1e+03 -999.2 … 498.4 499.2
      array([-1000. ,  -999.2,  -998.4, …,   497.6,   498.4,   499.2])
    • time_whole_rec_lfp
      (time_whole_rec_lfp)
      float64
      1.286e+06 1.286e+06 … 1.584e+06
      array([1285609.738645, 1285610.538645, 1285611.338645, …, 1584106.565384,
      1584107.365384, 1584108.165384])
    • time_whole_rec_ecp
      (time_whole_rec_ecp)
      float64
      1.286e+06 1.286e+06 … 1.584e+06
      array([1285609.738645, 1285610.538645, 1285611.338645, …, 1584106.565384,
      1584107.365384, 1584108.165384])
    • unit_id_LGN
      (unit_id_LGN)
      int32
      951782498 951782516 … 951798528
      array([951782498, 951782516, 951782530, 951782631, 951782683, 951782699,
      951782744, 951782795, 951782832, 951782895, 951787033, 951792341,
      951792375, 951792398, 951792418, 951792441, 951792504, 951792544,
      951797077, 951798318, 951798391, 951798404, 951798553, 951798681,
      951801317, 951798434, 951798528], dtype=int32)
    • time_spikes
      (time_spikes)
      float64
      -999.5 -998.5 … 498.5 499.5
      array([-999.5, -998.5, -997.5, …,  497.5,  498.5,  499.5])
    • unit_id_V1
      (unit_id_V1)
      int32
      951795075 951795086 … 951798053
      array([951795075, 951795086, 951795098, 951795140, 951795163, 951795222,
      951795238, 951795256, 951795269, 951795290, 951795309, 951795317,
      951795403, 951797419, 951795458, 951795474, 951795487, 951795495,
      951795554, 951795680, 951795563, 951795611, 951795663, 951795688,
      951795706, 951795713, 951795721, 951797465, 951795729, 951795737,
      951795747, 951795873, 951795753, 951795782, 951795792, 951795760,
      951795768, 951795775, 951795807, 951795824, 951795832, 951795839,
      951795858, 951795880, 951795886, 951795896, 951795918, 951797520,
      951797539, 951797553, 951795936, 951795943, 951795967, 951795976,
      951797567, 951797489, 951796016, 951796073, 951796125, 951796165,
      951796176, 951796229, 951797633, 951796333, 951797647, 951797664,
      951796352, 951796494, 951797718, 951796542, 951796556, 951797730,
      951796585, 951797742, 951796629, 951796639, 951796660, 951796696,
      951796791, 951797776, 951796866, 951796906, 951796928, 951796949,
      951797595, 951797613, 951797709, 951797764, 951797811, 951797995,
      951798053], dtype=int32)
    • unit_id_LM
      (unit_id_LM)
      int32
      951791074 951791093 … 951792163
      array([951791074, 951791093, 951791202, 951791214, 951791273, 951791305,
      951791297, 951791401, 951791422, 951791467, 951791442, 951791453,
      951792163], dtype=int32)
    • stimulus_start_times
      (stimulus_start_times)
      float64
      1.29e+06 1.294e+06 … 1.584e+06
      array([1289613.25125, 1293616.60125, 1299621.62125, 1301623.29125,
      1303624.97125, 1307628.33125, 1309629.95125, 1319638.32125,
      1329646.71125, 1333650.07125, 1337653.40125, 1341656.74125,
      1351665.05125, 1357670.12125, 1361673.43125, 1371681.84125,
      1373683.47125, 1375685.17125, 1383691.83125, 1387695.15125,
      1397703.53125, 1401706.88125, 1403708.58125, 1407711.93125,
      1411715.25125, 1413716.94125, 1415718.63125, 1417720.26125,
      1419721.95125, 1421723.62125, 1423725.25125, 1427728.62125,
      1429730.28125, 1431731.94125, 1433733.62125, 1435735.32125,
      1441740.33125, 1447745.31125, 1451748.67125, 1459755.38125,
      1461757.06125, 1467762.05125, 1473767.09125, 1481773.75125,
      1485777.10125, 1489780.46125, 1491782.12125, 1493783.81125,
      1497787.16125, 1501790.48125, 1503792.14125, 1505793.80125,
      1511798.87125, 1515802.17125, 1517803.89125, 1519805.55125,
      1521807.23125, 1529813.91125, 1531815.57125, 1533817.23125,
      1537820.57125, 1541823.93125, 1543825.60125, 1545827.30125,
      1547828.93125, 1551832.29125, 1553833.96125, 1563842.35125,
      1565843.99125, 1569847.35125, 1571849.02125, 1573850.69125,
      1575852.34125, 1577854.07125, 1583859.05125])
    • stimulus_stop_times
      (stimulus_stop_times)
      float64
      1.29e+06 1.294e+06 … 1.584e+06
      array([1289863.450591, 1293866.800591, 1299871.820591, 1301873.490591,
      1303875.173091, 1307878.525591, 1309880.158091, 1319888.525591,
      1329896.908091, 1333900.265591, 1337903.598091, 1341906.940591,
      1351915.258091, 1357920.320591, 1361923.638091, 1371932.033091,
      1373933.675591, 1375935.370591, 1383942.035591, 1387945.363091,
      1397953.738091, 1401957.083091, 1403958.778091, 1407962.128091,
      1411965.450591, 1413967.138091, 1415968.823091, 1417970.465591,
      1419972.150591, 1421973.823091, 1423975.460591, 1427978.823091,
      1429980.490591, 1431982.153091, 1433983.830591, 1435985.523091,
      1441990.535591, 1447995.523091, 1451998.875591, 1460005.585591,
      1462007.260591, 1468012.260591, 1474017.293091, 1482023.960591,
      1486027.308091, 1490030.665591, 1492032.325591, 1494034.013091,
      1498037.360591, 1502040.685591, 1504042.350591, 1506044.010591,
      1512049.070591, 1516052.383091, 1518054.088091, 1520055.753091,
      1522057.428091, 1530064.110591, 1532065.775591, 1534067.438091,
      1538070.778091, 1542074.135591, 1544075.808091, 1546077.498091,
      1548079.138091, 1552082.495591, 1554084.165591, 1564092.550591,
      1566094.193091, 1570097.553091, 1572099.225591, 1574100.895591,
      1576102.553091, 1578104.268091, 1584109.255591])
    • channel_id
      (channel_id)
      int32
      850144538 850144530 … 850144362
      array([850144538, 850144530, 850144522, 850144514, 850144506, 850144498,
      850144490, 850144482, 850144474, 850144466, 850144458, 850144450,
      850144442, 850144434, 850144426, 850144418, 850144410, 850144402,
      850144394, 850144386, 850144378, 850144370, 850144362], dtype=int32)
    • lfp_V1
      (channel_depth, trial_nr, time_lfp)
      float64
      -8.516e-06 -1.156e-05 … 6.131e-06
      array([[[-8.51569421e-06, -1.15631923e-05, -3.76924291e-07, …,
      -2.17654367e-06,  1.68119712e-07, -6.94200500e-06],
      [ 2.81771330e-08,  6.29403676e-06,  9.98029865e-06, …,
      1.01304291e-05,  5.93295341e-06,  1.72969475e-05],
      [-1.27271427e-06, -3.68783112e-06, -4.46340366e-06, …,
      3.65666426e-06,  2.96670978e-06,  1.24568597e-06],
      …,
      [ 4.70565253e-06,  1.94317598e-06,  1.20455675e-06, …,
      4.07503496e-06,  7.65054254e-06,  3.91673831e-06],
      [ 8.38144661e-07,  2.28123812e-06,  3.70896321e-06, …,
      1.34013793e-05,  1.15462596e-05,  8.42411093e-06],
      [ 7.32479652e-06,  1.07711181e-06,  7.00885884e-06, …,
      1.92065863e-05,  1.92065863e-05,  1.92065863e-05]],

      [[ 3.91952787e-06, 1.70056905e-06, 5.97275690e-06, …, 4.06974274e-06, 4.04094749e-06, -4.68055730e-06], [-4.10311367e-06, 5.00856269e-06, 6.20500275e-06, …, 8.75868311e-06, 5.69604080e-06, 8.57744575e-06], [ 1.09646506e-06, 1.62070262e-06, 1.10953040e-07, …, 1.01990671e-05, -8.36754580e-06, -1.43387447e-05], … [-6.46738552e-06, -1.19701456e-05, -2.41882630e-05, …, -2.31175915e-05, -2.37374262e-05, -7.53926446e-06], [ 2.91850049e-05, 2.81713398e-05, 5.17618757e-05, …, 4.44764295e-05, 4.08778688e-05, 3.83233192e-05], [-1.80642354e-05, -1.06068608e-05, -3.71177368e-06, …, 1.48063260e-05, 1.48063260e-05, 1.48063260e-05]],

      [[ 4.42062699e-05, 5.99208645e-05, 7.60618853e-05, …, 9.85508013e-05, 8.87923195e-05, 8.02998878e-05], [-6.30930699e-06, 2.14882295e-05, 5.40961203e-06, …, -6.18517313e-05, -8.49961217e-05, -7.07025406e-05], [ 1.32347041e-04, 9.66830285e-05, 9.77152893e-05, …, -3.68393584e-05, -2.08663734e-05, -1.27901811e-05], …, [ 7.59530363e-07, -9.00525049e-06, -1.95382678e-05, …, -2.78150664e-05, -1.46349189e-05, 3.65395950e-06], [ 3.22381374e-05, 3.90163375e-05, 6.39709157e-05, …, 5.17422904e-05, 4.94353600e-05, 3.90770304e-05], [-2.50190016e-05, -1.82145705e-05, -1.97245214e-05, …, 6.13148062e-06, 6.13148062e-06, 6.13148062e-06]]])

    • csd_V1
      (channel_depth, trial_nr, time_csd)
      float64
      -0.04729 -0.04843 … 0.004271
      array([[[-0.04729251, -0.04843248, -0.03745331, …, -0.0506883 ,
      -0.05553956, -0.06003479],
      [-0.01093619,  0.01574628,  0.00914164, …,  0.00598613,
      0.01101591,  0.03123014],
      [-0.03696462, -0.04099173, -0.03852095, …,  0.00098069,
      -0.0050393 , -0.01264635],
      …,
      [-0.01247878,  0.00877942,  0.02265446, …,  0.00331111,
      0.03482657,  0.01182155],
      [-0.00745365, -0.01184343, -0.0143719 , …, -0.00701411,
      -0.00677321,  0.00801698],
      [-0.00185681,  0.00029347,  0.01247577, …,  0.04692436,
      0.04692436,  0.04692436]],

      [[-0.05243487, -0.04187987, -0.01951768, …, 0.00767212, 0.02122529, -0.00091668], [-0.03800667, -0.0267893 , 0.00151346, …, -0.06356118, -0.05255179, -0.02253284], [-0.13918267, -0.15069373, -0.14869443, …, -0.08287076, -0.10533221, -0.11878146], … [-0.0317182 , -0.0291328 , -0.04440809, …, 0.0128667 , -0.00803555, 0.00140582], [-0.00313798, -0.02211998, 0.00973861, …, 0.01834821, 0.02898202, 0.03053275], [ 0.01935376, 0.02458448, 0.02770717, …, 0.0335107 , 0.0335107 , 0.0335107 ]],

      [[ 0.05527245, 0.06647928, 0.08365367, …, 0.03946655, -0.00638597, 0.01617121], [ 0.00484864, 0.04955322, 0.00056809, …, -0.02495747, -0.04720462, -0.05647099], [ 0.14248465, 0.09267001, 0.12532577, …, -0.02102215, 0.01070787, -0.00105385], …, [ 0.02074605, 0.01291679, -0.00810403, …, -0.00440359, 0.01890968, 0.03686672], [-0.03145411, -0.01193072, 0.05258549, …, 0.06270049, 0.05033156, 0.03718263], [-0.01626375, -0.00653823, -0.01209703, …, 0.00427089, 0.00427089, 0.00427089]]])

    • lfp_whole_recording_V1
      (channel_depth, time_whole_rec_lfp)
      float64
      1.131e-05 2.316e-05 … 6.131e-06
      array([[ 1.13123117e-05,  2.31601759e-05,  2.56464473e-05, …,
      2.90957718e-05,  2.80686400e-05,  1.92065863e-05],
      [ 1.25864221e-05,  2.61936713e-05,  2.05721887e-05, …,
      2.51329249e-05,  1.10562717e-05,  1.14064589e-05],
      [ 1.06294434e-05,  1.20898434e-05,  1.05466595e-05, …,
      4.39547441e-06,  2.49921901e-06, -3.03491628e-06],
      …,
      [-1.51105564e-05, -2.86651554e-05, -2.43583384e-05, …,
      1.55104740e-05,  1.08842192e-05,  7.88508374e-06],
      [-2.60411104e-05, -3.63399664e-05, -2.41173628e-05, …,
      4.57287775e-05,  2.16324211e-05,  1.48063260e-05],
      [-1.92918607e-05, -3.23216874e-05, -2.71540780e-05, …,
      5.95832945e-05,  2.76861759e-05,  6.13148062e-06]])
    • ecp_whole_recording_V1
      (channel_depth, time_whole_rec_ecp)
      float64
      1.131e-05 2.316e-05 … 6.131e-06
      array([[ 1.13123117e-05,  2.31601759e-05,  2.56464473e-05, …,
      2.90957718e-05,  2.80686400e-05,  1.92065863e-05],
      [ 1.25864221e-05,  2.61936713e-05,  2.05721887e-05, …,
      2.51329249e-05,  1.10562717e-05,  1.14064589e-05],
      [ 1.06294434e-05,  1.20898434e-05,  1.05466595e-05, …,
      4.39547441e-06,  2.49921901e-06, -3.03491628e-06],
      …,
      [-1.51105564e-05, -2.86651554e-05, -2.43583384e-05, …,
      1.55104740e-05,  1.08842192e-05,  7.88508374e-06],
      [-2.60411104e-05, -3.63399664e-05, -2.41173628e-05, …,
      4.57287775e-05,  2.16324211e-05,  1.48063260e-05],
      [-1.92918607e-05, -3.23216874e-05, -2.71540780e-05, …,
      5.95832945e-05,  2.76861759e-05,  6.13148062e-06]])
    • spike_counts_LGN
      (unit_id_LGN, trial_nr, time_spikes)
      int16
      0 0 0 0 0 0 0 0 … 1 0 0 0 0 0 0 0
      array([[[0, 0, 0, …, 0, 0, 0],
      [0, 0, 0, …, 0, 0, 0],
      [0, 0, 0, …, 0, 0, 0],
      …,
      [0, 0, 0, …, 0, 0, 0],
      [0, 0, 0, …, 0, 0, 0],
      [0, 0, 0, …, 0, 0, 0]],

      [[0, 0, 0, …, 0, 0, 0], [0, 0, 0, …, 0, 0, 0], [0, 0, 0, …, 0, 0, 0], …, [0, 0, 0, …, 0, 0, 0], [0, 0, 0, …, 0, 0, 0], [0, 0, 0, …, 0, 0, 0]],

      [[0, 0, 0, …, 0, 0, 0], [0, 0, 0, …, 0, 0, 0], [0, 0, 0, …, 0, 0, 0], …, … …, [0, 0, 0, …, 0, 0, 0], [0, 0, 0, …, 0, 0, 0], [0, 0, 0, …, 0, 0, 0]],

      [[0, 0, 0, …, 0, 0, 0], [0, 0, 0, …, 0, 0, 0], [0, 0, 0, …, 0, 0, 0], …, [0, 0, 0, …, 0, 0, 0], [0, 0, 0, …, 0, 0, 0], [0, 0, 0, …, 0, 0, 0]],

      [[0, 0, 0, …, 0, 0, 0], [0, 0, 0, …, 0, 0, 0], [0, 0, 0, …, 0, 0, 0], …, [0, 0, 0, …, 0, 0, 0], [0, 1, 0, …, 0, 0, 0], [0, 0, 0, …, 0, 0, 0]]], dtype=int16)

    • spike_counts_V1
      (unit_id_V1, trial_nr, time_spikes)
      int16
      0 0 0 0 0 0 0 0 … 0 0 0 0 0 0 0 0
      array([[[0, 0, 0, …, 0, 0, 0],
      [0, 0, 0, …, 0, 0, 0],
      [0, 0, 0, …, 0, 0, 0],
      …,
      [0, 0, 0, …, 0, 0, 0],
      [0, 0, 0, …, 0, 0, 0],
      [0, 0, 0, …, 0, 0, 0]],

      [[0, 0, 0, …, 0, 0, 0], [0, 0, 0, …, 0, 0, 0], [0, 0, 0, …, 0, 0, 0], …, [0, 0, 0, …, 0, 0, 0], [0, 0, 0, …, 0, 0, 0], [0, 0, 0, …, 0, 0, 0]],

      [[0, 0, 0, …, 0, 0, 0], [0, 0, 0, …, 0, 0, 0], [0, 0, 1, …, 0, 0, 0], …, … …, [0, 0, 0, …, 0, 0, 0], [0, 0, 0, …, 0, 0, 0], [0, 0, 0, …, 0, 0, 0]],

      [[0, 0, 0, …, 0, 0, 0], [0, 0, 0, …, 0, 0, 0], [0, 0, 0, …, 0, 0, 0], …, [0, 0, 0, …, 0, 0, 0], [0, 0, 0, …, 0, 0, 0], [0, 0, 0, …, 0, 0, 0]],

      [[0, 0, 0, …, 0, 0, 0], [0, 0, 0, …, 0, 0, 0], [0, 0, 0, …, 0, 0, 0], …, [0, 0, 0, …, 0, 0, 0], [0, 0, 0, …, 0, 0, 0], [0, 0, 0, …, 0, 0, 0]]], dtype=int16)

    • spike_counts_LM
      (unit_id_LM, trial_nr, time_spikes)
      int16
      0 0 0 0 0 0 0 0 … 0 0 0 0 0 0 0 0
      array([[[0, 0, 0, …, 0, 0, 0],
      [0, 0, 0, …, 0, 0, 0],
      [0, 0, 0, …, 0, 0, 0],
      …,
      [0, 0, 0, …, 0, 0, 0],
      [0, 0, 0, …, 0, 0, 0],
      [0, 0, 0, …, 0, 0, 0]],

      [[0, 0, 0, …, 0, 0, 0], [0, 0, 0, …, 0, 0, 0], [0, 0, 0, …, 0, 0, 0], …, [0, 0, 0, …, 0, 0, 0], [0, 0, 0, …, 0, 0, 0], [0, 0, 0, …, 0, 0, 0]],

      [[0, 0, 0, …, 0, 0, 0], [0, 0, 0, …, 0, 0, 0], [0, 0, 0, …, 0, 0, 0], …, … …, [0, 0, 0, …, 0, 0, 0], [0, 0, 0, …, 0, 0, 0], [0, 0, 0, …, 0, 0, 0]],

      [[0, 0, 0, …, 0, 0, 0], [0, 0, 0, …, 0, 0, 0], [0, 0, 0, …, 0, 0, 1], …, [0, 0, 0, …, 0, 1, 0], [0, 0, 0, …, 0, 0, 0], [0, 0, 0, …, 0, 0, 0]],

      [[0, 0, 0, …, 0, 0, 0], [0, 0, 0, …, 0, 0, 0], [0, 0, 0, …, 0, 0, 0], …, [0, 0, 0, …, 0, 0, 0], [0, 0, 0, …, 0, 0, 0], [0, 0, 0, …, 0, 0, 0]]], dtype=int16)

    • pupil_width
      (trial_nr)
      float64
      39.12 40.53 53.87 … 46.57 44.31
      array([39.11652012, 40.53014519, 53.86896327, 50.82396045, 52.16879212,
      57.96043078, 54.92497335, 55.54967205, 43.90274746, 44.79854897,
      38.38702834, 40.71644189, 37.98256784, 35.66463223, 33.77803219,
      37.33170246, 34.40298455, 33.56487003, 34.8366242 , 34.23566353,
      37.82023369, 36.64031488, 34.90016986, 34.01127022, 34.01676223,
      33.07691675, 36.24898149, 34.31897366, 32.37205121, 32.29131936,
      31.22026173, 32.11667831, 35.9407955 , 33.83439488, 40.32179372,
      40.2773362 , 36.8416765 , 38.28154173, 32.92621992, 34.10611125,
      32.64213782, 35.34716687, 38.83240189, 41.4527341 , 40.80453453,
      34.4518716 , 33.54378434, 34.76110284, 39.39896604, 38.12986388,
      35.01399576, 36.1764032 , 36.91026669, 38.09285533, 37.47092614,
      35.08431869, 34.10750204, 33.98908271, 33.49633874, 31.95082717,
      36.40762283, 33.54301125, 30.16956358, 34.97767163, 35.73862834,
      37.5021579 , 46.90415398, 64.55582159, 65.24024526, 62.84832274,
      63.79648979, 64.1050437 , 54.12537803, 46.57182176, 44.30600894])
    • run_speed
      (trial_nr)
      float64
      1.155 1.597 6.057 … 1.251 1.711
      array([ 1.15461545,  1.59668837,  6.056658  ,  5.15987544,  8.52760497,
      11.22847943,  4.57915849,  3.55552612,  1.21703267,  1.14123532,
      1.22313026,  1.45219009,  1.27881682,  1.28545771,  1.30252225,
      1.45938978,  1.23683681,  1.35499306,  1.24373061,  1.72814998,
      1.10057218,  1.12037113,  1.0171456 ,  1.12698213,  1.23746278,
      1.0051122 ,  1.22995347,  1.18035543,  1.33973747,  1.31655126,
      1.42316228,  1.16454631,  1.1175898 ,  1.33965127,  1.44008401,
      1.27856107,  1.08369774,  1.2852668 ,  1.22042713,  0.93635542,
      1.10224883,  1.3656531 ,  1.14346642,  1.34443297,  1.27934364,
      1.22477016,  0.89182778,  1.08752668,  1.29883034,  1.38104512,
      0.96683534,  1.19810534,  0.99052094,  0.9343001 ,  1.71404147,
      1.51968473,  1.23845685,  0.84848258,  1.59605601,  1.25635954,
      1.22987165,  1.64782276,  1.75731669,  1.67665638,  1.42473997,
      1.02886151,  5.65275275,  8.34342969,  9.03230058, 10.64468513,
      8.17371765,  1.18762197,  1.36400808,  1.25093511,  1.71061779])
    • channel_depth
      PandasIndex
      PandasIndex(Index([   0,  -40,  -80, -120, -160, -200, -240, -280, -320, -360, -400, -440,
      -480, -520, -560, -600, -640, -680, -720, -760, -800, -840, -880],
      dtype='int64', name='channel_depth'))
    • trial_nr
      PandasIndex
      PandasIndex(Index([ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14, 15, 16, 17,
      18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35,
      36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53,
      54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71,
      72, 73, 74],
      dtype='int32', name='trial_nr'))
    • time_lfp
      PandasIndex
      PandasIndex(Index([           -1000.0, -999.1999999999999,             -998.4,
      -997.5999999999999,             -996.8, -995.9999999999999,
      -995.1999999999998, -994.3999999999999, -993.5999999999998,
      -992.7999999999998,
      …
      492.00000000004263, 492.80000000004276,  493.6000000000429,
      494.4000000000428, 495.20000000004273, 496.00000000004286,
      496.800000000043,  497.6000000000429, 498.40000000004284,
      499.20000000004296],
      dtype='float64', name='time_lfp', length=1875))
    • time_csd
      PandasIndex
      PandasIndex(Index([           -1000.0, -999.1999999999999,             -998.4,
      -997.5999999999999,             -996.8, -995.9999999999999,
      -995.1999999999998, -994.3999999999999, -993.5999999999998,
      -992.7999999999998,
      …
      492.00000000004263, 492.80000000004276,  493.6000000000429,
      494.4000000000428, 495.20000000004273, 496.00000000004286,
      496.800000000043,  497.6000000000429, 498.40000000004284,
      499.20000000004296],
      dtype='float64', name='time_csd', length=1875))
    • time_whole_rec_lfp
      PandasIndex
      PandasIndex(Index([1285609.7386445338, 1285610.5386446053,  1285611.338644677,
      1285612.1386447486, 1285612.9386448204, 1285613.7386448921,
      1285614.5386449639, 1285615.3386450356, 1285616.1386451072,
      1285616.938645179,
      …
      1584100.9653832503, 1584101.7653833218, 1584102.5653833936,
      1584103.365383465, 1584104.1653835368, 1584104.9653836086,
      1584105.7653836801, 1584106.5653837519, 1584107.3653838234,
      1584108.1653838952],
      dtype='float64', name='time_whole_rec_lfp', length=373124))
    • time_whole_rec_ecp
      PandasIndex
      PandasIndex(Index([1285609.7386445338, 1285610.5386446053,  1285611.338644677,
      1285612.1386447486, 1285612.9386448204, 1285613.7386448921,
      1285614.5386449639, 1285615.3386450356, 1285616.1386451072,
      1285616.938645179,
      …
      1584100.9653832503, 1584101.7653833218, 1584102.5653833936,
      1584103.365383465, 1584104.1653835368, 1584104.9653836086,
      1584105.7653836801, 1584106.5653837519, 1584107.3653838234,
      1584108.1653838952],
      dtype='float64', name='time_whole_rec_ecp', length=373124))
    • unit_id_LGN
      PandasIndex
      PandasIndex(Index([951782498, 951782516, 951782530, 951782631, 951782683, 951782699,
      951782744, 951782795, 951782832, 951782895, 951787033, 951792341,
      951792375, 951792398, 951792418, 951792441, 951792504, 951792544,
      951797077, 951798318, 951798391, 951798404, 951798553, 951798681,
      951801317, 951798434, 951798528],
      dtype='int32', name='unit_id_LGN'))
    • time_spikes
      PandasIndex
      PandasIndex(Index([            -999.5,             -998.5,             -997.5,
      -996.4999999999999,             -995.5, -994.4999999999999,
      -993.5, -992.4999999999999,             -991.5,
      -990.4999999999999,
      …
      490.50000000000006, 491.50000000000006, 492.50000000000006,
      493.50000000000006, 494.50000000000006, 495.50000000000006,
      496.50000000000006, 497.50000000000006, 498.50000000000006,
      499.50000000000006],
      dtype='float64', name='time_spikes', length=1500))
    • unit_id_V1
      PandasIndex
      PandasIndex(Index([951795075, 951795086, 951795098, 951795140, 951795163, 951795222,
      951795238, 951795256, 951795269, 951795290, 951795309, 951795317,
      951795403, 951797419, 951795458, 951795474, 951795487, 951795495,
      951795554, 951795680, 951795563, 951795611, 951795663, 951795688,
      951795706, 951795713, 951795721, 951797465, 951795729, 951795737,
      951795747, 951795873, 951795753, 951795782, 951795792, 951795760,
      951795768, 951795775, 951795807, 951795824, 951795832, 951795839,
      951795858, 951795880, 951795886, 951795896, 951795918, 951797520,
      951797539, 951797553, 951795936, 951795943, 951795967, 951795976,
      951797567, 951797489, 951796016, 951796073, 951796125, 951796165,
      951796176, 951796229, 951797633, 951796333, 951797647, 951797664,
      951796352, 951796494, 951797718, 951796542, 951796556, 951797730,
      951796585, 951797742, 951796629, 951796639, 951796660, 951796696,
      951796791, 951797776, 951796866, 951796906, 951796928, 951796949,
      951797595, 951797613, 951797709, 951797764, 951797811, 951797995,
      951798053],
      dtype='int32', name='unit_id_V1'))
    • unit_id_LM
      PandasIndex
      PandasIndex(Index([951791074, 951791093, 951791202, 951791214, 951791273, 951791305,
      951791297, 951791401, 951791422, 951791467, 951791442, 951791453,
      951792163],
      dtype='int32', name='unit_id_LM'))
    • stimulus_start_times
      PandasIndex
      PandasIndex(Index([1289613.2512503476, 1293616.6012503474, 1299621.6212503475,
      1301623.2912503474, 1303624.9712503476, 1307628.3312503474,
      1309629.9512503473, 1319638.3212503474, 1329646.7112503473,
      1333650.0712503474, 1337653.4012503475, 1341656.7412503476,
      1351665.0512503474, 1357670.1212503475, 1361673.4312503475,
      1371681.8412503474, 1373683.4712503473, 1375685.1712503475,
      1383691.8312503477, 1387695.1512503477, 1397703.5312503476,
      1401706.8812503475, 1403708.5812503477, 1407711.9312503475,
      1411715.2512503476, 1413716.9412503475, 1415718.6312503477,
      1417720.2612503476, 1419721.9512503475, 1421723.6212503477,
      1423725.2512503476, 1427728.6212503475, 1429730.2812503476,
      1431731.9412503475, 1433733.6212503477, 1435735.3212503477,
      1441740.3312503477, 1447745.3112503476, 1451748.6712503475,
      1459755.3812503477, 1461757.0612503476, 1467762.0512503476,
      1473767.0912503477, 1481773.7512503476, 1485777.1012503477,
      1489780.4612503476, 1491782.1212503477, 1493783.8112503474,
      1497787.1612503477, 1501790.4812503476, 1503792.1412503477,
      1505793.8012503476, 1511798.8712503477, 1515802.1712503475,
      1517803.8912503475, 1519805.5512503476, 1521807.2312503476,
      1529813.9112503477, 1531815.5712503477, 1533817.2312503476,
      1537820.5712503474, 1541823.9312503478, 1543825.6012503474,
      1545827.3012503476, 1547828.9312503475, 1551832.2912503476,
      1553833.9612503476, 1563842.3512503474, 1565843.9912503478,
      1569847.3512503477, 1571849.0212503476, 1573850.6912503475,
      1575852.3412503474, 1577854.0712503474, 1583859.0512503476],
      dtype='float64', name='stimulus_start_times'))
    • stimulus_stop_times
      PandasIndex
      PandasIndex(Index([1289863.4505906447, 1293866.8005906448, 1299871.8205906446,
      1301873.490590645, 1303875.1730906446, 1307878.5255906447,
      1309880.1580906447, 1319888.5255906447, 1329896.9080906447,
      1333900.2655906447, 1337903.5980906447, 1341906.9405906445,
      1351915.2580906446, 1357920.3205906448, 1361923.6380906447,
      1371932.0330906445, 1373933.6755906448,  1375935.370590645,
      1383942.0355906447, 1387945.3630906448,  1397953.738090645,
      1401957.0830906448, 1403958.7780906446,  1407962.128090645,
      1411965.450590645, 1413967.1380906447,  1415968.823090645,
      1417970.4655906449, 1419972.1505906447, 1421973.8230906448,
      1423975.4605906447,  1427978.823090645, 1429980.4905906448,
      1431982.1530906449, 1433983.8305906449, 1435985.5230906447,
      1441990.5355906447, 1447995.5230906447, 1451998.8755906448,
      1460005.5855906447, 1462007.2605906448, 1468012.2605906453,
      1474017.2930906452,  1482023.960590645, 1486027.3080906447,
      1490030.6655906448, 1492032.3255906447, 1494034.0130906447,
      1498037.3605906449, 1502040.6855906448,  1504042.350590645,
      1506044.0105906448, 1512049.0705906448, 1516052.3830906448,
      1518054.088090645,  1520055.753090645, 1522057.4280906445,
      1530064.1105906446, 1532065.7755906447, 1534067.4380906445,
      1538070.7780906446, 1542074.1355906448, 1544075.8080906447,
      1546077.4980906448, 1548079.1380906447,  1552082.495590645,
      1554084.165590645, 1564092.5505906448,  1566094.193090645,
      1570097.5530906448, 1572099.2255906449, 1574100.8955906448,
      1576102.5530906448, 1578104.2680906446,  1584109.255590645],
      dtype='float64', name='stimulus_stop_times'))
    • channel_id
      PandasIndex
      PandasIndex(Index([850144538, 850144530, 850144522, 850144514, 850144506, 850144498,
      850144490, 850144482, 850144474, 850144466, 850144458, 850144450,
      850144442, 850144434, 850144426, 850144418, 850144410, 850144402,
      850144394, 850144386, 850144378, 850144370, 850144362],
      dtype='int32', name='channel_id'))
  • time_unit :
    millisecond
    lfp_unit :
    Volt
    channel_depth_unit :
    micrometer
    note_channel_depth :
    Measured in distance from electrode closest to scalp
    sampling_frequency_lfp :
    1250
    sampling_frequency_spikes :
    1000
    sampling_frequency_unit :
    Hz
    stimulus_onset :
    1000
    stimulus_offset :
    1250

lfp_V1 = dataset['lfp_V1']
lfp_whole_rec = dataset['lfp_whole_recording_V1']
channels_depth = dataset['channel_depth']

sampling_frequency_lfp = dataset.sampling_frequency_lfp # Hz
time_lfp = lfp_V1.time_lfp.values # ms

Example: A power spectrum is calculated for a generated signal in the cell below. Use an equivalent red noise spectrum to find and plot the threshold for statistically significant peaks in this power spectrum.

# provided

# Generate signal
np.random.seed(42)
sampling_frequency = 1000
duration = 1 # second
time_vec = np.arange(0, duration, 1/sampling_frequency)

f_peak = 25 # artificial peak
sig = 4*np.sin(2 * np.pi * f_peak * time_vec) + np.cumsum(np.random.randn(len(time_vec)))  # 1/f noise + peak

# compute the power spectrum
n_perseg = 256
freqs, psd_signal = signal.welch(sig, fs = sampling_frequency, noverlap=0)

# normalize the power spectrum
psd_signal_norm = psd_signal/psd_signal.max()

# plot signal and power spectrum
plot_signal_and_power_spectrum(time_vec, sig, freqs, psd_signal_norm)

Solution
# solution
n_samples = sig.shape[-1]
psd_expected, psd_significance_threshold = utils.get_significance_threshold_psd(red_noise_spectrum, freqs, psd_signal_norm, n_samples, n_perseg,)

# plot computed power spectrum together with threshold for peaks
utils.plot_spectra_with_significance_threshold(freqs, psd_signal_norm, psd_expected, n_samples, n_perseg)

Exercise: Use an equivalent red noise spectrum to find and plot the threshold for statistically significant peaks in the power spectrum calculated for the synthetic signal below.

# provided

# Generate signal
np.random.seed(42)
sampling_frequency = 1000
duration = 1 # second
time_vec = np.arange(0, duration, 1/sampling_frequency)

f_peak = 100 # artificial peak
sig = 4*np.sin(2 * np.pi * f_peak * time_vec) + np.cumsum(np.random.randn(len(time_vec)))  # 1/f noise + peak

# compute the power spectrum
n_perseg = 256
freqs, psd_signal = signal.welch(sig, fs = sampling_frequency, noverlap=0)

# normalize the power spectrum
psd_signal_norm = psd_signal/psd_signal.max()

# plot signal and power spectrum
plot_signal_and_power_spectrum(time_vec, sig, freqs, psd_signal_norm)

Solution
# solution
n_samples = sig.shape[-1]
psd_expected, psd_significance_threshold = utils.get_significance_threshold_psd(red_noise_spectrum, freqs, psd_signal_norm, n_samples, n_perseg,)

# plot computed power spectrum together with threshold for peaks
utils.plot_spectra_with_significance_threshold(freqs, psd_signal_norm, psd_expected, n_samples, n_perseg)

Exercise: Use an equivalent red noise spectrum to find and plot the threshold for statistically significant peaks in the power spectrum calculated from the LFP trace from channel index 5 in the first trial.

Hint: Here, what needs to be changed is the n_samples parameter (to the number of samples in the LFP trace) and that it’s the psd_lfp_trace_norm that goes into the utils.get_significance_threshold_psd and utils.plot_spectra_with_significance_threshold functions.

# provided 

itrial = 0
ichan = 5
lfp_trace = lfp_V1[ichan, itrial]
time_lfp = lfp_V1.time_lfp

# compute the power spectrum
n_perseg = 256
freqs, psd_lfp_trace = signal.welch(lfp_trace, fs = sampling_frequency_lfp, nperseg=n_perseg, noverlap=0)

# normalize the power spectrum
psd_lfp_trace_norm = psd_lfp_trace/psd_lfp_trace.max()

# plot signal and power spectrum
utils.plot_signal_and_power_spectrum(time_lfp, lfp_trace, freqs, psd_lfp_trace_norm)

Solution
# solution
n_samples = lfp_trace.shape[-1]
psd_expected, psd_significance_threshold = utils.get_significance_threshold_psd(red_noise_spectrum, freqs, psd_lfp_trace_norm, n_samples, n_perseg,)

# plot computed power spectrum together with threshold for peaks
utils.plot_spectra_with_significance_threshold(freqs, psd_lfp_trace_norm, psd_expected, n_samples, n_perseg)

Exercise: Use an equivalent red noise spectrum to find and plot the threshold for statistically significant peaks in the trial-averaged power spectrum (psd_trial_avg_norm) calculated from the LFP traces from channel index 5.

Hint: The psd_trial_avg_norm variables needs to go into the get_significance_threshold_psd and plot_spectra_with_significance_threshold functions.

# provided
ichan = 5
lfp_traces = lfp_V1[ichan].values

n_perseg = 256
freqs, psd_all_trials = signal.welch(lfp_traces, fs = sampling_frequency_lfp, nperseg=n_perseg, noverlap = 0)
psd_trial_avg = np.mean(psd_all_trials, axis=0)

# normalize the power spectrum
psd_trial_avg_norm = psd_trial_avg/psd_trial_avg.max()
Solution
# solution
n_samples = lfp_trace.shape[-1]
psd_expected, psd_significance_threshold = utils.get_significance_threshold_psd(red_noise_spectrum, freqs, psd_trial_avg_norm, n_samples, n_perseg,)

# plot computed power spectrum together with threshold for peaks
utils.plot_spectra_with_significance_threshold(freqs, psd_trial_avg_norm, psd_expected, n_samples, n_perseg)

Example: Find the frequencies of the peak(s) in the power spectrum that are above the threshold for statistical significance with p = 0.05 as the limit. Hint: Find where the original PSD is greater than the threshold for significance and use that as an index or a mask for the frequencies.

Solution
# solution

# set threshold for significance
alpha = 0.05

n_samples = lfp_traces.shape[-1]
psd_expected, psd_significance_threshold = utils.get_significance_threshold_psd(red_noise_spectrum, freqs, psd_trial_avg_norm, 
                                                                                n_samples, n_perseg, alpha=alpha)

# find frequencies where the power spectrum is above the threshold for significance
idx_significant_freqs = np.where((psd_trial_avg_norm > psd_significance_threshold))[0]
freqs[idx_significant_freqs]
array([53.7109375, 58.59375  ])

Exercise: Change the alpha parameter (the threshold for significance) to p = 0.01 and find the frequencies of the peak(s) in the power spectrum that are above the threshold for statistical significance.

Solution
# solution

# set threshold for significance
alpha = 0.01

n_samples = lfp_traces.shape[-1]
psd_expected, psd_significance_threshold = utils.get_significance_threshold_psd(red_noise_spectrum, freqs, psd_trial_avg_norm, n_samples, n_perseg,)

# find frequencies where the power spectrum is above the threshold for significance
idx_significant_freqs = np.where((psd_trial_avg_norm > psd_significance_threshold))[0]
freqs[idx_significant_freqs]
array([53.7109375, 58.59375  ])

Section 3: Correcting for multiple comparisons - False Discovery Rate

Get p-values and correct for multiple comparisons.

Code Description
np.where(array==value)[0] Returns the indeces in an array where the condition is met.
np.sum(array < some_number) Counts the number of elements in the array that are smaller than some_number.
p_values = utils.get_p_value(
    idx_freqs_to_test, psd_original,
    psd_expected, n_samples,
    n_perseg)
Calculates the p-values of the peaks in the spectrum psd_original at the frequencies selected by idx_freqs_to_test.
p_values_adjusted =
    false_discovery_control(p_values)
Adjust the p-values to account for multiple statistical tests.
np.arange(start, stop, step) Generates an array of values evenly spaced from start to stop, with the spacing given by step

Exercises

from scipy.stats import false_discovery_control
# provided

lfp_traces = lfp_V1[ichan].values

n_perseg = 256
freqs, psd_all_trials = signal.welch(lfp_traces, fs = sampling_frequency_lfp, nperseg=n_perseg, noverlap=0)
psd_trial_avg = np.mean(psd_all_trials, axis=0)
psd_trial_avg_norm = psd_trial_avg/psd_trial_avg.max()

n_samples = lfp_traces.shape[-1]
psd_expected, psd_significance_threshold = utils.get_significance_threshold_psd(red_noise_spectrum, freqs, psd_trial_avg_norm, n_samples, n_perseg,)

Exercise: Get the p-value of the power spectral density at 4 Hz (the frequency at index 1 in the power spectrum).

Solution
# solution

index_peak = 1
freq_to_test = freqs[index_peak]

idx_freqs_to_test = np.where(freqs==freq_to_test)[0]

p_values = utils.get_p_value(idx_freqs_to_test, psd_trial_avg_norm, psd_expected, n_samples, n_perseg)
print(f'Frequency: {np.round(freq_to_test, 3)}, p_values: {p_values}')
Frequency: 4.883, p_values: [0.53966323]

Exercise: Change the index_peak variable to 12 to get the p-value for the peak at 58 Hz.

Solution
freq_to_test = freqs[12]

idx_freqs_to_test = np.where(freqs==freq_to_test)[0]

p_values = utils.get_p_value(idx_freqs_to_test, psd_trial_avg_norm, psd_expected, n_samples, n_perseg)
print(f'Frequency: {np.round(freq_to_test, 3)}, p_values: {p_values}')
Frequency: 58.594, p_values: [0.00331165]

Exercise: Pick a frequency yourself and check the p-value for the PSD at this frequency.

Solution
freq_to_test = freqs[3]

idx_freqs_to_test = np.where(freqs==freq_to_test)[0]

p_values = utils.get_p_value(idx_freqs_to_test, psd_trial_avg_norm, psd_expected, n_samples, n_perseg)
print(f'Frequency: {np.round(freq_to_test, 3)}, p_values: {p_values}')
Frequency: 14.648, p_values: [0.69768094]

Example: For the list of p-values below, use the function false_discovery_control to adjust the p-values for multiple tests and print the adjusted values.

pvalues = [0.01,0.001, 0.05, 0.20, 0.15, 0.15]

print(false_discovery_control(pvalues))
[0.03  0.006 0.1   0.2   0.18  0.18 ]

Example: Calculate the p-values for all frequencies in the spectrum and print them.

freq_to_test = freqs[12]

idx_freqs_to_test = np.arange(len(freqs))

p_values = utils.get_p_value(idx_freqs_to_test, psd_trial_avg_norm, psd_expected, n_samples, n_perseg)
p_values
array([0.99994182, 0.53966323, 0.37461806, 0.69768094, 0.8437394 ,
       0.83954932, 0.78209327, 0.63652384, 0.55241563, 0.54448858,
       0.35513472, 0.02124864, 0.00331165, 0.15382451, 0.46759613,
       0.49673074, 0.38721897, 0.40848411, 0.45124855, 0.51715938,
       0.59512564, 0.63416958, 0.59640879, 0.5247559 , 0.58267034,
       0.70482311, 0.69319825, 0.70374299, 0.81361991, 0.88745834,
       0.86759701, 0.89620194, 0.89785376, 0.88179782, 0.87863781,
       0.86271158, 0.87561069, 0.91624073, 0.90923992, 0.91074673,
       0.90154141, 0.94412918, 0.95738849, 0.95157662, 0.94073517,
       0.93839015, 0.94689053, 0.9439922 , 0.94951468, 0.95963682,
       0.96086268, 0.95994646, 0.97019623, 0.9698779 , 0.95351398,
       0.95096987, 0.95822741, 0.97178855, 0.96026926, 0.95326415,
       0.95414415, 0.96522751, 0.95835848, 0.94013732, 0.94623332,
       0.94633128, 0.94752047, 0.95072628, 0.95313756, 0.95460715,
       0.9626944 , 0.96588426, 0.97225063, 0.97406425, 0.95528308,
       0.96232144, 0.96616054, 0.96780375, 0.96891313, 0.96513378,
       0.96866497, 0.96789064, 0.96984792, 0.96485843, 0.97035351,
       0.97243048, 0.95669288, 0.95163354, 0.94801019, 0.94842228,
       0.95139092, 0.96563306, 0.96390652, 0.95258153, 0.94662519,
       0.95205831, 0.95766357, 0.97200653, 0.97421254, 0.97004718,
       0.9791819 , 0.96709422, 0.95781304, 0.96658175, 0.97823302,
       0.98888737, 0.99755821, 0.99946247, 0.99992888, 0.99998283,
       0.99998806, 0.99999233, 0.99999484, 0.99999476, 0.99999614,
       0.99999634, 0.9999972 , 0.99999705, 0.9999949 , 0.99999461,
       0.99999567, 0.99999626, 0.99999656, 0.99999526, 0.999992  ,
       0.99999324, 0.99999346, 0.99999458, 0.99999995])

Exercise: How many frequencies have p-values below 0.05 according to this naive test?

Solution
np.sum(p_values < 0.05)
2

Exercise: Above we tested every frequency in the spectrum, which means that we carried out 129 tests for significance. With this many tests, we would actually expect up to 6 frequencies to have peaks with p-values < 0.05 even if the peaks are only due to noise. Use the false_discovery_control function to adjust the p_values obtained above for doing multiple tests.

Solution
print(false_discovery_control(p_values))
[0.99999995 0.99999995 0.99999995 0.99999995 0.99999995 0.99999995
 0.99999995 0.99999995 0.99999995 0.99999995 0.99999995 0.99999995
 0.42720283 0.99999995 0.99999995 0.99999995 0.99999995 0.99999995
 0.99999995 0.99999995 0.99999995 0.99999995 0.99999995 0.99999995
 0.99999995 0.99999995 0.99999995 0.99999995 0.99999995 0.99999995
 0.99999995 0.99999995 0.99999995 0.99999995 0.99999995 0.99999995
 0.99999995 0.99999995 0.99999995 0.99999995 0.99999995 0.99999995
 0.99999995 0.99999995 0.99999995 0.99999995 0.99999995 0.99999995
 0.99999995 0.99999995 0.99999995 0.99999995 0.99999995 0.99999995
 0.99999995 0.99999995 0.99999995 0.99999995 0.99999995 0.99999995
 0.99999995 0.99999995 0.99999995 0.99999995 0.99999995 0.99999995
 0.99999995 0.99999995 0.99999995 0.99999995 0.99999995 0.99999995
 0.99999995 0.99999995 0.99999995 0.99999995 0.99999995 0.99999995
 0.99999995 0.99999995 0.99999995 0.99999995 0.99999995 0.99999995
 0.99999995 0.99999995 0.99999995 0.99999995 0.99999995 0.99999995
 0.99999995 0.99999995 0.99999995 0.99999995 0.99999995 0.99999995
 0.99999995 0.99999995 0.99999995 0.99999995 0.99999995 0.99999995
 0.99999995 0.99999995 0.99999995 0.99999995 0.99999995 0.99999995
 0.99999995 0.99999995 0.99999995 0.99999995 0.99999995 0.99999995
 0.99999995 0.99999995 0.99999995 0.99999995 0.99999995 0.99999995
 0.99999995 0.99999995 0.99999995 0.99999995 0.99999995 0.99999995
 0.99999995 0.99999995 0.99999995]

Exercise: How many of the adjusted p-values are <0.05?

Solution
sum(false_discovery_control(p_values) < 0.05)
0