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, "figure.figsize": (8, 4)}
sns.set_context("paper") 
sns.set_theme(style="ticks", palette="colorblind", font_scale=1.3, rc=custom_params)

Time series methods#

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, time_support = nap.IntervalSet(0, 100))
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.19925   0.07987   0.06787
11.0        0.08398   -0.61482  -1.70552
12.0        -2.22681  -0.15132  0.17359
13.0        0.6409    1.08896   0.86976
14.0        0.17155   1.86466   1.6397
15.0        0.35112   0.03573   -0.82936
16.0        0.48691   0.03401   -0.98133
...         ...       ...       ...
74.0        0.70075   0.32404   -0.17514
75.0        0.30855   -0.23013  -1.44083
76.0        0.76278   1.21897   0.5059
77.0        1.06303   -1.03094  -0.73017
78.0        -1.63542  2.6729    0.31074
79.0        -1.80983  1.83099   0.85133
80.0        0.08852   -1.21296  0.11458
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/bf8e4b31b56a6501571e85c3c1dd42c110c2bd3263939be0e401dc5bd4efc5e3.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 returns 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:

count = tsgroup.count(bin_size=1.0, time_units='s')
print(count) 
Time (s)    0    1    2
----------  ---  ---  ---
0.5         0    0    2
1.5         0    0    0
2.5         0    0    0
3.5         0    0    0
4.5         0    0    1
5.5         0    0    1
6.5         1    0    0
...         ...  ...  ...
93.5        0    0    1
94.5        0    0    0
95.5        0    0    0
96.5        0    0    0
97.5        0    0    1
98.5        0    0    1
99.5        0    0    0
dtype: int64, shape: (100, 3)
Hide code cell source
plt.figure()
plt.step(count.t, count[:,2], where='mid', label="count[:,2]")
plt.title("tsgroup.count(bin_size=1.0)")
plt.plot(tsgroup[2].fillna(-0.5), '|', markeredgewidth=3, label="tsgroup[2]")
[plt.axvline(t, linewidth=0.5, alpha=0.5) for t in np.arange(0, 31)]
plt.xlabel("Time (s)")
plt.xlim(0, 30)
plt.legend()
plt.show()
../_images/bca0af3d4a936806610bbca70b7fc5d4f6e9547a07308f4d4f852ae40cbe9477.png

With an IntervalSet:

count_ep = tsgroup.count(ep=epochs)

print(count_ep)
Time (s)      0    1    2
----------  ---  ---  ---
17.5          1    5    7
72.5          1    2    5
dtype: int64, shape: (2, 3)

trial_count#

TsGroup and Ts objects each have the method trial_count, which builds a trial-based count tensor from an IntervalSet object. Similar to count, this function requires a bin_size parameter which determines the number of time bins within each trial. The resulting tensor has shape (number of group elements, number of trials, number of time bins) for TsGroup objects, or (number of trials, number of time bins) for Ts objects.

ep = nap.IntervalSet([5, 17, 30, 50], metadata={'trials':[1, 2]})
tensor = tsgroup.trial_count(ep, bin_size=2)
print(tensor, "\n")
print("Tensor shape = ", tensor.shape)
[[[ 1.  0.  0.  0.  0.  1. nan nan nan nan]
  [ 0.  2.  0.  0.  1.  0.  0.  0.  0.  0.]]

 [[ 0.  2.  0.  0.  1.  1. nan nan nan nan]
  [ 0.  1.  0.  1.  1.  1.  1.  0.  1.  0.]]

 [[ 1.  0.  1.  2.  0.  1. nan nan nan nan]
  [ 0.  0.  1.  1.  1.  0.  0.  0.  0.  0.]]] 

Tensor shape =  (3, 2, 10)
Hide code cell source
color_cycle = plt.rcParams['axes.prop_cycle'].by_key()['color']
from matplotlib.colors import LinearSegmentedColormap
plt.figure()
gs = plt.GridSpec(3,2)
plt.subplot(gs[:,0])
for i, n in enumerate(tsgroup.keys()):
    plt.plot(tsgroup[n].fillna(i+1), '|', markeredgewidth=3, color=color_cycle[i])
for i in range(len(ep)):
    plt.axvspan(ep[i,0], ep[i,1], alpha=0.2)
    [plt.axvline(t, linewidth=0.5, alpha=0.5) for t in np.arange(ep[i,0], ep[i,1], 2.0)]
