Metadata#
Metadata can be added to TsGroup
, IntervalSet
, and TsdFrame
objects at initialization or after an object has been created.
TsGroup
metadata is information associated with each Ts/Tsd object, such as brain region or unit type.IntervalSet
metadata is information associated with each interval, such as a trial label or stimulus condition.TsdFrame
metadata is information associated with each column, such as a channel or position.
Adding metadata at initialization#
At initialization, metadata can be passed via a dictionary or pandas DataFrame using the keyword argument metadata
. The metadata name is taken from the dictionary key or DataFrame column, and it can be set to any string name with a couple class-specific exceptions.
Class-specific exceptions
If column names are supplied to
TsdFrame
, metadata cannot overlap with those names.The
rate
attribute forTsGroup
is stored with the metadata and cannot be overwritten.
The length of the metadata must match the length of the object it describes (see class examples below for more detail).
Show code cell content
import numpy as np
import pandas as pd
import pynapple as nap
# input parameters for TsGroup
group = {
1: nap.Ts(t=np.sort(np.random.uniform(0, 100, 100))),
2: nap.Ts(t=np.sort(np.random.uniform(0, 100, 200))),
3: nap.Ts(t=np.sort(np.random.uniform(0, 100, 300))),
4: nap.Ts(t=np.sort(np.random.uniform(0, 100, 400))),
}
# input parameters for IntervalSet
starts = [0,35,70]
ends = [30,65,100]
# input parameters for TsdFrame
t = np.arange(5)
d = np.tile([1,2,3], (5, 1))
columns = ["a", "b", "c"]
TsGroup
#
Metadata added to TsGroup
must match the number of Ts
/Tsd
objects, or the length of its index
property.
metadata = {"region": ["pfc", "pfc", "hpc", "hpc"]}
tsgroup = nap.TsGroup(group, metadata=metadata)
print(tsgroup)
Index rate region
------- ------- --------
1 1.0011 pfc
2 2.00219 pfc
3 3.00329 hpc
4 4.00439 hpc
When initializing with a DataFrame, the index must align with the input dictionary keys (only when a dictionary is used to create the TsGroup
).
metadata = pd.DataFrame(
index=group.keys(),
data=["pfc", "pfc", "hpc", "hpc"],
columns=["region"]
)
tsgroup = nap.TsGroup(group, metadata=metadata)
print(tsgroup)
Index rate region
------- ------- --------
1 1.0011 pfc
2 2.00219 pfc
3 3.00329 hpc
4 4.00439 hpc
IntervalSet
#
Metadata added to IntervalSet
must match the number of intervals, or the length of its index
property.
metadata = {
"reward": [1, 0, 1],
"choice": ["left", "right", "left"],
}
intervalset = nap.IntervalSet(starts, ends, metadata=metadata)
print(intervalset)
index start end reward choice
0 0 30 1 left
1 35 65 0 right
2 70 100 1 left
shape: (3, 2), time unit: sec.
Metadata can be initialized as a DataFrame using the metadata argument, or it can be inferred when initializing an IntervalSet
with a DataFrame.
df = pd.DataFrame(
data=[[0, 30, 1, "left"], [35, 65, 0, "right"], [70, 100, 1, "left"]],
columns=["start", "end", "reward", "choice"]
)
intervalset = nap.IntervalSet(df)
print(intervalset)
index start end reward choice
0 0 30 1 left
1 35 65 0 right
2 70 100 1 left
shape: (3, 2), time unit: sec.
TsdFrame
#
Metadata added to TsdFrame
must match the number of data columns, or the length of its columns
property.
metadata = {
"color": ["red", "blue", "green"],
"position": [10,20,30],
"label": ["x", "x", "y"]
}
tsdframe = nap.TsdFrame(d=d, t=t, columns=["a", "b", "c"], metadata=metadata)
print(tsdframe)
Time (s) a b c
---------- --- ---- -----
0.0 1 2 3
1.0 1 2 3
2.0 1 2 3
3.0 1 2 3
4.0 1 2 3
Metadata
color red blue green
position 10 20 30
... ... ... ...
dtype: int64, shape: (5, 3)
When initializing with a DataFrame, the DataFrame index must match the TsdFrame
columns.
metadata = pd.DataFrame(
index=["a", "b", "c"],
data=[["red", 10, "x"], ["blue", 20, "x"], ["green", 30, "y"]],
columns=["color", "position", "label"],
)
tsdframe = nap.TsdFrame(d=d, t=t, columns=["a", "b", "c"], metadata=metadata)
print(tsdframe)
Time (s) a b c
---------- --- ---- -----
0.0 1 2 3
1.0 1 2 3
2.0 1 2 3
3.0 1 2 3
4.0 1 2 3
Metadata
color red blue green
position 10 20 30
... ... ... ...
dtype: int64, shape: (5, 3)
Adding metadata after initialization#
After creation, metadata can be added using the class method set_info()
. Additionally, single metadata fields can be added as a dictionary-like key or as an attribute, with a few noted exceptions outlined below.
Note
The remaining metadata examples will be shown on a TsGroup
object; however, all examples can be directly applied to IntervalSet
and TsdFrame
objects.
set_info
#
Metadata can be passed as a dictionary or pandas DataFrame as the first positional argument, or metadata can be passed as name-value keyword arguments.
tsgroup.set_info(unit_type=["multi", "single", "single", "single"])
print(tsgroup)
Index rate region unit_type
------- ------- -------- -----------
1 1.0011 pfc multi
2 2.00219 pfc single
3 3.00329 hpc single
4 4.00439 hpc single
Using dictionary-like keys (square brackets)#
Most metadata names can set as a dictionary-like key (i.e. using square brackets). The only exceptions are for IntervalSet
, where the names “start” and “end” are reserved for class properties.
tsgroup["depth"] = [0, 1, 2, 3]
print(tsgroup)
Index rate region unit_type depth
------- ------- -------- ----------- -------
1 1.0011 pfc multi 0
2 2.00219 pfc single 1
3 3.00329 hpc single 2
4 4.00439 hpc single 3
Using attribute assignment#
If the metadata name is unique from other class attributes and methods, and it is formatted properly (i.e. only alpha-numeric characters and underscores), it can be set as an attribute (i.e. using a .
followed by the metadata name).
tsgroup.label=["MUA", "good", "good", "good"]
print(tsgroup)
Index rate region unit_type depth label
------- ------- -------- ----------- ------- -------
1 1.0011 pfc multi 0 MUA
2 2.00219 pfc single 1 good
3 3.00329 hpc single 2 good
4 4.00439 hpc single 3 good
Allowed data types#
As long as the length of the metadata container matches the length of the object (number of columns for TsdFrame
and number of indices for IntervalSet
and TsGroup
), elements of the metadata can be any data type.
tsgroup.coords = [[1,0],[0,1],[1,1],[2,1]]
print(tsgroup)
Index rate region unit_type depth label coords ...
------- ------- -------- ----------- ------- ------- -------- -----
1 1.0011 pfc multi 0 MUA [1 0] ...
2 2.00219 pfc single 1 good [0 1] ...
3 3.00329 hpc single 2 good [1 1] ...
4 4.00439 hpc single 3 good [2 1] ...
Accessing metadata#
Metadata is stored as a pandas DataFrame, which can be previewed using the metadata
attribute.
print(tsgroup.metadata)
rate region unit_type depth label coords
1 1.001096 pfc multi 0 MUA [1, 0]
2 2.002193 pfc single 1 good [0, 1]
3 3.003289 hpc single 2 good [1, 1]
4 4.004385 hpc single 3 good [2, 1]
Single metadata columns (or lists of columns) can be retrieved using the get_info()
class method:
print(tsgroup.get_info("region"))
1 pfc
2 pfc
3 hpc
4 hpc
Name: region, dtype: object
Similarly, metadata can be accessed using key indexing (i.e. square brakets)
print(tsgroup["region"])
1 pfc
2 pfc
3 hpc
4 hpc
Name: region, dtype: object
Note
Metadata names must be strings. Key indexing with an integer will produce different behavior based on object type.
Finally, metadata that can be set as an attribute can also be accessed as an attribute.
print(tsgroup.region)
1 pfc
2 pfc
3 hpc
4 hpc
Name: region, dtype: object
Overwriting metadata#
User-set metadata is mutable and can be overwritten.
print(tsgroup, "\n")
tsgroup.set_info(label=["A", "B", "C", "D"])
print(tsgroup)
Index rate region unit_type depth label coords ...
------- ------- -------- ----------- ------- ------- -------- -----
1 1.0011 pfc multi 0 MUA [1 0] ...
2 2.00219 pfc single 1 good [0 1] ...
3 3.00329 hpc single 2 good [1 1] ...
4 4.00439 hpc single 3 good [2 1] ...
Index rate region unit_type depth label coords ...
------- ------- -------- ----------- ------- ------- -------- -----
1 1.0011 pfc multi 0 A [1 0] ...
2 2.00219 pfc single 1 B [0 1] ...
3 3.00329 hpc single 2 C [1 1] ...
4 4.00439 hpc single 3 D [2 1] ...
Dropping metadata#
To drop metadata, use the drop_info()
method. Multiple metadata columns can be dropped by passing a list of metadata names.
print(tsgroup, "\n")
tsgroup.drop_info("coords")
print(tsgroup)
Index rate region unit_type depth label coords ...
------- ------- -------- ----------- ------- ------- -------- -----
1 1.0011 pfc multi 0 A [1 0] ...
2 2.00219 pfc single 1 B [0 1] ...
3 3.00329 hpc single 2 C [1 1] ...
4 4.00439 hpc single 3 D [2 1] ...
Index rate region unit_type depth label
------- ------- -------- ----------- ------- -------
1 1.0011 pfc multi 0 A
2 2.00219 pfc single 1 B
3 3.00329 hpc single 2 C
4 4.00439 hpc single 3 D
Using metadata to slice objects#
Metadata can be used to slice or filter objects based on metadata values.
print(tsgroup[tsgroup.label == "A"])
Index rate region unit_type depth label
------- ------ -------- ----------- ------- -------
1 1.0011 pfc multi 0 A
groupby
: Using metadata to group objects#
Similar to pandas, metadata can be used to group objects based on one or more metadata columns using the object method groupby
, where the first argument is the metadata columns name(s) to group by. This function returns a dictionary with keys corresponding to unique groups and values corresponding to object indices belonging to each group.
print(tsgroup,"\n")
print(tsgroup.groupby("region"))
Index rate region unit_type depth label
------- ------- -------- ----------- ------- -------
1 1.0011 pfc multi 0 A
2 2.00219 pfc single 1 B
3 3.00329 hpc single 2 C
4 4.00439 hpc single 3 D
{'hpc': array([3, 4]), 'pfc': array([1, 2])}
Grouping by multiple metadata columns should be passed as a list.
tsgroup.groupby(["region","unit_type"])
{('hpc', 'single'): array([3, 4]),
('pfc', 'multi'): array([1]),
('pfc', 'single'): array([2])}
The optional argument get_group
can be provided to return a new object corresponding to a specific group.
tsgroup.groupby("region", get_group="hpc")
Index rate region unit_type depth label
------- ------- -------- ----------- ------- -------
3 3.00329 hpc single 2 C
4 4.00439 hpc single 3 D
groupby_apply
: Applying functions to object groups#
The groupby_apply
object method allows a specific function to be applied to object groups. The first argument, same as groupby
, is the metadata column(s) used to group the object. The second argument is the function to apply to each group. If only these two arguments are supplied, it is assumed that the grouped object is the first and only input to the applied function. This function returns a dictionary, where keys correspond to each unique group, and values correspond to the function output on each group.
print(tsdframe,"\n")
print(tsdframe.groupby_apply("label", np.mean))
Time (s) a b c
---------- --- ---- -----
0.0 1 2 3
1.0 1 2 3
2.0 1 2 3
3.0 1 2 3
4.0 1 2 3
Metadata
color red blue green
position 10 20 30
... ... ... ...
dtype: int64, shape: (5, 3)
{'x': np.float64(1.5), 'y': np.float64(3.0)}
If the applied function requires additional inputs, these can be passed as additional keyword arguments into groupby_apply
.
feature = nap.Tsd(t=np.arange(100), d=np.repeat([0,1], 50))
tsgroup.groupby_apply(
"region",
nap.compute_1d_tuning_curves,
feature=feature,
nb_bins=2)
{'hpc': 3 4
0.25 3.050505 3.919192
0.75 2.969697 4.080808,
'pfc': 1 2
0.25 0.949495 1.898990
0.75 1.070707 2.121212}
Alternatively, an anonymous function can be passed instead that defines additional arguments.
func = lambda x: nap.compute_1d_tuning_curves(x, feature=feature, nb_bins=2)
tsgroup.groupby_apply("region", func)
{'hpc': 3 4
0.25 3.050505 3.919192
0.75 2.969697 4.080808,
'pfc': 1 2
0.25 0.949495 1.898990
0.75 1.070707 2.121212}
An anonymous function can also be used to apply a function where the grouped object is not the first input.
func = lambda x: nap.compute_1d_tuning_curves(
group=tsgroup,
feature=feature,
nb_bins=2,
ep=x)
intervalset.groupby_apply("choice", func)
{'left': 1 2 3 4
0.25 0.847181 1.792115 3.030303 3.551645
0.75 1.077441 2.087542 3.030303 3.838384,
'right': 1 2 3 4
0.25 0.808081 2.154882 2.895623 4.579125
0.75 1.073232 2.398990 2.714646 4.482323}
Alternatively, the optional parameter input_key
can be passed to specify which keyword argument the grouped object corresponds to. Other required arguments of the applied function need to be passed as keyword arguments.
intervalset.groupby_apply(
"choice",
nap.compute_1d_tuning_curves,
input_key="ep",
group=tsgroup,
feature=feature,
nb_bins=2)
{'left': 1 2 3 4
0.25 0.847181 1.792115 3.030303 3.551645
0.75 1.077441 2.087542 3.030303 3.838384,
'right': 1 2 3 4
0.25 0.808081 2.154882 2.895623 4.579125
0.75 1.073232 2.398990 2.714646 4.482323}