AnalogSignalArray Tutorial¶
AnalogSignalArrayTutorial.ipynb
Overview¶
The AnalogSignalArray
is used to store fairly-regularly-sampled temporal signals. Ideally the signals should be sampled regularly, but many of the methods handle irregularly sampled data gracefully, and moreover, the AnalogSignalArray
makes it easy to sanitize an irregularly sampled signal into an easier-to-work-with regularly sampled signal.
Fundamentally, an AnalogSignalArray
contains a .time
attribute, and a .data
attribute, corresponding to the (n,) sample timestamps, in seconds, and the (m, n) signal values (n samples for each of m signals).
# create an AnalogSignalArray with a single signal, with four samples:
asa = nel.AnalogSignalArray(data=[2, 4, 5, 6])
# create an AnalogSignalArray with a single signal, with four samples:
asa = nel.AnalogSignalArray(timestamps=[0, 1, 2, 3],
data=[2, 4, 5, 6])
# create an AnalogSignalArray with two signals, each with four samples:
asa = nel.AnalogSignalArray(timestamps=[0, 1, 2, 3],
data=[[2, 4, 5, 6], [5, 4, 3, 2]])
# create an AnalogSignalArray with a single signal, with four samples:
asa = nel.AnalogSignalArray(timestamps=[0, 1, 2, 3],
data=[2, 4, 5, 6])
# create an AnalogSignalArray with a single signal, with four samples,
# and an explicit support:
asa = nel.AnalogSignalArray(timestamps=[0, 1, 2, 3],
data=[2, 4, 5, 6],
support=nel.EpochArray(0,4))
# create an AnalogSignalArray with a single signal, with four samples,
# and an explicit sampling rate:
asa = nel.AnalogSignalArray(timestamps=[0, 1, 2, 3],
data=[2, 4, 5, 6],
fs=1)
# create an AnalogSignalArray with a single signal, with ten samples:
asa = nel.AnalogSignalArray(timestamps=[0, 1, 2, 3, 10, 11, 12, 13, 14, 15],
data=[1, 1, 1, 1, 2, 2, 2, 2, 2, 2])
# npl.plot(asa, marker='.')
Core Nelpy Objects¶
AnalogSignalArray (m-D)¶
An AnalogSignalArray
represents a multi-dimensional analog signal function:
$$f: \mathbb{R} \to \mathbb{R}^m \quad \bigl(t \mapsto (y_1, y_2, \ldots, y_m)\bigr)$$
The default domain is $\Omega = \mathbb{R}$, and the support can either be specified or inferred.
Support Specification¶
If a support is specified as a collection of intervals $\{s_1, s_2, \ldots s_k\}$, then the AnalogSignalArray behaves like:
$$f: \mathbb{S} \to \mathbb{R}^m \quad \bigl(t \mapsto (y_1, y_2, \ldots, y_m)\bigr)$$
where $\mathbb{S} = \bigcup_{i=1}^k s_i$, so that the AnalogSignalArray is undefined in $\Omega \backslash \mathbb{S}$.
Internal Data Organization¶
An AnalogSignalArray
contains data
and time
attributes:
data
$\in \mathbb{R}^{m \times n}$ where $m$ is the number of signals and $n$ is the number of samplestime
is a numpy vector (1-dimensional array) with shape $(n,)$, containing the sample times in seconds
Working with Data¶
When looking at the data
matrix itself (as a numpy array, not a nelpy object), it's convenient to iterate over signals:
for signal in data:
pass
However, plotting with matplotlib directly can be inconvenient since matplotlib plots each column of a matrix as a single trace. To plot data
with matplotlib:
import matplotlib.pyplot as plt
plt.plot(data.T) # plot each signal as a single continuous trace
!!! warning "Discontinuities" This approach ignores the fact that signals are not always continuous. Nelpy considers each signal to be composed of different segments, each assumed to be continuous.
Nelpy Plotting¶
Nelpy provides plotting as an almost drop-in replacement for matplotlib:
import nelpy.plotting as npl
npl.plot(asa) # plot each signal as a single trace, respecting discontinuities
Working with Segments¶
Here's an example of creating an AnalogSignalArray
with multiple segments:
import numpy as np
import nelpy as nel
t = np.linspace(0, 10, 100)
y1 = t**3
y2 = 3*t**2
y3 = 6*t
y4 = 6*np.ones(t.shape)
asa = nel.AnalogSignalArray(
np.vstack((y1, y2, y3, y4)),
timestamps=t,
support=nel.EpochArray([[0,3], [5,10]])
)
This creates an AnalogSignalArray
with four signals and two snippets/segments/epochs.
Accessing Epoch Data¶
You can access timestamps and signal data using asa.time
and asa.data
, but these don't directly indicate which samples are contiguous. For better handling of discontinuities, use the _epochtime
and _epochdata
special objects:
import matplotlib.pyplot as plt
# Plot each snippet separately to preserve discontinuities
for timestamps, data in zip(asa._epochtime, asa._epochdata):
plt.plot(timestamps, data.T)
!!! tip "Better Plotting" While this preserves discontinuities, signal snippets will have different colors, making it difficult to see which segments correspond to which signals. Using npl.plot(asa)
handles this automatically.
Iteration¶
Iteration over an AnalogSignalArray
(and most core temporal nelpy objects) iterates over the continuous epochs:
for snippet in asa:
# snippet is an AnalogSignalArray with only a single underlying support epoch
timestamps, data = (snippet.time, snippet.data) # one continuous epoch at a time
Indexing and Restriction¶
AnalogSignalArray
s can be indexed/restricted using EpochArray
s. The resulting AnalogSignalArray
will be defined on the intersection of its own underlying support and the requested EpochArray
:
ep = nel.EpochArray([2, 7])
asa_new = asa[ep]
If asa
had a support of $[0, 3) \cup [5, 10)$, then asa_new
will be defined on $[2, 3) \cup [5, 7)$.
Epoch Indexing¶
You can index epochs with integers:
asa[0] # asa restricted to first epoch
asa[1] # asa restricted to second epoch
asa[2] # empty AnalogSignalArray (no third epoch)
Signal Indexing¶
Extended syntax allows access to individual signals:
asa[0, 0] # first epoch, first signal
asa[0, 1] # first epoch, second signal
asa[:, 2:] # all epochs, third and fourth signals
asa[:, [1,3]] # all epochs, second and fourth signals
!!! info "Future Enhancement" Eventually, sample indexing will be supported with the form asa[epoch, signal, sample]
. This indexing form is consistent across most nelpy objects (e.g., SpikeTrainArray
uses sta[epoch, unit, ...]
).
Common Operations¶
Common tricks include:
- Resampling
- Simplifying for plots
- Joining arrays
- Casting to
BinnedSpikeTrainArray
s - Changing underlying support
- Creating copy-like objects with attached metadata without actual data
- Using
__call__
andasarray
!!! warning "Advanced Usage" For advanced users: access data with underscore methods, and use __renew__
after modifying objects.
PositionArray¶
1D Position¶
A 1D PositionArray
represents position as a function of time:
$$f: \mathbb{R} \to \mathbb{R} \quad \bigl(t \mapsto x\bigr)$$
Special attributes: x
, speed
2D Position¶
A 2D PositionArray
represents 2D position as a function of time:
$$f: \mathbb{R} \to \mathbb{R}^2 \quad \bigl(t \mapsto (x,y)\bigr)$$
Special attributes: x
, y
, speed
SpikeTrainArray (N-D)¶
A SpikeTrainArray
represents spike trains for multiple units:
$$f: \mathbb{Z} \to \mathbb{R}^{n_i} \quad \bigl(u \mapsto (t_1, t_2, \ldots, t_{n_i})\bigr)$$
where $u \in \{1,2,\ldots N\}$ and unit $u$ has $n_i$ spikes.