plt.title("tsgroup")
plt.ylim(0, len(tsgroup)+1)
plt.xlim(0, 60)
plt.xlabel("Time (s)")

for i in range(3):
    plt.subplot(gs[2-i,1])
    cmap = LinearSegmentedColormap.from_list("fade", ["lightgrey", color_cycle[i]])
    plt.pcolormesh(np.arange(0, tensor.shape[-1]), [1, 2], tensor[i], cmap=cmap)
    if i == 1: plt.ylabel("Trials")    
    if i == 2: plt.title("tsgroup.trial_cout(ep, bin_size=2)")
    if i == 0: plt.xlabel("Trial time")
    plt.text(1, 0.5, f"tensor[{i}]", transform=plt.gca().transAxes)
plt.tight_layout()
plt.show()
../_images/8751df2a081e1b59fa3d2f77dcb2688f4f9af1577e856c054e358c3bdd340392.png

The array is padded with NaNs when the trials have uneven durations, The padding value can be controlled using the parameter padding_value. Additionally, the parameter align can change whether the count is aligned to the “start” or “end” of each trial.

tensor = tsgroup.trial_count(ep, bin_size=2, align="end", padding_value=-1)
print(tensor, "\n")
print("Tensor shape = ", tensor.shape)
[[[-1. -1. -1. -1.  1.  0.  0.  0.  0.  1.]
  [ 0.  2.  0.  0.  1.  0.  0.  0.  0.  0.]]

 [[-1. -1. -1. -1.  0.  2.  0.  0.  1.  1.]
  [ 0.  1.  0.  1.  1.  1.  1.  0.  1.  0.]]

 [[-1. -1. -1. -1.  1.  0.  1.  2.  0.  1.]
  [ 0.  0.  1.  1.  1.  0.  0.  0.  0.  0.]]] 

Tensor shape =  (3, 2, 10)

bin_average#

bin_average downsamples time series by averaging data point falling within a bin. This method is available for Tsd, TsdFrame and TsdTensor. While bin_average is good for downsampling with precise control of the resulting bins, it does not apply any antialiasing filter. The function decimate is also available for down-sampling without aliasing.

tsdframe.bin_average(3.5)
Time (s)    a         b         c
----------  --------  --------  --------
1.75        -0.264    0.23391   0.02838
5.25        0.61354   -0.29581  -1.18755
8.75        0.42294   0.5498    0.2
12.25       -0.50064  0.1076    -0.22072
15.75       0.26093   0.2692    0.33027
19.25       -0.352    0.69107   0.00492
22.75       -0.58785  -0.28437  -0.47073
...         ...       ...       ...
75.25       0.5907    0.43763   -0.37002
78.75       -0.57342  0.56499   0.13662
82.25       -0.16531  0.93403   0.52074
85.75       -1.04863  -0.33702  0.601
89.25       1.20919   0.11223   0.74397
92.75       -0.06854  0.43621   -0.12781
96.25       -0.616    -0.73763  0.11503
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/78596781f14ab4505a007b15371c4c35de9dc8ce8bbf8aab42c0e530765a808f.png

decimate#

The decimate method downsamples the time series by an integer factor after an antialiasing filter.

Hide code cell source
noisy_data = np.random.rand(100) + np.sin(np.linspace(0, 2 * np.pi, 100))
tsd = nap.Tsd(t=np.arange(100), d=noisy_data, time_support=nap.IntervalSet(0, 100))
new_tsd = tsd.decimate(down=4)

The original time series was sampled at 1Hz. The new time series has a rate of 0.25 Hz.

print(f"Original rate : {tsd.rate}")
print(f"New rate : {new_tsd.rate}") 
Original rate : 1.0
New rate : 0.25
Hide code cell source
plt.figure()
plt.plot(tsd, label="original")
plt.plot(new_tsd, marker="o", label="decimate")
plt.plot(tsd[::4], marker="o", label="naive downsample")
plt.legend()
plt.show()
../_images/c793aca2675a80ce353aad600942f17156c01597caed642a0ce4bd6b504fe9d8.png

interpolate#

The interpolate 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/00b164f47af5cb8a4408b6bad01e518082bee29ee77ebf4290c07b6d73553d5a.png

value_from#

