Core methods#

Hide 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#

Hide 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.07437  -0.39901  -0.47757
11.0        -0.6925   -0.10357  0.14988
12.0        1.3562    -2.02556  1.11611
13.0        1.20897   -0.26968  1.75129
14.0        -0.33195  -1.18659  -2.56531
15.0        0.53603   -0.52528  -0.68526
16.0        1.00245   1.37059   0.26432
...         ...       ...       ...
74.0        -0.03285  0.18247   0.23737
75.0        0.86818   1.08719   1.94957
76.0        2.15747   0.41758   0.54125
77.0        -0.53152  -1.12629  -0.98165
78.0        -0.44976  0.00443   -0.73719
79.0        -0.09992  -0.43672  0.22542
80.0        0.65943   -1.10717  -0.65823
dtype: float64, shape: (32, 3)
Hide 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()
../_images/ba0d13396723889d6a0ad5b2b130396ba53393b33c71161b31bab4afc057e221.png

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
------------  ---  ---  ---
2.946110497   0.0  1.0  0.0
3.946110497   0.0  1.0  0.0
4.946110497   0.0  0.0  0.0
5.946110497   1.0  0.0  0.0
6.946110497   0.0  0.0  0.0
7.946110497   0.0  1.0  1.0
8.946110497   0.0  0.0  0.0
...           ...  ...  ...
89.946110497  0.0  0.0  0.0
90.946110497  0.0  0.0  0.0
91.946110497  0.0  0.0  1.0
92.946110497  0.0  0.0  0.0
93.946110497  0.0  0.0  1.0
94.946110497  0.0  0.0  0.0
95.946110497  0.0  0.0  2.0
dtype: float64, shape: (94, 3)
Hide 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()
../_images/f4f90e75785a8d7a5193b46b092f0291eb6655b2ae29adb58f03aa07dfb485d5.png

With an IntervalSet:

count_ep = tsgroup.count(ep=epochs)

print(count_ep)
Time (s)      0    1    2
----------  ---  ---  ---
17.5          2    3    4
72.5          0    2    6
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.40446   0.20579   0.56676
5.25        -0.35795  0.39361   -0.63057
8.75        0.37019   0.0311    -0.40365
12.25       0.62422   -0.7996   1.00576
15.75       0.07377   -0.15213  -0.63236
19.25       0.60779   0.07375   -0.02707
22.75       -0.84416  -0.1539   0.07897
...         ...       ...       ...
75.25       0.9976    0.56241   0.9094
78.75       -0.10544  -0.66644  -0.53791
82.25       -0.20844  -0.24116  0.14887
85.75       0.00608   0.54024   -0.73285
89.25       -0.53845  -0.20702  0.38436
92.75       -1.04505  -0.18367  -0.08722
96.25       0.51846   -0.60597  -0.44136
dtype: float64, shape: (28, 3)
Hide 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()
../_images/b78ee13f79e1c5d1d2720b192ef8b943dd4e3f50eab6a73bfa2490fc901fb901.png

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.

Hide 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)
Hide 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()
../_images/094cd20731572e4bf71a662f008dd8d783dd10dddf6f4e02f7417b3f4ac0708b.png

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.

Hide 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.

Hide 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()
../_images/491a9e5c225c89fd94aa49b10e53586d8670f3974ba43d6872aaaea8a0756220.png

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.

Hide 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
Hide 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()
../_images/80a39ba5f7ade7f37015176686a51a30a9f57efe6d420cd4d1c133c6bbe7c452.png

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.446110497    1
4.189418152    1
5.936728309    0
7.712918414    2
8.169440509    1
10.712289082   1
10.862703771   2
...
81.989991912   2
87.898007276   1
91.849369293   2
93.715922237   2
95.598101586   2
96.378723161   2
96.79890474    1
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.10599
      1  0.21197
      2  0.31796

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.10599  0
      1  0.21197  3.14159
      2  0.31796  6.28319
Hide 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()
../_images/19d4862360df048cae2299cd649e65f6c0b6db316d9f6883af678a682f559cf7.png

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.18944  -1.25477  -0.68773
1           -0.45807   0.49043   0.05497
2           -0.8381    0.4615    0.10843
3           -1.65987   1.15394  -0.2774
dtype: float64, shape: (4, 3)

Slicing should be done like numpy array :

tsdframe[0]
Time (s)           0         1         2
----------  --------  --------  --------
0           -0.18944  -1.25477  -0.68773
dtype: float64, shape: (1, 3)
tsdframe[:, 1]
Time (s)
----------  ---------
0           -1.25477
1            0.49043
2            0.461502
3            1.15394
dtype: float64, shape: (4,)
tsdframe[:, [0, 2]]
Time (s)           0         2
----------  --------  --------
0           -0.18944  -0.68773
1           -0.45807   0.05497
2           -0.8381    0.10843
3           -1.65987  -0.2774
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.11193   0.65136   0.41097  -1.35246
1           -0.11881  -0.61744  -2.33614   0.06487
2           -0.52308   0.47509  -0.05871  -0.88657
3           -1.82886  -1.67125  -0.42887  -1.55415
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.11193   0.41097
1           -0.11881  -2.33614
2           -0.52308  -0.05871
3           -1.82886  -0.42887
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            0.41097   0.65136
1           -2.33614  -0.61744
2           -0.05871   0.47509
3           -0.42887  -1.67125
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.96781  -0.19439  -1.78588
1           -0.22928   0.5258   -1.40102
2            0.48731  -1.10614  -0.15255
3           -0.46802  -0.88212   1.65717
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.96781
1           -0.229283
2            0.487306
3           -0.468021
dtype: float64, shape: (4,)
Time (s)
----------  ---------
0           -0.96781
1           -0.229283
2            0.487306
3           -0.468021
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           0.5132    0.42419             -0.35567
1           0.76421   1.06022             -0.38623
2           0.60681  -0.9064              -0.76589
3           0.37988  -0.78232             -1.40217
dtype: float64, shape: (4, 3)

Direct bracket indexing only works if the column label is a string.

print(tsdframe['kiwi'])
Time (s)
----------  --------
0           0.513205
1           0.764205
2           0.606808
3           0.379876
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           0.5132              -0.35567
1           0.76421             -0.38623
2           0.60681             -0.76589
3           0.37988             -1.40217
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#

Hide 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.