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)

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")
            start    end
       0        0     10
shape: (1, 2), time unit: sec. 

            start    end
       0        5     20
       1       30     45
shape: (2, 2), time unit: sec. 

union#

epoch = epoch1.union(epoch2)
print(epoch)
            start    end
       0        0     20
       1       30     45
shape: (2, 2), time unit: sec.

intersect#

epoch = epoch1.intersect(epoch2)
print(epoch)
            start    end
       0        5     10
shape: (1, 2), time unit: sec.

set_diff#

epoch = epoch1.set_diff(epoch2)
print(epoch)
            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"))
            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)
            start    end
       0        5      6
       1       30     45
shape: (2, 2), time unit: sec.

drop_short_intervals#

print(
    epoch.drop_short_intervals(threshold=5)
    )
            start    end
       0       30     45
shape: (1, 2), time unit: sec.

drop_long_intervals#

print(
    epoch.drop_long_intervals(threshold=5)
    )
            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)
            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)
    )
            start    end
       0        1     45
shape: (1, 2), time unit: sec.

Metadata#

One advantage of grouping time series is that metainformation can be added directly on an element-wise basis. In this case, we add labels to each Ts object when instantiating the group and after. We can then use this label to split the group. See the TsGroup documentation for a complete methodology for splitting TsGroup objects.

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))),
}
time_support = nap.IntervalSet(0, 100)

tsgroup = nap.TsGroup(group, time_support=time_support)

tsgroup['label1'] = np.random.randn(3)

tsgroup.set_info(label2 = ['a', 'b', 'c'])

print(tsgroup, "\n")
  Index    rate    label1  label2
-------  ------  --------  --------
      0     0.1  -0.38498  a
      1     0.2  -0.36197  b
      2     0.3   0.97355  c 

Time series method#

Hide code cell content
tsdframe = nap.TsdFrame(t=np.arange(100), d=np.random.randn(100, 3), columns=['a', 'b', 'c'])
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        -1.29879  -0.37761   0.63836
11.0         0.31305  -1.21007   3.41824
12.0        -1.22761   0.66482  -0.28102
13.0         0.26912   0.02872   1.70389
14.0        -1.3437    1.27422  -0.96464
15.0        -0.17449   1.80624  -1.61886
16.0        -0.62451  -0.5072    0.94247
...
74.0        -0.60337   1.2595    0.47252
75.0        -0.62192  -0.68012  -0.24489
76.0        -1.22179  -0.23589   2.07311
77.0         0.75754   1.50113   0.99764
78.0         0.94284  -1.02706   0.409
79.0        -1.23968  -0.52289   1.09597
80.0         0.93847  -0.34655  -1.2611
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/1bf7297c386413d71661728cc7aff07b1344cbdfd78dde784026016f7e488c5a.png

This operation update the time support attribute accordingly.

print(epochs)
print(tsdframe.restrict(epochs).time_support) 
            start    end
       0       10     25
       1       65     80
shape: (2, 2), time unit: sec.
            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
----------  ---  ---  ---
0.5           0    0    0
1.5           0    0    0
2.5           0    1    0
3.5           0    0    0
4.5           0    0    1
5.5           0    0    1
6.5           0    0    0
...
93.5          0    0    0
94.5          0    0    0
95.5          0    0    0
96.5          1    0    0
97.5          0    1    0
98.5          0    0    1
99.5          0    0    0
dtype: float64, shape: (100, 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/6317fb436b78b111f2385146d3ffff7d6dadc58d9a06f8e43a232d6a3ea15cce.png

With an IntervalSet:

count_ep = tsgroup.count(ep=epochs)

print(count_ep)
Time (s)      0    1    2
----------  ---  ---  ---
17.5          4    4    7
72.5          1    3    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.19445   0.57608   0.72252
5.25        -0.19095   0.11687   0.03488
8.75         0.13184  -0.59322   1.22942
12.25       -0.21515  -0.17218   1.61371
15.75       -0.87916   0.04059  -0.51584
19.25       -0.62883  -0.05773  -0.07907
22.75        0.74808  -0.25512  -0.63578
...
75.25       -0.81569   0.1145    0.76691
78.75        0.34979  -0.09884   0.31038
82.25       -0.16756  -1.16021  -0.19153
85.75        0.38568   0.27616   0.73901
89.25        0.65452   0.70026  -0.41582
92.75       -0.47837  -0.45975  -0.31926
96.25       -1.5642    0.29827  -0.3323
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/b7224f27d43e4108768ffe41c2a3d31b8f9c69afb0b5e62a38d70f6006eb1b9a.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/62680faef19d74cc3c03d63a5d144ca928bbde3b45598991f750fd5b71de53a6.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/3dd6271a71a2cfe5ebb933b2e56e45ad081ab84d8842b9617c19d8d5028f8a45.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/72bd946fe703b4fc657ae8ef534cafd84a6a17ed142fdb115f07cff857e8c6ff.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.970909468    1
4.776047248    2
5.87049296     2
7.023850764    2
7.96902467     2
10.448590986   2
10.983792172   2
...
84.753482109   2
86.493418102   1
89.768121616   1
92.184028717   2
96.035215503   0
97.771961471   1
98.00524504    2
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.1
      1     0.2
      2     0.3

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    label1  label2      label
-------  ------  --------  --------  -------
      0     0.1  -0.38498  a         0
      1     0.2  -0.36197  b         3.14159
      2     0.3   0.97355  c         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/d5fda4c20fd25f69e42e7fd87d3e5f092b062a3e015fa092cd396a84e3c9be5e.png