By default, value_from assigns to timestamps the closest 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/cfcd930ea747d910c83886aa317a846ba1fb2dc3775d7726057c240fa630f289.png

The argument mode can control if the nearest target time is taken before or after the reference time.

Hide code cell content
tsd = nap.Tsd(t=np.arange(0, 10, 1), d=np.arange(0, 100, 10))
ts = nap.Ts(t=np.arange(0.5, 9, 1))

In this case, the variable ts receive data from the time point before.

new_ts_before = ts.value_from(tsd, mode="before")
Hide code cell source
plt.figure()
plt.plot(ts.fillna(-1), "|", label="ts", markersize=20, mew=3)
plt.plot(tsd, "*-", linewidth=2, label="tsd")
plt.plot(new_ts_before, "o-", label = "new_ts_before", markersize=10)
plt.title("ts.value_from(tsd, mode='before')")
plt.xlabel("Time (s)")
plt.legend(bbox_to_anchor=(1.0, 0.5, 0.5, 0.5))
plt.show()
../_images/584731e043b1685133c53a103552990da7738f6805aafac26d234012e26feaf8.png
Hide code cell source
new_ts_after = ts.value_from(tsd, mode="after")
plt.figure()
plt.plot(ts.fillna(-1), "|", label="ts", markersize=20, mew=3)
plt.plot(tsd, "*-", linewidth=2, label="tsd")
plt.plot(new_ts_after, "o-", label = "new_ts_after", markersize=10)
plt.title("ts.value_from(tsd, mode='after')")
plt.xlabel("Time (s)")
plt.legend(bbox_to_anchor=(1.0, 0.5, 0.5, 0.5))
plt.show()
../_images/be917e5f01182970af0d9fcd148abd436fd1486df2f6411dd2e7e4114a2ad05d.png

If there is no time point found before or after or within the interval, the function assigns Nans.

tsd = nap.Tsd(t=np.arange(1, 10, 1), d=np.arange(10, 100, 10))
ep = nap.IntervalSet(start=0, end = 10)
ts = nap.Ts(t=[0, 9])

# First ts is at 0s. First tsd is at 1s.
ts.value_from(tsd, ep=ep, mode="before")
Time (s)
----------  ---
0           nan
9            90
dtype: float64, shape: (2,)

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/8910ae9d672769431b26effad533fb645cd8f7945096169a67bbb05a085014a2.png

derivative#

The derivative method of Tsd, TsdFrame and TsdTensor can be used to calculate the derivative of a time series with respect to time. It is a wrapper of numpy.gradient.

Hide code cell content
tsd = nap.Tsd(
    t=np.arange(0, 10, 0.1),
    d=np.sin(np.arange(0, 10, 0.1)),
)
ep = nap.IntervalSet(start=[0, 6], end=[4, 10])
derivative = tsd.derivative(ep=ep)
Hide code cell source
plt.figure()
plt.plot(tsd, label="tsd")
plt.plot(derivative, 'o-', label="derivative")
[plt.axvspan(s, e, alpha=0.2) for s, e in derivative.time_support.values]
plt.axhline(0, linewidth=0.5, color='grey')
plt.legend(loc="lower right")
plt.xlabel("Time (s)")
plt.title("tsd.derivative()")
plt.show()
../_images/e27f0f89a0cae830734bf0022ac1552cec2d24e96927ca192951bb37c97db3f9.png

to_trial_tensor#

Tsd, TsdFrame, and TsdTensor all have the method to_trial_tensor, which creates a numpy array from an IntervalSet by slicing the time series. The resulting tensor has shape (shape of time series, number of trials, number of time points), where the first dimension(s) is dependent on the object.

tsd = nap.Tsd(t=np.arange(0, 100, 1), d=np.sin(np.arange(0, 10, 0.1))) 
ep = nap.IntervalSet([0, 10, 30, 50, 70, 75], metadata={'trials':[1, 2, 3]})
print(ep)
  index    start    end    trials
      0        0     10         1
      1       30     50         2
      2       70     75         3
shape: (3, 2), time unit: sec.

The following example returns a tensor with shape (3, 21), for 3 trials and 21 time points, where the first dimension is dropped due to this being a Tsd object.

