From 6793901403d8415c241c496aeca537641b42070c Mon Sep 17 00:00:00 2001 From: sjvenditto Date: Tue, 19 Nov 2024 11:39:42 -0500 Subject: [PATCH] move init docs of intervalset and tsdframe. add some property docstrings --- pynapple/core/base_class.py | 13 ++++ pynapple/core/interval_set.py | 103 +++++++++++++++++++++++++-- pynapple/core/time_series.py | 127 ++++++++++++++++++++++++++++++++++ pynapple/core/ts_group.py | 5 +- 4 files changed, 243 insertions(+), 5 deletions(-) diff --git a/pynapple/core/base_class.py b/pynapple/core/base_class.py index d6a48118..37a5a779 100644 --- a/pynapple/core/base_class.py +++ b/pynapple/core/base_class.py @@ -23,6 +23,15 @@ class _Base(abc.ABC): _initialized = False + index: TsIndex + """The time index of the time series""" + + rate: float + """Frequency of the time series (Hz) computed over the time support""" + + time_support: IntervalSet + """The time support of the time series""" + def __init__(self, t, time_units="s", time_support=None): if isinstance(t, TsIndex): self.index = t @@ -50,18 +59,22 @@ def __init__(self, t, time_units="s", time_support=None): @property def t(self): + """The time index of the time series""" return self.index.values @property def start(self): + """The first time index in the time series""" return self.start_time() @property def end(self): + """The last time index in the time series""" return self.end_time() @property def shape(self): + """The shape of the time series""" return self.index.shape def __repr__(self): diff --git a/pynapple/core/interval_set.py b/pynapple/core/interval_set.py index 4add5865..3f6eda0c 100644 --- a/pynapple/core/interval_set.py +++ b/pynapple/core/interval_set.py @@ -46,10 +46,88 @@ class IntervalSet(NDArrayOperatorsMixin, _MetadataMixin): The `IntervalSet` object behaves like a numpy ndarray with the limitation that the object is not mutable. - You can still apply any numpy array function to it : + If start and end are not aligned, meaning that: + 1. len(start) != len(end) + 2. end[i] > start[i] + 3. start[i+1] > end[i] + 4. start and end are not sorted, + IntervalSet will try to "fix" the data by eliminating some of the start and end data points. + + Parameters + ---------- + start : numpy.ndarray or number or pandas.DataFrame or pandas.Series or iterable of (start, end) pairs + Beginning of intervals. Alternatively, the `end` argument can be left out and `start` can be one of the + following: + - IntervalSet + - pandas.DataFrame with columns ["start", "end"] + - iterable of (start, end) pairs + - a single (start, end) pair + end : numpy.ndarray or number or pandas.Series, optional + Ends of intervals. + time_units : str, optional + Time unit of the intervals ('us', 'ms', 's' [default]) + metadata: pandas.DataFrame or dict, optional + Metadata associated with each interval. Metadata names are pulled from DataFrame columns or dictionary keys. + The length of the metadata should match the length of the intervals. + + Raises + ------ + RuntimeError + If `start` and `end` arguments are of unknown type. + + Examples + -------- + Initialize an IntervalSet with a list of start and end times: >>> import pynapple as nap >>> import numpy as np + >>> start = [0, 10, 20] + >>> end = [5, 12, 33] + >>> ep = nap.IntervalSet(start=start, end=end) + >>> ep + index start end + 0 0 5 + 1 10 12 + 2 20 33 + shape: (3, 2), time unit: sec. + + Initialize an IntervalSet with an array of start and end pairs: + + >>> times = np.array([[0, 5], [10, 12], [20, 33]]) + >>> ep = nap.IntervalSet(times) + >>> ep + index start end + 0 0 5 + 1 10 12 + 2 20 33 + shape: (3, 2), time unit: sec. + + Initialize an IntervalSet with metadata: + + >>> start = [0, 10, 20] + >>> end = [5, 12, 33] + >>> metadata = {"label": ["a", "b", "c"]} + >>> ep = nap.IntervalSet(start=start, end=end, metadata=metadata) + index start end label + 0 0 5 | a + 1 10 12 | b + 2 20 33 | c + shape: (3, 2), time unit: sec. + + Initialize an IntervalSet with a pandas DataFrame: + + >>> import pandas as pd + >>> df = pd.DataFrame(data={"start": [0, 10, 20], "end": [5, 12, 33], "label": ["a", "b", "c"]}) + >>> ep = nap.IntervalSet(df) + >>> ep + index start end label + 0 0 5 | a + 1 10 12 | b + 2 20 33 | c + shape: (3, 2), time unit: sec. + + Apply numpy functions to an IntervalSet: + >>> ep = nap.IntervalSet(start=[0, 10], end=[5,20]) >>> ep index start end @@ -62,7 +140,7 @@ class IntervalSet(NDArrayOperatorsMixin, _MetadataMixin): array([[ 5.], [10.]]) - You can slice : + Slicing an IntervalSet: >>> ep[:,0] array([ 0., 10.]) @@ -72,13 +150,30 @@ class IntervalSet(NDArrayOperatorsMixin, _MetadataMixin): 0 0 5 shape: (1, 2) - But modifying the `IntervalSet` with raise an error: + Modifying the `IntervalSet` with raise an error: >>> ep[0,0] = 1 RuntimeError: IntervalSet is immutable. Starts and ends have been already sorted. - """ + start: np.ndarray + """The start times of each interval""" + + end: np.ndarray + """The end times of each interval""" + + values: np.ndarray + """Array of start and end times""" + + index: np.ndarray + """Index of each interval, automatically set from 0 to n_intervals""" + + columns: np.ndarray + """Column names of the IntervalSet, which are always ["start", "end"]""" + + nap_class: str + """The pynapple class name""" + def __init__( self, start, diff --git a/pynapple/core/time_series.py b/pynapple/core/time_series.py index 49c42899..995ec9ab 100644 --- a/pynapple/core/time_series.py +++ b/pynapple/core/time_series.py @@ -69,6 +69,9 @@ class _BaseTsd(_Base, NDArrayOperatorsMixin, abc.ABC): Implement most of the shared functions across concrete classes `Tsd`, `TsdFrame`, `TsdTensor` """ + values: np.ndarray + """An array of the time series data""" + def __init__(self, t, d, time_units="s", time_support=None, load_array=True): super().__init__(t, time_units, time_support) @@ -881,6 +884,27 @@ def save(self, filename): class TsdFrame(_BaseTsd, _MetadataMixin): """ Column-based container for neurophysiological time series. + A pandas.DataFrame can be passed directly. + + Parameters + ---------- + t : numpy.ndarray or pandas.DataFrame + the time index t, or a pandas.DataFrame (if d is None) + d : numpy.ndarray + The data + time_units : str, optional + The time units in which times are specified ('us', 'ms', 's' [default]). + time_support : IntervalSet, optional + The time support of the TsdFrame object + columns : iterables + Column names + load_array : bool, optional + Whether the data should be converted to a numpy (or jax) array. Useful when passing a memory map object like zarr. + Default is True. Does not apply if `d` is already a numpy array. + metadata: pd.DataFrame or dict, optional + Metadata associated with data columns. Metadata names are pulled from DataFrame columns or dictionary keys. + The length of the metadata should match the number of data columns. + If a DataFrame is passed, the index should match the columns of the TsdFrame. Attributes ---------- @@ -888,8 +912,111 @@ class TsdFrame(_BaseTsd, _MetadataMixin): Frequency of the time series (Hz) computed over the time support time_support : IntervalSet The time support of the time series + + Examples + -------- + Initialize a TsdFrame: + + >>> import pynapple as nap + >>> import numpy as np + >>> t = np.arange(100) + >>> d = np.ones((100, 3)) + >>> tsdframe = nap.TsdFrame(t=t, d=d) + >>> tsdframe + Time (s) 0 1 2 + ---------- --- --- --- + 0.0 1.0 1.0 1.0 + 1.0 1.0 1.0 1.0 + 2.0 1.0 1.0 1.0 + 3.0 1.0 1.0 1.0 + 4.0 1.0 1.0 1.0 + ... ... ... ... + 95.0 1.0 1.0 1.0 + 96.0 1.0 1.0 1.0 + 97.0 1.0 1.0 1.0 + 98.0 1.0 1.0 1.0 + 99.0 1.0 1.0 1.0 + dtype: float64, shape: (100, 3) + + Initialize a TsdFrame with column names: + + >>> tsdframe = nap.TsdFrame(t=t, d=d, columns=['A', 'B', 'C']) + >>> tsdframe + Time (s) A B C + ---------- --- --- --- + 0.0 1.0 1.0 1.0 + 1.0 1.0 1.0 1.0 + 2.0 1.0 1.0 1.0 + 3.0 1.0 1.0 1.0 + 4.0 1.0 1.0 1.0 + ... ... ... ... + 95.0 1.0 1.0 1.0 + 96.0 1.0 1.0 1.0 + 97.0 1.0 1.0 1.0 + 98.0 1.0 1.0 1.0 + 99.0 1.0 1.0 1.0 + dtype: float64, shape: (100, 3) + + Initialize a TsdFrame with metadata: + + >>> metadata = {"color": ["red", "blue", "green"], "depth": [1, 2, 3]} + >>> tsdframe = nap.TsdFrame(t=t, d=d, columns=["A", "B", "C"], metadata=metadata) + >>> tsdframe + Time (s) A B C + ---------- -------- -------- -------- + 0.0 1.0 1.0 1.0 + 1.0 1.0 1.0 1.0 + 2.0 1.0 1.0 1.0 + 3.0 1.0 1.0 1.0 + 4.0 1.0 1.0 1.0 + ... ... ... ... + 95.0 1.0 1.0 1.0 + 96.0 1.0 1.0 1.0 + 97.0 1.0 1.0 1.0 + 98.0 1.0 1.0 1.0 + 99.0 1.0 1.0 1.0 + Metadata + -------- -------- -------- -------- + color red blue green + depth 1 2 3 + + dtype: float64, shape: (100, 3) + + Initialize a TsdFrame with a pandas DataFrame: + + >>> import pandas as pd + >>> data = pd.DataFrame(index=t, columns=["A", "B", "C"], data=d) + >>> metadata = pd.DataFrame( + ... index=["A", "B", "C"], + ... columns=["color", "depth"], + ... data=[["red", 1], ["blue", 2], ["green", 3]], + ... ) + >>> tsdframe = nap.TsdFrame(data, metadata=metadata) + >>> tsdframe + Time (s) A B C + ---------- -------- -------- -------- + 0.0 1.0 1.0 1.0 + 1.0 1.0 1.0 1.0 + 2.0 1.0 1.0 1.0 + 3.0 1.0 1.0 1.0 + 4.0 1.0 1.0 1.0 + ... ... ... ... + 95.0 1.0 1.0 1.0 + 96.0 1.0 1.0 1.0 + 97.0 1.0 1.0 1.0 + 98.0 1.0 1.0 1.0 + 99.0 1.0 1.0 1.0 + Metadata + -------- -------- -------- -------- + color red blue green + depth 1 2 3 + + dtype: float64, shape: (100, 3) """ + columns: pd.Index + """Data column names of the TsdFrame""" + def __init__( self, t, diff --git a/pynapple/core/ts_group.py b/pynapple/core/ts_group.py index a3a6836b..6691e5a4 100644 --- a/pynapple/core/ts_group.py +++ b/pynapple/core/ts_group.py @@ -180,11 +180,14 @@ class TsGroup(UserDict, _MetadataMixin): """ + index: np.ndarray + """The index of the TsGroup, indicating the keys of each member""" + time_support: IntervalSet """The time support of the TsGroup, indicating the time intervals where the TsGroup is defined""" nap_class: str - """String for the pynapple class name""" + """The pynapple class name""" def __init__( self,