Permutation test between observed samples and surrogate samples (peak-to-peak shuffled)#

In this tutorial, we will perform a permutation test between observed samples and surrogate samples (peak-to-peak shuffled) to assess whether the observed samples are significantly different from what would be expected by chance.

[9]:
import pickle as pkl
import numpy as np
import matplotlib.pyplot as plt

from pyriodic import Circular, CircPlot
from pyriodic.datasets import sample
from pyriodic.preproc import RawSignal
from pyriodic.phase_events import create_phase_events
from pyriodic.surrogate import surrogate_shuffle_breath_cycles
from pyriodic.permutation import permutation_test_against_surrogate

Load the sample dataset#

[2]:
path = sample.data_path()

with open(path, "rb") as f:
    data = pkl.load(f)

behav_data = data["behav_data"]
resp_ts, sfreq, event_samples, event_ids, labels = data["resp_ts"], data["sfreq"], behav_data["event_samples"], behav_data["event_ids"], behav_data["event_type"]

We crop the data to the beginning of the experiment to make the processing time shorter.

[3]:
start_sample = event_samples[0] - 1000  # start 1000 samples before the first event

# subtract start sample from events (the first column)
event_samples -= start_sample
resp_ts = resp_ts[start_sample:]

Next, the data is filtered, smoothed, and z-scored to prepare it for analysis.

[4]:
# initialise RawSignal object
raw = RawSignal(resp_ts, fs=sfreq)
raw.filter_bandpass(low=0.1, high=1.0)
raw.smoothing(window_size=500)
raw.zscore()

We extract the phase angles and identify the peaks and troughs of the signal using a two-point method. The create_phase_events function is then used to create phase events based on the extracted phase angles and the target event samples.

[5]:
PA, peaks, troughs = raw.phase_twopoint(prominence=0.1)

# get the samples at which the target events occur
target_event_samples = [
    samp for samp, label in zip(event_samples, labels) if "target" in label
]

circ = create_phase_events(
    PA, events=target_event_samples
)
Rejected 0 out of 416 events (0.0%)

Surrogate samples are generated by shuffling the peak-to-peak intervals of the observed samples. This creates a null distribution of phase angles that can be used to assess the significance of the observed samples.

[6]:
surr_samples = surrogate_shuffle_breath_cycles(
    PA, target_event_samples,
    n_surrogate=1000
)
100%|██████████| 1000/1000 [00:29<00:00, 33.58it/s]
Generated 1000 surrogate time series by shuffling breathing cycles.
[7]:
# Run permutation test
obs_stat, pval = permutation_test_against_surrogate(
    circ.data,
    surrogate_samples=surr_samples,
    alternative="greater",
    n_permutations = 1000,
)
p val: 0.000999000999000999, observed stat: 772578.500, mean null stat: 499983.753

Let’s visually compare the observed phase angles of the target stimuli to a null distribution of phase angles drawn from the full respiratory cycle by plotting the mean resultant vector length and the mean angle for both the observed target phase angles and a sample of null phase angles. Note that this is not the statistical test, but rather a visualisation of the data.

[8]:
# make a circular object with the null_samples for plotting
circ_surr = Circular.from_multiple(
    [Circular(samp, labels=[i]*len(samp)) for i, samp in enumerate(surr_samples)]
)

circplot_surr = CircPlot(circ_surr)
circplot_surr.add_circular_mean(color = "lightblue", alpha = 0.3)
circplot_surr.add_arrows(
    np.array([circ.mean()]),
    np.array([circ.r()]),
    color="darkblue"
)

plt.tight_layout()
../_images/tutorials_04a_permutation_against_surrogate_shuffled_14_0.png