tensor = tsd.to_trial_tensor(ep)
print(tensor, "\n")
print("Tensor shape = ", tensor.shape)
[[ 0.          0.09983342  0.19866933  0.29552021  0.38941834  0.47942554
   0.56464247  0.64421769  0.71735609  0.78332691  0.84147098         nan
          nan         nan         nan         nan         nan         nan
          nan         nan         nan]
 [ 0.14112001  0.04158066 -0.05837414 -0.15774569 -0.2555411  -0.35078323
  -0.44252044 -0.52983614 -0.61185789 -0.68776616 -0.7568025  -0.81827711
  -0.87157577 -0.91616594 -0.95160207 -0.97753012 -0.993691   -0.99992326
  -0.99616461 -0.98245261 -0.95892427]
 [ 0.6569866   0.72896904  0.79366786  0.85043662  0.8987081   0.93799998
          nan         nan         nan         nan         nan         nan
          nan         nan         nan         nan         nan         nan
          nan         nan         nan]] 

Tensor shape =  (3, 21)
Hide code cell source
color_cycle = plt.rcParams['axes.prop_cycle'].by_key()['color']
plt.figure()
plt.subplot(121)
plt.plot(tsd, '-', label="tsd")
for i in range(len(ep)):
    plt.plot(tsd.get(ep[i,0], ep[i,1]), 'o-', color=color_cycle[i]) 
    plt.axvspan(ep[i,0], ep[i,1], alpha=0.2)
plt.legend(loc="lower right")
plt.xlabel("Time (s)")
plt.subplot(122)
plt.plot(tensor.T, 'o-')
plt.title("tsd.to_trial_tensor(ep)")
plt.tight_layout()
plt.xlabel("Trial time")
plt.show()
../_images/8303a356a79df0d643a85822256cf85955ca5795d5375bb1f1711bb3ed882f1a.png

Since trial 2 is twice as long as trial 1, the array is padded with NaNs. The padding value can be changed by setting the parameter padding_value.

tensor = tsd.to_trial_tensor(ep, padding_value=-1)
print(tensor, "\n")
print("Tensor shape = ", tensor.shape)
[[ 0.          0.09983342  0.19866933  0.29552021  0.38941834  0.47942554
   0.56464247  0.64421769  0.71735609  0.78332691  0.84147098 -1.
  -1.         -1.         -1.         -1.         -1.         -1.
  -1.         -1.         -1.        ]
 [ 0.14112001  0.04158066 -0.05837414 -0.15774569 -0.2555411  -0.35078323
  -0.44252044 -0.52983614 -0.61185789 -0.68776616 -0.7568025  -0.81827711
  -0.87157577 -0.91616594 -0.95160207 -0.97753012 -0.993691   -0.99992326
  -0.99616461 -0.98245261 -0.95892427]
 [ 0.6569866   0.72896904  0.79366786  0.85043662  0.8987081   0.93799998
  -1.         -1.         -1.         -1.         -1.         -1.
  -1.         -1.         -1.         -1.         -1.         -1.
  -1.         -1.         -1.        ]] 

Tensor shape =  (3, 21)

By default, time series are aligned to the start of each trial. To align the time series to the end of each trial, the optional parameter align can be set to “end”.

tensor = tsd.to_trial_tensor(ep, align="end")
print(tensor, "\n")
print("Tensor shape = ", tensor.shape)
[[        nan         nan         nan         nan         nan         nan
          nan         nan         nan         nan  0.          0.09983342
   0.19866933  0.29552021  0.38941834  0.47942554  0.56464247  0.64421769
   0.71735609  0.78332691  0.84147098]
 [ 0.14112001  0.04158066 -0.05837414 -0.15774569 -0.2555411  -0.35078323
  -0.44252044 -0.52983614 -0.61185789 -0.68776616 -0.7568025  -0.81827711
  -0.87157577 -0.91616594 -0.95160207 -0.97753012 -0.993691   -0.99992326
  -0.99616461 -0.98245261 -0.95892427]
 [        nan         nan         nan         nan         nan         nan
          nan         nan         nan         nan         nan         nan
          nan         nan         nan  0.6569866   0.72896904  0.79366786
   0.85043662  0.8987081   0.93799998]] 

Tensor shape =  (3, 21)
Hide code cell source
color_cycle = plt.rcParams['axes.prop_cycle'].by_key()['color']
plt.figure()
plt.subplot(121)
plt.plot(tsd, '-', label="tsd")
for i in range(len(ep)):
    plt.plot(tsd.get(ep[i,0], ep[i,1]), 'o-', color=color_cycle[i]) 
    plt.axvspan(ep[i,0], ep[i,1], alpha=0.2)
