diff --git a/pynapple/core/interval_set.py b/pynapple/core/interval_set.py index 9b57edc8..498238e1 100644 --- a/pynapple/core/interval_set.py +++ b/pynapple/core/interval_set.py @@ -51,33 +51,31 @@ class IntervalSet(NDArrayOperatorsMixin, _MetadataMixin): >>> import pynapple as nap >>> import numpy as np >>> ep = nap.IntervalSet(start=[0, 10], end=[5,20]) - start end - 0 0 5 - 1 10 20 - shape: (1, 2) + >>> ep + index start end + 0 0 5 + 1 10 20 + shape: (2, 2), time unit: sec. >>> np.diff(ep, 1) - UserWarning: Converting IntervalSet to numpy.array - array([[ 5.], - [10.]]) - + UserWarning: Converting IntervalSet to numpy.array + array([[ 5.], + [10.]]) You can slice : >>> ep[:,0] - array([ 0., 10.]) + array([ 0., 10.]) >>> ep[0] - start end - 0 0 5 - shape: (1, 2) - + start end + 0 0 5 + shape: (1, 2) But modifying the `IntervalSet` with raise an error: - >>> ep[0,0] = 1 - RuntimeError: IntervalSet is immutable. Starts and ends have been already sorted. + RuntimeError: IntervalSet is immutable. Starts and ends have been already sorted. """ @@ -89,12 +87,13 @@ def __init__( metadata=None, ): """ + `IntervalSet` object initializor. + 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 @@ -102,7 +101,6 @@ def __init__( 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 @@ -111,14 +109,66 @@ def __init__( Ends of intervals. time_units : str, optional Time unit of the intervals ('us', 'ms', 's' [default]) - metadata: pd.DataFrame or dict, optional - Metadata associated with each interval + 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 + -------- + >>> import pynapple as nap + >>> import numpy as np + + Initialize an `IntervalSet` object with a list of start and end times: + + >>> 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` object 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` object 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` object 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. """ # set directly in __dict__ to avoid infinite recursion in __setattr__ self.__dict__["_initialized"] = False diff --git a/pynapple/core/metadata_class.py b/pynapple/core/metadata_class.py index 65499b19..a6e94e11 100644 --- a/pynapple/core/metadata_class.py +++ b/pynapple/core/metadata_class.py @@ -161,6 +161,14 @@ def set_info(self, metadata=None, **kwargs): """ Add metadata information about the object. Metadata are saved as a pandas.DataFrame. + If the metadata name does not contain special nor overlaps with class attributes, + it can also be set using attribute assignment. + + If the metadata name does not overlap with class-reserved keys, + it can also be set using key assignment. + + Metadata entries (excluding "rate" for `TsGroup`) are mutable and can be overwritten. + Parameters ---------- metadata : pandas.DataFrame or dict or pandas.Series, optional @@ -188,10 +196,10 @@ def set_info(self, metadata=None, **kwargs): -------- >>> import pynapple as nap >>> import numpy as np - >>> tmp = { 0:nap.Ts(t=np.arange(0,200), time_units='s'), - 1:nap.Ts(t=np.arange(0,200,0.5), time_units='s'), - 2:nap.Ts(t=np.arange(0,300,0.25), time_units='s'), - } + >>> tmp = {0:nap.Ts(t=np.arange(0,200), time_units='s'), + ... 1:nap.Ts(t=np.arange(0,200,0.5), time_units='s'), + ... 2:nap.Ts(t=np.arange(0,300,0.25), time_units='s'), + ... } >>> tsgroup = nap.TsGroup(tmp) To add metadata with a pandas.DataFrame: @@ -227,6 +235,37 @@ def set_info(self, metadata=None, **kwargs): 0 0.66722 pfc [0, 0] 0 1 1.33445 pfc [0, 1] 1 2 4.00334 ca1 [1, 0] 1 + + To add metadata as an attribute: + + >>> tsgroup.label = ["a", "b", "c"] + >>> tsgroup + Index rate struct coords hd label + ------- ------- -------- -------- ---- ------- + 0 0.66722 pfc [0, 0] 0 a + 1 1.33445 pfc [0, 1] 1 b + 2 4.00334 ca1 [1, 0] 1 c + + To add metadata as a key: + + >>> tsgroup["type"] = ["multi", "multi", "single"] + >>> tsgroup + Index rate struct coords hd label type + ------- ------- -------- -------- ---- ------- ------ + 0 0.66722 pfc [0, 0] 0 a multi + 1 1.33445 pfc [0, 1] 1 b multi + 2 4.00334 ca1 [1, 0] 1 c single + + Metadata can be overwritten: + + >>> tsgroup.set_info(label=["x", "y", "z"]) + >>> tsgroup + Index rate struct coords hd label type + ------- ------- -------- -------- ---- ------- ------ + 0 0.66722 pfc [0, 0] 0 x multi + 1 1.33445 pfc [0, 1] 1 y multi + 2 4.00334 ca1 [1, 0] 1 z single + """ # check for duplicate names and/or formatted names that cannot be accessed as attributes or keys self._check_metadata_column_names(metadata, **kwargs) @@ -287,6 +326,12 @@ def get_info(self, key): """ Returns metadata based on metadata column name or index. + If the metadata name does not contain special nor overlaps with class attributes, + it can also be accessed as an attribute. + + If the metadata name does not overlap with class-reserved keys, + it can also be accessed as a key. + Parameters ---------- key : @@ -310,10 +355,10 @@ def get_info(self, key): -------- >>> import pynapple as nap >>> import numpy as np - >>> tmp = { 0:nap.Ts(t=np.arange(0,200), time_units='s'), - 1:nap.Ts(t=np.arange(0,200,0.5), time_units='s'), - 2:nap.Ts(t=np.arange(0,300,0.25), time_units='s'), - } + >>> tmp = {0:nap.Ts(t=np.arange(0,200), time_units='s'), + ... 1:nap.Ts(t=np.arange(0,200,0.5), time_units='s'), + ... 2:nap.Ts(t=np.arange(0,300,0.25), time_units='s'), + ... } >>> metadata = {"l1": [1, 2, 3], "l2": ["x", "x", "y"]} >>> tsgroup = nap.TsGroup(tmp,metadata=metadata) @@ -328,7 +373,7 @@ def get_info(self, key): To access multiple metadata columns: >>> tsgroup.get_info(["l1", "l2"]) - l1 l2 + l1 l2 0 1 x 1 2 x 2 3 y @@ -353,6 +398,30 @@ def get_info(self, key): >>> tsgroup.get_info((0, "l1")) np.int64(1) + To access metadata as an attribute: + + >>> tsgroup.l1 + 0 1 + 1 2 + 2 3 + Name: l1, dtype: int64 + + To access metadata as a key: + + >>> tsgroup["l1"] + 0 1 + 1 2 + 2 3 + Name: l1, dtype: int64 + + Multiple metadata columns can be accessed as keys: + + >>> tsgroup[["l1", "l2"]] + l1 l2 + 0 1 x + 1 2 x + 2 3 y + """ # string indexing of one or more metadata columns if isinstance(key, str) or (