Numpy tutorial#

This tutorial shows how pynapple interact with numpy.

Hide code cell content

import numpy as np
import pynapple as nap
import pandas as pd

Multiple time series object are available depending on the shape of the data.

  • TsdTensor : for data with of more than 2 dimensions, typically movies.

  • TsdFrame : for column-based data. It can be easily converted to a pandas.DataFrame. Columns can be labelled and selected similar to pandas.

  • Tsd : one-dimensional time series. It can be converted to a pandas.Series.

  • Ts : For timestamps data only.

Initialization#

tsdtensor = nap.TsdTensor(t=np.arange(100), d=np.random.rand(100, 5, 5), time_units="s")
tsdframe = nap.TsdFrame(t=np.arange(100), d=np.random.rand(100, 3), columns = ['a', 'b', 'c'])
tsd = nap.Tsd(t=np.arange(100), d=np.random.rand(100))
ts = nap.Ts(t=np.arange(100))

print(tsdtensor)
Time (s)
----------  -----------------------------
0.0         [[0.149383 ... 0.494836] ...]
1.0         [[0.278137 ... 0.658296] ...]
2.0         [[0.268938 ... 0.722441] ...]
3.0         [[0.022812 ... 0.697365] ...]
4.0         [[0.151809 ... 0.036636] ...]
5.0         [[0.944259 ... 0.671475] ...]
6.0         [[0.11665  ... 0.595416] ...]
...
93.0        [[0.440987 ... 0.672151] ...]
94.0        [[0.063086 ... 0.14127 ] ...]
95.0        [[0.469463 ... 0.438587] ...]
96.0        [[0.91071  ... 0.409918] ...]
97.0        [[0.265004 ... 0.492366] ...]
98.0        [[0.688337 ... 0.78783 ] ...]
99.0        [[0.719507 ... 0.11935 ] ...]
dtype: float64, shape: (100, 5, 5)

Tsd and Ts can be converted to a pandas.Series.

print(tsd.as_series())
0.0     0.810807
1.0     0.489552
2.0     0.850996
3.0     0.654973
4.0     0.022682
          ...   
95.0    0.734996
96.0    0.885566
97.0    0.121479
98.0    0.212521
99.0    0.866288
Length: 100, dtype: float64

TsdFrame to a pandas.DataFrame.

print(tsdframe.as_dataframe())
             a         b         c
0.0   0.769150  0.070607  0.951607
1.0   0.510711  0.711133  0.690783
2.0   0.090814  0.290451  0.168465
3.0   0.766942  0.902428  0.037193
4.0   0.991975  0.010213  0.907406
...        ...       ...       ...
95.0  0.340036  0.656352  0.204085
96.0  0.631314  0.718476  0.258320
97.0  0.994326  0.722734  0.957977
98.0  0.976558  0.889153  0.253337
99.0  0.840979  0.168133  0.451344

[100 rows x 3 columns]

Attributes#

The numpy array is accessible with the attributes .values, .d and functions as_array(), to_numpy(). The time index array is a TsIndex object accessible with .index or .t. .shape and .ndim are also accessible.

print(tsdtensor.ndim)
print(tsdframe.shape)
print(len(tsd))
3
(100, 3)
100

Slicing#

Slicing is very similar to numpy array. The first dimension is always time and time support is always passed on if a pynapple object is returned.

First 10 elements. Return a TsdTensor

print(tsdtensor[0:10])
Time (s)
----------  -----------------------------
0           [[0.149383 ... 0.494836] ...]
1           [[0.278137 ... 0.658296] ...]
2           [[0.268938 ... 0.722441] ...]
3           [[0.022812 ... 0.697365] ...]
4           [[0.151809 ... 0.036636] ...]
5           [[0.944259 ... 0.671475] ...]
6           [[0.11665  ... 0.595416] ...]
7           [[0.623343 ... 0.179732] ...]
8           [[0.658122 ... 0.021493] ...]
9           [[0.350829 ... 0.82029 ] ...]
dtype: float64, shape: (10, 5, 5)

First column. Return a Tsd

print(tsdframe[:,0])
Time (s)
----------  --------
0.0         0.76915
1.0         0.510711
2.0         0.090814
3.0         0.766942
4.0         0.991975
5.0         0.426949
6.0         0.630407
...
93.0        0.864819
94.0        0.886028
95.0        0.340036
96.0        0.631314
97.0        0.994326
98.0        0.976558
99.0        0.840979
dtype: float64, shape: (100,)