plt.legend(loc="lower right")
plt.xlabel("Time (s)")
plt.subplot(122)
plt.plot(tensor.T, 'o-')
plt.title(r"tsd.to_trial_tensor(ep, align='end')")
plt.tight_layout()
plt.xlabel("Trial time")
plt.show()
../_images/b570243e2a4e80eadccc8867a13d6dc7263a7abd3076ca238d20c32378ee4a4c.png

time_diff#

Ts, Tsd, TsdFrame, TsdTensor, and TsGroup all have the method time_diff, which computes the time differences between subsequent timepoints. For example, if a Ts object contained a set of spike times, time_diff would compute the inter-spike interval (ISI).

This method returns a new Tsd object, with values being each time difference, and time indices being their reference time point. Passing epochs restricts the computation to the given epochs. The reference time point can be adjusted by the optional align parameter, which can be set to "start", "center", or "end" (the default being "center").

Hide code cell source
ts = nap.Ts(t=[1,5,6,12,16,18,19])
time_diffs = ts.time_diff(align="center")
print(time_diffs)
Time (s)
----------  --
3            4
5.5          1
9            6
14           4
17           2
18.5         1
dtype: float64, shape: (6,)

Setting align="center" sets the reference time point to the midpoint between the timestamps used to calculate the time difference. Setting align="start" or align="end" sets the reference time point to the earlier or later timestamp, respectively.

Hide code cell source
fig, axs = plt.subplots(3, 1, layout="constrained", figsize=(5,6))
for ax, align in zip(axs, ["center", "start", "end"]):
    time_diffs = ts.time_diff(align=align)
    ax.plot(ts.fillna(0), "|", label="ts", markersize=20, mew=3)
    ax.plot(time_diffs, "o-", label="new_tsd")
    ax.set_ylabel("Time diffs (s)")
    ax.set_title(f'ts.time_diff(align="{align}")')
    if align != "end":
        ax.set_xticks([])
    for center, time_diff in zip(time_diffs.times(), time_diffs.values):
        ax.plot([center, center], [-.25, time_diff], linestyle="--", c="black", zorder=-1)
ax.set_xlabel("Time (s)")
axs[0].legend(bbox_to_anchor=(1.05, 0.5, 0.5, 0.5))
plt.show()
../_images/d4c2799665af9d17b7fc80d50a98f095ae3fbcbc73cc0fe7a11d0d80233aef4e.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)
------------  --
0.312043173    2
0.76077208     2
4.124033674    2
5.529318713    2
6.381618289    0
7.997450253    1
8.607221438    1
...
82.573445413   2
83.882486109   0
85.156477734   2
89.677898585   2
93.962798803   2
97.766683551   2
98.269118959   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    label
-------  ------  -------
      0     0.1     0
      1     0.2     3.14
      2     0.3     6.28
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/1d4db2d21b1a3a3e48f42159cb7668efae94dd2e6866626511d9649822c6d6c4.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.05959   1.70027  -0.23515
1           -0.08844  -1.69573  -0.31686
2            0.46302  -2.21572  -0.24322
3           -0.46929  -0.32857   0.12069
dtype: float64, shape: (4, 3)

Slicing should be done like numpy array:

tsdframe[0]
array([-0.05959247,  1.70026883, -0.23514791])
tsdframe[:, 1]
Time (s)
----------  ---------
0            1.70027
1           -1.69573
2           -2.21572
3           -0.328571
dtype: float64, shape: (4,)
tsdframe[:, [0, 2]]
Time (s)           0         2
----------  --------  --------
0           -0.05959  -0.23515
1           -0.08844  -0.31686
2            0.46302  -0.24322
3           -0.46929   0.12069
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.17801   0.0711    0.08842  -1.04315
1            0.6182   -0.66961  -0.58969  -1.40236
2           -1.71622   0.60616   0.05381   2.20228
3           -0.45942  -1.38087   0.8733    0.20073
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.17801   0.08842
1            0.6182   -0.58969
2           -1.71622   0.05381
3           -0.45942   0.8733
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.08842   0.0711
1           -0.58969  -0.66961
2            0.05381   0.60616
3            0.8733   -1.38087
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.6256    0.6322   -0.10671
1           2.54058  -0.75681   1.07808
2           0.4336   -1.09535  -0.86177
3           1.1642    0.11154  -0.5053
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.625599
1           2.54058
2           0.433597
3           1.1642
dtype: float64, shape: (4,)
Time (s)
----------  --------
0           0.625599
1           2.54058
2           0.433597
3           1.1642
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.28017  -1.01545             -0.77807
1           -2.08609   0.06555             -0.67228
2            0.34664   0.28255             -0.61698
3           -1.34919   0.50578             -0.45816
dtype: float64, shape: (4, 3)

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

