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#

We can easily create custom synthetic datasets using basic NumPy functionality and Syncopy’s AnalogData.

To create a synthetic timeseries data set follow these steps:

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

  • 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 as data and set the desired 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

# collect the trials
nSamples = 1000
nChannels = 2
nTrials = 100
trls = []

for _ in range(nTrials):
    trial = generate_trial(Samples, nChannels)
    # manipulate further as needed, e.g. add a constant
    trial += 3
    trls.append(trial)

# instantiate syncopy data object
my_fancy_data = spy.AnalogData(data=trls, samplerate=my_samplerate)

Note

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

Example: Noisy Harmonics#

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
    ch1 = np.cos(2 * np.pi * f1 * tvec)
    ch2 = np.cos(2 * np.pi * f2 * tvec)

    # concatenate channels to to trial array
    trial = np.column_stack([ch1, ch2])

    # add some white noise
    trial += 0.5 * np.random.randn(nSamples, nChannels)

    return trial


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

# collect trials
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 (nTrials) and then the number of samples (nSamples) and channels (nChannels) 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 a 20Hz harmonic on the 1st channel, a 50Hz harmonic on the 2nd channel and white noise to all channels, Every trial got collected into a Python list, which at the last line was used to initialize our AnalogData object synth_data. Note that data instantiated that way always has a default trigger offset of -1 seconds.

Now we can directly run a multi-tapered FFT analysis and plot the power spectra of all 2 channels:

spectrum = spy.freqanalysis(synth_data, foilim=[0,80], tapsmofrq=2, 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.

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.harmonic(freq, ...)

A harmonic with frequency freq.

syncopy.tests.synth_data.linear_trend(y_max)

A linear trend on all channels from 0 to y_max in nSamples.

syncopy.tests.synth_data.phase_diffusion(freq)

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

syncopy.tests.synth_data.AR2_network([...])

Simulation of a network of coupled AR(2) processes

syncopy.tests.synth_data.white_noise([...])

Plain white noise with unity standard deviation.