First element. Return a numpy ndarray

print(tsdtensor[0])
[[0.14938278 0.96441369 0.20620201 0.07059229 0.49483628]
 [0.05615169 0.26323657 0.33012877 0.49123912 0.20646909]
 [0.03765796 0.85269593 0.96421395 0.73208385 0.31676141]
 [0.7710408  0.84821815 0.48686498 0.75635346 0.71809498]
 [0.78822064 0.85628168 0.42651725 0.23864174 0.31080762]]

The time support is never changing when slicing time down.

print(tsd.time_support)
print(tsd[0:20].time_support)
  index    start    end
      0        0     99
shape: (1, 2), time unit: sec.
  index    start    end
      0        0     99
shape: (1, 2), time unit: sec.

TsdFrame offers special slicing similar to pandas.DataFrame.

Only TsdFrame can have columns labelling and indexing.

print(tsdframe.loc['a'])
print(tsdframe.loc[['a', 'c']])
Time (s)
----------  --------
0.0         0.76915
1.0         0.510711
2.0         0.090814
3.0         0.766942
4.0         0.991975
5.0         0.426949
6.0         0.630407
...
93.0        0.864819
94.0        0.886028
95.0        0.340036
96.0        0.631314
97.0        0.994326
98.0        0.976558
99.0        0.840979
dtype: float64, shape: (100,)
Time (s)           a          c
----------  --------  ---------
0.0         0.76915   0.951607
1.0         0.510711  0.690783
2.0         0.090814  0.168465
3.0         0.766942  0.0371931
4.0         0.991975  0.907406
5.0         0.426949  0.321387
6.0         0.630407  0.0985532
...
93.0        0.864819  0.0794314
94.0        0.886028  0.95162
95.0        0.340036  0.204085
96.0        0.631314  0.25832
97.0        0.994326  0.957977
98.0        0.976558  0.253337
99.0        0.840979  0.451344
dtype: float64, shape: (100, 2)

Arithmetic#

Arithmetical operations works similar to numpy

tsd = nap.Tsd(t=np.arange(5), d=np.ones(5))
print(tsd + 1)
Time (s)
----------  --
0            2
1            2
2            2
3            2
4            2
dtype: float64, shape: (5,)

It is possible to do array operations on the time series provided that the dimensions matches. The output will still be a time series object.

print(tsd - np.ones(5))
Time (s)
----------  --
0            0
1            0
2            0
3            0
4            0
dtype: float64, shape: (5,)

Nevertheless operations like this are not permitted :

try:
	tsd + tsd
except Exception as error:
	print(error)
operand type(s) all returned NotImplemented from __array_ufunc__(<ufunc 'add'>, '__call__', Time (s)
----------  --
0            1
1            1
2            1
3            1
4            1
dtype: float64, shape: (5,), Time (s)
----------  --
0            1
1            1
2            1
3            1
4            1
dtype: float64, shape: (5,)): 'Tsd', 'Tsd'

Array operations#

The most common numpy functions will return a time series if the output first dimension matches the shape of the time index.

Here the TsdTensor is averaged along the time axis. The output is a numpy array.

print(np.mean(tsdtensor, 0))
[[0.48537875 0.50330358 0.47889782 0.45462986 0.52662787]
 [0.45186499 0.53024809 0.48180407 0.49794312 0.49377826]
 [0.51090537 0.50556817 0.54870099 0.50970142 0.50520311]
 [0.49516047 0.49285938 0.46423324 0.48092279 0.54427993]
 [0.50112562 0.51002639 0.53029549 0.4854819  0.48926354]]

Here averaging across the second dimension returns a TsdFrame.