print(tsdframe['kiwi'])
Time (s)
----------  ---------
0           -0.280171
1           -2.08609
2            0.346639
3           -1.34919
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.28017             -0.77807
1           -2.08609             -0.67228
2            0.34664             -0.61698
3           -1.34919             -0.45816
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#

Intervals can be combined in different ways.

epoch1 = nap.IntervalSet(start=[0, 40], end=[10, 50])  # 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
      1       40     50
shape: (2, 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     50
shape: (2, 2), time unit: sec.
Hide code cell source
colors = plt.rcParams['axes.prop_cycle'].by_key()['color']
plt.figure()
[plt.axvspan(s, e, ymin=0.8, ymax=1, color=colors[0]) for s, e in epoch1.values]
[plt.axvspan(s, e, ymin=0.4, ymax=0.6, color=colors[1]) for s, e in epoch2.values]
[plt.axvspan(s, e, ymin=0, ymax=0.2, color=colors[2]) for s, e in epoch.values]
plt.xlabel("Time (s)")
plt.ylim(0, 1)
plt.xlim(0, 50)
plt.gca().spines["left"].set_visible(False)
plt.yticks([0.1, 0.5, 0.9], ['epoch1.union(epoch2)', 'epoch2', 'epoch1'])
plt.title("Union")
plt.show()
../_images/6e374ef135881b6a21b1df722b4dc53fecc9b6795460be78c1a40be13f913e62.png

intersect#

epoch = epoch1.intersect(epoch2)
print(epoch)
  index    start    end
      0        5     10
      1       40     45
shape: (2, 2), time unit: sec.
Hide code cell source
colors = plt.rcParams['axes.prop_cycle'].by_key()['color']
plt.figure()
[plt.axvspan(s, e, ymin=0.8, ymax=1, color=colors[0]) for s, e in epoch1.values]
[plt.axvspan(s, e, ymin=0.4, ymax=0.6, color=colors[1]) for s, e in epoch2.values]
[plt.axvspan(s, e, ymin=0, ymax=0.2, color=colors[2]) for s, e in epoch.values]
plt.xlabel("Time (s)")
plt.ylim(0, 1)
plt.xlim(0, 50)
plt.gca().spines["left"].set_visible(False)
plt.yticks([0.1, 0.5, 0.9], ['epoch1.intersect(epoch2)', 'epoch2', 'epoch1'])
plt.title("Intersection")
plt.show()
../_images/1475a5d81db3de2a50b0ced41f878b1eddcf2adbf690d925c7961a9390906367.png

set_diff#

epoch = epoch1.set_diff(epoch2)
print(epoch)
  index    start    end
      0        0      5
      1       45     50
shape: (2, 2), time unit: sec.
Hide code cell source
colors = plt.rcParams['axes.prop_cycle'].by_key()['color']
plt.figure()
[plt.axvspan(s, e, ymin=0.8, ymax=1, color=colors[0]) for s, e in epoch1.values]
[plt.axvspan(s, e, ymin=0.4, ymax=0.6, color=colors[1]) for s, e in epoch2.values]
[plt.axvspan(s, e, ymin=0, ymax=0.2, color=colors[2]) for s, e in epoch.values]
plt.xlabel("Time (s)")
plt.ylim(0, 1)
plt.xlim(0, 50)
plt.gca().spines["left"].set_visible(False)
plt.yticks([0.1, 0.5, 0.9], ['epoch1.set_diff(epoch2)', 'epoch2', 'epoch1'])
plt.title("Difference")
plt.show()
../_images/898029cbe81deac4991861258ca92dfb1c065b21610eaa99b1b85b34be22bdae.png

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.