Perievent / Spike-triggered average#
The perievent module allows for aligning timeseries and timestamps data around events, as well as computing event-triggered averages (e.g. spike-triggered averages).
Peri-Event Time Histograms (PETH)#
Spike data#
We will start with the common use-case of aligning the spiking activity of a unit to a set of events.
Single unit#
Let’s simulate some uniform stimuli and a unit that has a gaussian firing field after the stimulus:
The compute_perievent function
can align timestamps to a particular set of events:
window = (-0.1, 0.4)
peth = nap.compute_perievent(data=ts, events=stimuli, window=window)
peth
Index rate events
------- ------ --------
0 6.0 1.0
1 10.0 2.0
2 6.0 3.0
3 6.0 4.0
4 6.0 5.0
5 6.0 6.0
6 6.0 7.0
... ... ...
992 6.0 993.0
993 6.0 994.0
994 10.0 995.0
995 8.0 996.0
996 8.0 997.0
997 6.0 998.0
998 6.0 999.0
The returned object is a TsGroup containing the aligned timestamps per event.
The event times are stored in the events metadata column.
If you want to get aligned counts, you can now easily call:
bin_size = 0.01
peth_counts = peth.count(bin_size)
peth_counts
Time (s) 0 1 2 3 4 ...
---------- --- --- --- --- --- -----
-0.095 0 0 0 0 0 ...
-0.085 0 0 0 0 0 ...
-0.075 0 0 0 0 0 ...
-0.065 0 0 0 0 0 ...
-0.055 0 0 0 0 0 ...
-0.045 0 0 0 0 0 ...
-0.035 0 0 0 0 0 ...
... ...
0.335 0 0 0 0 0 ...
0.345 0 0 0 0 0 ...
0.355 0 0 0 0 0 ...
0.365 0 0 0 0 0 ...
0.375 0 0 0 0 0 ...
0.385 0 0 0 0 0 ...
0.395 0 0 0 0 0 ...
Metadata
events 1.0 2.0 3.0 4.0 5.0 ...
dtype: int64, shape: (50, 999)
Visualizing the aligned timestamps can now be done by using the to_tsd function.
This function flattens the TsGroup into one Tsd containing all the timestamps and storing the event ids as data.
We can also take the mean of the counts and divide by the bin size to show an estimate of the aligned firing rate:
Text(0.5, 1.0, 'peri.count(bin_size) / bin_size')
Multiple units#
The same function can be applied to a group of units:
In this case, it returns a dict of TsGroup, containing the same object as before, but now per unit.
peth = nap.compute_perievent(data=tsgroup, events=stimuli, window=window)
peth
{1: Index rate events
------- ------ --------
0 6.0 1.0
1 10.0 2.0
2 6.0 3.0
3 6.0 4.0
4 6.0 5.0
5 6.0 6.0
6 6.0 7.0
... ... ...
992 6.0 993.0
993 6.0 994.0
994 10.0 995.0
995 8.0 996.0
996 8.0 997.0
997 6.0 998.0
998 6.0 999.0,
2: Index rate events
------- ------ --------
0 6.0 1.0
1 8.0 2.0
2 6.0 3.0
3 6.0 4.0
4 8.0 5.0
5 6.0 6.0
6 6.0 7.0
... ... ...
992 6.0 993.0
993 6.0 994.0
994 6.0 995.0
995 6.0 996.0
996 6.0 997.0
997 6.0 998.0
998 6.0 999.0,
3: Index rate events
------- ------ --------
0 6.0 1.0
1 6.0 2.0
2 6.0 3.0
3 6.0 4.0
4 6.0 5.0
5 6.0 6.0
6 6.0 7.0
... ... ...
992 4.0 993.0
993 6.0 994.0
994 8.0 995.0
995 6.0 996.0
996 8.0 997.0
997 6.0 998.0
998 6.0 999.0}
We can again visualize easily:
Continuous data#
If you have continuous data, e.g. calcium imaging traces, you can use the exact same function!
compute_perievent is designed in such a way
that it recognizes the input format and decides the correct computation.
Hence, when given a Tsd, TsdFrame or even a TsdTensor, it will know what to do.
Note
compute_perievent only works with regularly
sampled continuous data. If your data is irregularly sampled, try padding it with nan first.
Single unit#
Let’s again start by simulating some data, but this time continuous traces:
We can pass continuous units (as a Tsd) to the function in the exact same way:
peth = nap.compute_perievent(data=tsd, events=stimuli, window=window)
peth
Time (s) 0 1 2 3 4 ...
---------- ---------- -------- --------- --------- --------- -----
-0.1 nan 0.67001 0.0352572 0.427523 0.108556 ...
-0.08 nan 0.235106 0.951892 0.239927 0.165249 ...
-0.06 nan 0.642842 0.918455 0.211255 0.783425 ...
-0.04 nan 0.789414 0.978445 0.386464 0.977083 ...
-0.02 nan 0.360932 0.748351 0.476296 1.1472 ...
0.0 1.08306 1.50333 0.678878 1.19077 0.99051 ...
0.02 2.00771 1.79345 1.71931 1.75735 1.48297 ...
... ...
0.28 0.600366 0.955442 0.673684 0.798336 0.554792 ...
0.3 0.819278 0.963498 0.655775 0.497935 0.914406 ...
0.32 0.202198 0.32481 0.433978 0.730133 0.102648 ...
0.34 0.147349 0.338948 0.37255 0.0411339 0.0875405 ...
0.36 0.785896 0.127397 0.386794 0.846991 0.646502 ...
0.38 0.6338 0.169052 0.894664 0.171691 0.257385 ...
0.4 0.551991 0.73567 0.575797 0.260073 0.93598 ...
dtype: float64, shape: (26, 1000)
This time, it will return a TsdFrame with a column per event.
We can again visualize, this time using a heatmap (i.e. using imshow):
Multiple units#
The same function can also handle multiple continuous units, passed as a TsdFrame:
peth = nap.compute_perievent(data=tsdframe, events=stimuli, window=window)
peth
Time (s)
---------- -----------------------------
-0.1 [[nan ... nan] ...]
-0.08 [[nan ... nan] ...]
-0.06 [[nan ... nan] ...]
-0.04 [[nan ... nan] ...]
-0.02 [[nan ... nan] ...]
0.0 [[1.083058 ... 0.885986] ...]
0.02 [[2.007713 ... 0.378317] ...]
...
0.28 [[0.600366 ... 4.078696] ...]
0.3 [[0.819278 ... 4.204869] ...]
0.32 [[0.202198 ... 4.117117] ...]
0.34 [[0.147349 ... 2.961957] ...]
0.36 [[0.785896 ... 2.018256] ...]
0.38 [[0.6338 ... 1.732266] ...]
0.4 [[0.551991 ... 0.686566] ...]
dtype: float64, shape: (26, 1000, 3)
In this case, it returns a TsdTensor, containing an added dimension for the events.
We can again visualize by reusing our plotting function:
In the most complex case, you can even pass a TsdTensor to compute_perievent.
It will return a TsdTensor with an added dimension for the events.
Visualizing such a tensor becomes a bit complex, but feel free to try!
Event-Triggered Average (ETA/STA)#
The compute_event_triggered_average computes
the average of a continuous feature aligned to a set of events.
The classic use-case is to recover the stimulus feature that drives a neuron’s spiking.
Note
In neuroscience, this is commonly known as a spike-triggered average (STA).
For convenience, compute_spike_triggered_average
is provided as an alias.
Single unit#
Let’s simulate a slowly varying stimulus and a neuron that fires preferentially at its peaks:
We can pass this to the function, passing a bin size and a window:
eta = nap.compute_event_triggered_average(feature, ts, binsize=0.02, window=(-5,5))
eta
Time (s) 0
---------- ---------
-5.0 -0.657077
-4.98 -0.656894
-4.96 -0.656608
-4.94 -0.656218
-4.92 -0.655725
-4.9 -0.655127
-4.88 -0.654427
...
4.88 -0.664481
4.9 -0.664927
4.92 -0.665268
4.94 -0.665503
4.96 -0.665634
4.98 -0.665659
5.0 -0.665579
dtype: float64, shape: (501, 1)
The result is a TsdFrame with one column. If the neuron is driven by the stimulus,
the ETA should recover the stimulus waveform preceding each spike:
Multiple units#
The same function can be used for a group of units.
When passing a TsGroup, the function returns one column per unit.
Here, we simulate units driven by the stimulus at different phases:
eta = nap.compute_event_triggered_average(feature, tsgroup, binsize=0.02, window=10.0)
eta
Time (s) 1 2 3
---------- -------- ------------ ---------
-10.0 0.657175 -0.000487287 -0.649027
-9.98 0.656984 0.00773503 -0.649103
-9.96 0.65669 0.0159561 -0.649076
-9.94 0.656292 0.0241747 -0.648943
-9.92 0.65579 0.0323937 -0.648707
-9.9 0.655185 0.0406075 -0.648369
-9.88 0.654476 0.0488192 -0.647929
...
9.88 0.65782 -0.0517478 -0.647786
9.9 0.65824 -0.0435362 -0.648461
9.92 0.658557 -0.035326 -0.649035
9.94 0.65877 -0.0271103 -0.649506
9.96 0.658879 -0.0188903 -0.649874
9.98 0.658883 -0.0106674 -0.65014
10.0 0.658784 -0.00243851 -0.650303
dtype: float64, shape: (1001, 3)
Each unit recovers a phase-shifted version of the stimulus:
Multiple features#
You can also pass multiple features at once. Here, we simulate two units each driven by a different frequency, and pass both stimulus features together:
eta = nap.compute_event_triggered_average(tsdframe, tsgroup, binsize=0.02, window=10.0)
eta
Time (s)
---------- ----------------------------
-10.0 [[ 0.501369, -0.008342] ...]
-9.98 [[ 0.501225, -0.008271] ...]
-9.96 [[ 0.501003, -0.008195] ...]
-9.94 [[ 0.500702, -0.008112] ...]
-9.92 [[ 0.500323, -0.008023] ...]
-9.9 [[ 0.499864, -0.007929] ...]
-9.88 [[ 0.499327, -0.00783 ] ...]
...
9.88 [[ 0.501959, -0.009343] ...]
9.9 [[ 0.502293, -0.009328] ...]
9.92 [[ 0.502547, -0.009308] ...]
9.94 [[ 0.502721, -0.009281] ...]
9.96 [[ 0.502818, -0.009246] ...]
9.98 [[ 0.502833, -0.009211] ...]
10.0 [[ 0.502768, -0.009169] ...]
dtype: float64, shape: (1001, 2, 2)
The result is a TsdTensor. Each unit recovers its own driving feature while the other averages to near zero: