Core methods#
Show code cell content
import pynapple as nap
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
custom_params = {"axes.spines.right": False, "axes.spines.top": False}
sns.set_theme(style="ticks", palette="colorblind", font_scale=1.5, rc=custom_params)
Time series method#
Show code cell content
tsdframe = nap.TsdFrame(t=np.arange(100), d=np.random.randn(100, 3), columns=['a', 'b', 'c'])
group = {
0: nap.Ts(t=np.sort(np.random.uniform(0, 100, 10))),
1: nap.Ts(t=np.sort(np.random.uniform(0, 100, 20))),
2: nap.Ts(t=np.sort(np.random.uniform(0, 100, 30))),
}
tsgroup = nap.TsGroup(group)
epochs = nap.IntervalSet([10, 65], [25, 80])
tsd = nap.Tsd(t=np.arange(0, 100, 1), d=np.sin(np.arange(0, 10, 0.1)))
restrict
#
restrict
is used to get time points within an IntervalSet
. This method is available
for TsGroup
, Tsd
, TsdFrame
, TsdTensor
and Ts
objects.
tsdframe.restrict(epochs)
Time (s) a b c
---------- -------- -------- --------
10.0 0.0568 0.45924 -0.79802
11.0 0.08912 0.85601 -0.72455
12.0 0.33361 0.22723 -1.21074
13.0 0.08685 -0.2578 -1.4255
14.0 0.67478 -1.38177 -0.47977
15.0 -0.4205 0.71544 0.36109
16.0 -0.06995 -1.48921 -1.56964
... ... ... ...
74.0 -0.47214 -0.682 0.61617
75.0 -0.14261 0.18166 0.79478
76.0 -0.02689 -0.73021 0.57206
77.0 0.17464 -1.38708 -0.32932
78.0 0.55766 -1.24671 -0.93825
79.0 1.18769 -2.14967 -0.59939
80.0 1.13285 -0.57188 -0.46047
dtype: float64, shape: (32, 3)
Show code cell source
plt.figure()
plt.plot(tsdframe.restrict(epochs))
[plt.axvspan(s, e, alpha=0.2) for s, e in epochs.values]
plt.xlabel("Time (s)")
plt.title("tsdframe.restrict(epochs)")
plt.xlim(0, 100)
plt.show()

This operation update the time support attribute accordingly.
print(epochs)
print(tsdframe.restrict(epochs).time_support)
index start end
0 10 25
1 65 80
shape: (2, 2), time unit: sec.
index start end
0 10 25
1 65 80
shape: (2, 2), time unit: sec.
count
#
count
the number of timestamps within bins or epochs of an IntervalSet
object.
This method is available for TsGroup
, Tsd
, TsdFrame
, TsdTensor
and Ts
objects.
With a defined bin size:
count1 = tsgroup.count(bin_size=1.0, time_units='s')
print(count1)
Time (s) 0 1 2
------------ --- --- ---
3.069049902 0.0 2.0 0.0
4.069049902 0.0 0.0 0.0
5.069049902 0.0 1.0 0.0
6.069049902 0.0 0.0 0.0
7.069049902 1.0 0.0 0.0
8.069049902 0.0 0.0 0.0
9.069049902 0.0 0.0 0.0
... ... ... ...
93.069049902 0.0 0.0 0.0
94.069049902 0.0 0.0 0.0
95.069049902 0.0 1.0 0.0
96.069049902 0.0 0.0 1.0
97.069049902 0.0 0.0 0.0
98.069049902 0.0 0.0 0.0
99.069049902 0.0 0.0 0.0
dtype: float64, shape: (97, 3)
Show code cell source
plt.figure()
plt.plot(count1[:,2], 'o-')
plt.title("tsgroup.count(bin_size=1.0)")
plt.plot(tsgroup[2].fillna(-1), '|', markeredgewidth=2)
[plt.axvline(t, linewidth=0.5, alpha=0.5) for t in np.arange(0, 21)]
plt.xlabel("Time (s)")
plt.xlim(0, 20)
plt.show()

With an IntervalSet
:
count_ep = tsgroup.count(ep=epochs)
print(count_ep)
Time (s) 0 1 2
---------- --- --- ---
17.5 1 3 5
72.5 1 2 7
dtype: float64, shape: (2, 3)
bin_average
#
bin_average
downsample time series by averaging data point falling within a bin.
This method is available for Tsd
, TsdFrame
and TsdTensor
.
tsdframe.bin_average(3.5)
Time (s) a b c
---------- -------- -------- --------
1.75 -0.07908 -0.19219 -0.17024
5.25 0.07005 -0.01622 -1.22006
8.75 0.68784 0.36713 -0.15952
12.25 0.16986 0.27515 -1.12026
15.75 -0.24224 -0.36117 -0.34666
19.25 1.46261 0.10452 -0.05626
22.75 0.28896 0.48237 -0.05696
... ... ... ...
75.25 -0.21388 -0.41018 0.661
78.75 0.76321 -1.33884 -0.58186
82.25 -0.02832 0.92648 0.47391
85.75 0.81933 0.19054 -0.27965
89.25 0.01116 -0.44579 -1.06761
92.75 0.36244 -0.44903 -1.30778
96.25 -0.71109 -0.11309 0.10058
dtype: float64, shape: (28, 3)
Show code cell source
bin_size = 3.5
plt.figure()
plt.plot(tsdframe[:,0], '.--', label="tsdframe[:,0]")
plt.plot(tsdframe[:,0].bin_average(bin_size), 'o-', label="new_tsdframe[:,0]")
plt.title(f"tsdframe.bin_average(bin_size={bin_size})")
[plt.axvline(t, linewidth=0.5, alpha=0.5) for t in np.arange(0, 21,bin_size)]
plt.xlabel("Time (s)")
plt.xlim(0, 20)
plt.legend(bbox_to_anchor=(1.0, 0.5, 0.5, 0.5))
plt.show()

interpolate
#
Theinterpolate
method of Tsd
, TsdFrame
and TsdTensor
can be used to fill gaps in a time series. It is a wrapper of numpy.interp
.
Show code cell content
tsd = nap.Tsd(t=np.arange(0, 25, 5), d=np.random.randn(5))
ts = nap.Ts(t=np.arange(0, 21, 1))
new_tsd = tsd.interpolate(ts)
Show code cell source
plt.figure()
plt.plot(new_tsd, '.-', label="new_tsd")
plt.plot(tsd, 'o', label="tsd")
plt.plot(ts.fillna(0), '+', label="ts")
plt.title("tsd.interpolate(ts)")
plt.xlabel("Time (s)")
plt.legend(bbox_to_anchor=(1.0, 0.5, 0.5, 0.5))
plt.show()

value_from
#
value_from
assign to every timestamps the closed value in time from another time series. Let’s define the time series we want to assign values from.
For every timestamps in tsgroup
, we want to assign the closest value in time from tsd
.
Show code cell content
tsd = nap.Tsd(t=np.arange(0, 100, 1), d=np.sin(np.arange(0, 10, 0.1)))
tsgroup_from_tsd = tsgroup.value_from(tsd)
We can display the first element of tsgroup
and tsgroup_sin
.
Show code cell source
plt.figure()
plt.plot(tsgroup[0].fillna(0), "|", label="tsgroup[0]", markersize=20, mew=3)
plt.plot(tsd, linewidth=2, label="tsd")
plt.plot(tsgroup_from_tsd[0], "o", label = "tsgroup_from_tsd[0]", markersize=20)
plt.title("tsgroup.value_from(tsd)")
plt.xlabel("Time (s)")
plt.yticks([-1, 0, 1])
plt.legend(bbox_to_anchor=(1.0, 0.5, 0.5, 0.5))
plt.show()

threshold
#
The method threshold
of Tsd
returns a new Tsd
with all the data above or
below a certain threshold. Default is above
. The time support
of the new Tsd
object get updated accordingly.
Show code cell content
tsd = nap.Tsd(t=np.arange(0, 100, 1), d=np.sin(np.arange(0, 10, 0.1)))
tsd_above = tsd.threshold(0.5, method='above')
This method can be used to isolate epochs for which a signal is above/below a certain threshold.
epoch_above = tsd_above.time_support
Show code cell source
plt.figure()
plt.plot(tsd, label="tsd")
plt.plot(tsd_above, 'o-', label="tsd_above")
[plt.axvspan(s, e, alpha=0.2) for s, e in epoch_above.values]
plt.axhline(0.5, linewidth=0.5, color='grey')
plt.legend()
plt.xlabel("Time (s)")
plt.title("tsd.threshold(0.5)")
plt.show()

Mapping between TsGroup
and Tsd
#
It’s is possible to transform a TsGroup
to Tsd
with the method
to_tsd
and a Tsd
to TsGroup
with the method to_tsgroup
.
This is useful to flatten the activity of a population in a single array.
tsd = tsgroup.to_tsd()
print(tsd)
Time (s)
------------ --
2.569049902 1
3.055166862 1
4.687394462 1
7.052363538 0
9.80798574 1
10.373993851 2
14.060300279 0
...
88.288924056 2
90.165848251 1
91.090401085 2
95.311795343 1
96.342859159 2
99.725873327 2
99.934036729 0
dtype: float64, shape: (60,)
The object tsd
contains all the timestamps of the tsgroup
with
the associated value being the index of the unit in the TsGroup
.
The method to_tsgroup
converts the Tsd
object back to the original TsGroup
.
back_to_tsgroup = tsd.to_tsgroup()
print(back_to_tsgroup)
Index rate
------- -------
0 0.10271
1 0.20541
2 0.30812
Parameterizing a raster#
The method to_tsd
makes it easier to display a raster plot.
TsGroup
object can be plotted with plt.plot(tsgroup.to_tsd(), 'o')
.
Timestamps can be mapped to any values passed directly to the method
or by giving the name of a specific metadata name of the TsGroup
.
tsgroup['label'] = np.arange(3)*np.pi
print(tsgroup)
Index rate label
------- ------- -------
0 0.10271 0
1 0.20541 3.14159
2 0.30812 6.28319
Show code cell source
plt.figure()
plt.subplot(2,2,1)
plt.plot(tsgroup.to_tsd(), '|')
plt.title("tsgroup.to_tsd()")
plt.xlabel("Time (s)")
plt.subplot(2,2,2)
plt.plot(tsgroup.to_tsd([10,20,30]), '|')
plt.title("togroup.to_tsd([10,20,30])")
plt.xlabel("Time (s)")
plt.subplot(2,2,3)
plt.plot(tsgroup.to_tsd("label"), '|')
plt.title("togroup.to_tsd('label')")
plt.xlabel("Time (s)")
plt.tight_layout()
plt.show()

Special slicing : TsdFrame#
For users that are familiar with pandas, TsdFrame
is the closest object to a DataFrame.
but there are distinctive behavior when slicing the object. TsdFrame
behaves primarily like a numpy array. This section
lists all the possible ways of slicing TsdFrame
.
1. If not column labels are passed#
tsdframe = nap.TsdFrame(t=np.arange(4), d=np.random.randn(4,3))
print(tsdframe)
Time (s) 0 1 2
---------- -------- -------- --------
0 0.77488 -0.51209 0.74499
1 1.14559 -2.07648 -0.50408
2 -0.59553 -0.4384 -0.93154
3 0.14372 0.10143 0.61109
dtype: float64, shape: (4, 3)
Slicing should be done like numpy array :
tsdframe[0]
array([ 0.77487908, -0.51208695, 0.74499319])
tsdframe[:, 1]
Time (s)
---------- ---------
0 -0.512087
1 -2.07648
2 -0.438399
3 0.101433
dtype: float64, shape: (4,)
tsdframe[:, [0, 2]]
Time (s) 0 2
---------- -------- --------
0 0.77488 0.74499
1 1.14559 -0.50408
2 -0.59553 -0.93154
3 0.14372 0.61109
dtype: float64, shape: (4, 2)
2. If column labels are passed as integers#
The typical case is channel mapping. The order of the columns on disk are different from the order of the columns on the recording device it corresponds to.
tsdframe = nap.TsdFrame(t=np.arange(4), d=np.random.randn(4,4), columns = [3, 2, 0, 1])
print(tsdframe)
Time (s) 3 2 0 1
---------- -------- -------- -------- --------
0 0.69472 0.77684 1.09459 -0.33094
1 0.13952 0.85108 0.5572 -0.65863
2 0.45981 0.55413 0.67856 -1.29164
3 -1.00913 -0.05351 -0.73256 0.86612
dtype: float64, shape: (4, 4)
In this case, indexing like numpy still has priority which can led to confusing behavior :
tsdframe[:, [0, 2]]
Time (s) 3 0
---------- -------- --------
0 0.69472 1.09459
1 0.13952 0.5572
2 0.45981 0.67856
3 -1.00913 -0.73256
dtype: float64, shape: (4, 2)
Note how this corresponds to column labels 3 and 0.
To slice using column labels only, the TsdFrame
object has the loc
method similar to Pandas :
tsdframe.loc[[0, 2]]
Time (s) 0 2
---------- -------- --------
0 1.09459 0.77684
1 0.5572 0.85108
2 0.67856 0.55413
3 -0.73256 -0.05351
dtype: float64, shape: (4, 2)
In this case, this corresponds to columns labelled 0 and 2.
3. If column labels are passed as strings#
Similar to Pandas, it is possible to label columns using strings.
tsdframe = nap.TsdFrame(t=np.arange(4), d=np.random.randn(4,3), columns = ["kiwi", "banana", "tomato"])
print(tsdframe)
Time (s) kiwi banana tomato
---------- -------- -------- --------
0 0.89579 -2.30105 -1.60554
1 1.78596 -0.30575 0.70787
2 -0.01839 -0.90192 -0.10672
3 2.09631 -0.03917 -1.74431
dtype: float64, shape: (4, 3)
When the column labels are all strings, it is possible to use either direct bracket indexing or using the loc
method:
print(tsdframe['kiwi'])
print(tsdframe.loc['kiwi'])
Time (s)
---------- ---------
0 0.895788
1 1.78596
2 -0.018389
3 2.09631
dtype: float64, shape: (4,)
Time (s)
---------- ---------
0 0.895788
1 1.78596
2 -0.018389
3 2.09631
dtype: float64, shape: (4,)
4. If column labels are mixed type#
It is possible to mix types in column names.
tsdframe = nap.TsdFrame(t=np.arange(4), d=np.random.randn(4,3), columns = ["kiwi", 0, np.pi])
print(tsdframe)
Time (s) kiwi 0 3.141592653589793
---------- -------- -------- -------------------
0 -1.71526 -0.92588 -0.5932
1 0.97876 -0.07252 -0.70301
2 0.43592 1.08071 1.21761
3 -1.26454 0.40326 -0.49799
dtype: float64, shape: (4, 3)
Direct bracket indexing only works if the column label is a string.
print(tsdframe['kiwi'])
Time (s)
---------- ---------
0 -1.71526
1 0.978762
2 0.43592
3 -1.26454
dtype: float64, shape: (4,)
To slice with mixed types, it is best to use the loc
method :
print(tsdframe.loc[['kiwi', np.pi]])
Time (s) kiwi 3.141592653589793
---------- -------- -------------------
0 -1.71526 -0.5932
1 0.97876 -0.70301
2 0.43592 1.21761
3 -1.26454 -0.49799
dtype: float64, shape: (4, 2)
In general, it is probably a bad idea to mix types when labelling columns.
Interval sets methods#
Interaction between epochs#
epoch1 = nap.IntervalSet(start=0, end=10) # no time units passed. Default is us.
epoch2 = nap.IntervalSet(start=[5, 30], end=[20, 45])
print(epoch1, "\n")
print(epoch2, "\n")
index start end
0 0 10
shape: (1, 2), time unit: sec.
index start end
0 5 20
1 30 45
shape: (2, 2), time unit: sec.
union
#
epoch = epoch1.union(epoch2)
print(epoch)
index start end
0 0 20
1 30 45
shape: (2, 2), time unit: sec.
intersect
#
epoch = epoch1.intersect(epoch2)
print(epoch)
index start end
0 5 10
shape: (1, 2), time unit: sec.
set_diff
#
epoch = epoch1.set_diff(epoch2)
print(epoch)
index start end
0 0 5
shape: (1, 2), time unit: sec.
split
#
Useful for chunking time series, the split
method splits an IntervalSet
in a new
IntervalSet
based on the interval_size
argument.
epoch = nap.IntervalSet(start=0, end=100)
print(epoch.split(10, time_units="s"))
index start end
0 0 10
1 10 20
2 20 30
3 30 40
4 40 50
5 50 60
6 60 70
7 70 80
8 80 90
9 90 100
shape: (10, 2), time unit: sec.
Drop intervals#
epoch = nap.IntervalSet(start=[5, 30], end=[6, 45])
print(epoch)
index start end
0 5 6
1 30 45
shape: (2, 2), time unit: sec.
drop_short_intervals
#
print(
epoch.drop_short_intervals(threshold=5)
)
index start end
0 30 45
shape: (1, 2), time unit: sec.
drop_long_intervals
#
print(
epoch.drop_long_intervals(threshold=5)
)
index start end
0 5 6
shape: (1, 2), time unit: sec.
merge_close_intervals
#
Show code cell source
epoch = nap.IntervalSet(start=[1, 7], end=[6, 45])
print(epoch)
index start end
0 1 6
1 7 45
shape: (2, 2), time unit: sec.
If two intervals are closer than the threshold
argument, they are merged.
print(
epoch.merge_close_intervals(threshold=2.0)
)
index start end
0 1 45
shape: (1, 2), time unit: sec.