Synthetic Data

For testing and demonstrational purposes it is always good to work with synthetic data. Syncopy brings its own suite of synthetic data generators, but it is also possible to devise your own synthetic data and conveniently analyze it with Syncopy.

General Recipe

To create a synthetic data set follow these steps:

  • write a function which returns a single trial with desired shape (nSamples, nChannels), such that each trial is a 2d-ndarray

  • collect all the trials into a Python list, for example with a list comprehension or simply a for loop

  • Instantiate an AnalogData object by passing this list holding the trials and set the samplerate

In (pseudo-)Python code:

def generate_trial(nSamples, nChannels):

     trial = .. something fancy ..

     # These should evaluate to True
     isinstance(trial, np.ndarray)
     trial.shape == (nSamples, nChannels)

     return trial

# here we use a list comprehension
trial_list = [generate_trial(nSamples, nChannels) for _ in range(nTrials)]

my_fancy_data = spy.AnalogData(trial_list, samplerate=my_samplerate)

Note

The same recipe can be used to generally instantiate Syncopy data objects from NumPy arrays.

Built-in Generators

These generators return single-trial NumPy arrays, so to import them into Syncopy use the General Recipe described above.

syncopy.tests.synth_data.phase_diffusion(freq, eps=0.1, fs=1000, nChannels=2, nSamples=1000, return_phase=False)[source]

Linear (harmonic) phase evolution + a Brownian noise term inducing phase diffusion around the deterministic phase drift with slope 2pi * freq (angular frequency).

The linear phase increments are given by dPhase = 2pi * freq/fs, the Brownian increments are scaled with eps relative to these phase increments.

Parameters:
  • freq (float) – Harmonic frequency in Hz

  • eps (float) – Scaled Brownian increments 1 means the single Wiener step has on average the size of the harmonic increments

  • fs (float) – Sampling rate in Hz

  • nChannels (int) – Number of channels

  • nSamples (int) – Number of samples in time

  • return_phase (bool, optional) – If set to true returns the phases in radians

Returns:

phases – Synthetic nSamples x nChannels data array simulating noisy phase evolution/diffusion

Return type:

numpy.ndarray

syncopy.tests.synth_data.AR2_network(AdjMat=None, nSamples=1000, alphas=[0.55, - 0.8])[source]

Simulation of a network of coupled AR(2) processes

With the default parameters the individual processes (as in Dhamala 2008) have a spectral peak at 40Hz with a sampling frequency of 200Hz.

NOTE: There is no check for stability: setting the

alphas ad libitum and/or defining large and dense (many connections) systems will almost surely lead to an unstable system

Parameters:
  • AdjMat (np.ndarray or None) – nChannel x nChannel adjacency matrix where entry (i,j) is the coupling strength from channel i -> j. If left at None, the default 2 Channel system with unidirectional 2 -> 1 coupling is generated.

  • nSamples (int, optional) – Number of samples in time

  • alphas (2-element sequence, optional) – The AR(2) parameters for lag1 and lag2

Returns:

sol – The nSamples x nChannel solution of the network dynamics

Return type:

numpy.ndarray

Example: Noisy Harmonics

We can easily create custom synthetic datasets using basic NumPy functionality and Syncopy’s AnalogData. Let’s create two harmonics and add some white noise to it:

import numpy as np
import syncopy as spy


def generate_noisy_harmonics(nSamples, nChannels, samplerate):

    f1, f2 = 20, 50 # the harmonic frequencies in Hz
    
    # the sampling times vector
    tvec = np.arange(nSamples) * 1 / samplerate 

    # define the two harmonics
    harm1 = np.cos(2 * np.pi * f1 * tvec)
    harm2 = np.cos(2 * np.pi * f2 * tvec)
   
    # add some white noise
    trial = 0.5 * np.random.randn(nSamples, nChannels)
    # add 1st harmonic to 1st channel
    trial[:, 0] += harm1 
    # add 2nd harmonic to 2nd channel
    trial[:, 1] += 0.5 * harm2 
	
    return trial


nTrials = 50
nSamples = 1000
nChannels = 3
samplerate = 500   # in Hz

trials = []
for _ in range(nTrials):
    trial = generate_noisy_harmonics(nSamples, nChannels, samplerate)
    trials.append(trial)
    
synth_data = spy.AnalogData(trials, samplerate=samplerate)

Here we first defined the number of trials and then the number of samples and channels per trial. With a sampling rate of 500Hz and 1000 samples this gives us a trial length of two seconds. The function generate_noisy_harmonics adds white noise to all channels, a 20Hz harmonic on the 1st channel and a 50Hz harmonic on the 2nd channel. Every trial got collected into a Python list, which at the last line was used to initialize our AnalogData object. Note that data instantiated that way always has a default trigger offset of -1 seconds.

Now we can directly run a simple FFT analysis and plot the power spectra of all 3 channels:

spectrum = spy.freqanalysis(synth_data, foilim=[0,80], keeptrials=False)
spectrum.singlepanelplot()
../_images/synth_data_spec.png

As constructed, we have two harmonic peaks at the respective frequencies (20Hz and 50Hz) and the white noise floor on all channels.