print(np.mean(tsdtensor, 1))
Time (s)           0         1         2         3         4
----------  --------  --------  --------  --------  --------
0.0         0.360491  0.756969  0.482785  0.457782  0.409394
1.0         0.418048  0.596328  0.439893  0.574721  0.683777
2.0         0.414527  0.676953  0.630308  0.551403  0.577891
3.0         0.597145  0.638796  0.500155  0.408959  0.549999
4.0         0.524224  0.27329   0.591129  0.242853  0.426257
5.0         0.761444  0.476383  0.795092  0.655084  0.380153
6.0         0.41256   0.6884    0.437583  0.659749  0.453873
...
93.0        0.518964  0.486854  0.530007  0.422634  0.220848
94.0        0.358368  0.459661  0.643801  0.517837  0.296932
95.0        0.545333  0.328111  0.713834  0.593709  0.407302
96.0        0.597293  0.406066  0.571722  0.718949  0.325596
97.0        0.471173  0.466263  0.649174  0.446203  0.692743
98.0        0.603124  0.624461  0.25321   0.467279  0.486446
99.0        0.358465  0.385549  0.646313  0.360579  0.267738
dtype: float64, shape: (100, 5)

This is not true for FFT functions though.

try:
	np.fft.fft(tsd)
except Exception as error:
	print(error)
no implementation found for 'numpy.fft.fft' on types that implement __array_function__: [<class 'pynapple.core.time_series.Tsd'>]

Concatenating#

It is possible to concatenate time series providing than they don’t overlap meaning time indexe should be already sorted through all time series to concatenate

tsd1 = nap.Tsd(t=np.arange(5), d=np.ones(5))
tsd2 = nap.Tsd(t=np.arange(5)+10, d=np.ones(5)*2)
tsd3 = nap.Tsd(t=np.arange(5)+20, d=np.ones(5)*3)

print(np.concatenate((tsd1, tsd2, tsd3)))
Time (s)
----------  --
0.0          1
1.0          1
2.0          1
3.0          1
4.0          1
10.0         2
11.0         2
...
13.0         2
14.0         2
20.0         3
21.0         3
22.0         3
23.0         3
24.0         3
dtype: float64, shape: (15,)

It’s also possible to concatenate vertically if time indexes matches up to pynapple float precision

tsdframe = nap.TsdFrame(t=np.arange(5), d=np.random.randn(5, 3))

print(np.concatenate((tsdframe, tsdframe), 1))
Time (s)            0          1          2          0          1  ...
----------  ---------  ---------  ---------  ---------  ---------  -----
0            0.533454  -1.35784    0.97354    0.533454  -1.35784   ...
1           -0.280685  -1.08427   -0.927925  -0.280685  -1.08427   ...
2           -0.90009   -1.4984     0.120373  -0.90009   -1.4984    ...
3           -0.310966  -0.824915  -1.31755   -0.310966  -0.824915  ...
4            0.272539   0.23711   -0.548888   0.272539   0.23711   ...
dtype: float64, shape: (5, 6)

Splitting#

Array split functions are also implemented

print(np.array_split(tsdtensor[0:10], 2))
[Time (s)
----------  -----------------------------
0           [[0.149383 ... 0.494836] ...]
1           [[0.278137 ... 0.658296] ...]
2           [[0.268938 ... 0.722441] ...]
3           [[0.022812 ... 0.697365] ...]
4           [[0.151809 ... 0.036636] ...]
dtype: float64, shape: (5, 5, 5), Time (s)
----------  -----------------------------
5           [[0.944259 ... 0.671475] ...]
6           [[0.11665  ... 0.595416] ...]
7           [[0.623343 ... 0.179732] ...]
8           [[0.658122 ... 0.021493] ...]
9           [[0.350829 ... 0.82029 ] ...]
dtype: float64, shape: (5, 5, 5)]

Modifying#

It is possible to modify a time series element wise

print(tsd1)

tsd1[0] = np.pi

print(tsd1)
Time (s)
----------  --
0            1
1            1
2            1
3            1
4            1
dtype: float64, shape: (5,)
Time (s)
----------  -------
0           3.14159
1           1
2           1
3           1
4           1
dtype: float64, shape: (5,)

It is also possible to modify a time series with logical operations

tsd[tsd.values>0.5] = 0.0

print(tsd)
Time (s)
----------  --
0            0
1            0
2            0
3            0
4            0
dtype: float64, shape: (5,)

Sorting#

It is not possible to sort along the first dimension as it would break the sorting of the time index

tsd = nap.Tsd(t=np.arange(100), d=np.random.rand(100))

try:
	np.sort(tsd)
except Exception as error:
	print(error)
no implementation found for 'numpy.sort' on types that implement __array_function__: [<class 'pynapple.core.time_series.Tsd'>]