my god it works but what have i done

This commit is contained in:
sneakers-the-rat 2024-08-07 02:03:04 -07:00
parent a993ee10f2
commit a9909485a4
Signed by untrusted user who does not match committer: jonny
GPG key ID: 6DCB96EF1E4D232D
16 changed files with 725 additions and 245 deletions

View file

@ -250,6 +250,8 @@ class AfterGenerateClass:
cls.cls.bases = ["VectorDataMixin"] cls.cls.bases = ["VectorDataMixin"]
elif cls.cls.name == "VectorIndex": elif cls.cls.name == "VectorIndex":
cls.cls.bases = ["VectorIndexMixin"] cls.cls.bases = ["VectorIndexMixin"]
elif cls.cls.name == "DynamicTableRegion":
cls.cls.bases = ["DynamicTableRegionMixin", "VectorData"]
return cls return cls

View file

@ -2,7 +2,18 @@
Special types for mimicking HDMF special case behavior Special types for mimicking HDMF special case behavior
""" """
from typing import TYPE_CHECKING, Any, ClassVar, Dict, List, Optional, Tuple, Union, overload from typing import (
TYPE_CHECKING,
Any,
ClassVar,
Dict,
Iterable,
List,
Optional,
Tuple,
Union,
overload,
)
import numpy as np import numpy as np
from linkml.generators.pydanticgen.template import Import, Imports, ObjectImport from linkml.generators.pydanticgen.template import Import, Imports, ObjectImport
@ -69,7 +80,7 @@ class DynamicTableMixin(BaseModel):
]: ... ]: ...
@overload @overload
def __getitem__(self, item: slice) -> DataFrame: ... def __getitem__(self, item: Union[slice, "NDArray"]) -> DataFrame: ...
def __getitem__( def __getitem__(
self, self,
@ -77,6 +88,7 @@ class DynamicTableMixin(BaseModel):
str, str,
int, int,
slice, slice,
"NDArray",
Tuple[int, Union[int, str]], Tuple[int, Union[int, str]],
Tuple[Union[int, slice], ...], Tuple[Union[int, slice], ...],
], ],
@ -96,7 +108,7 @@ class DynamicTableMixin(BaseModel):
""" """
if isinstance(item, str): if isinstance(item, str):
return self._columns[item] return self._columns[item]
if isinstance(item, (int, slice)): if isinstance(item, (int, slice, np.integer, np.ndarray)):
return DataFrame.from_dict(self._slice_range(item)) return DataFrame.from_dict(self._slice_range(item))
elif isinstance(item, tuple): elif isinstance(item, tuple):
if len(item) != 2: if len(item) != 2:
@ -107,7 +119,7 @@ class DynamicTableMixin(BaseModel):
# all other cases are tuples of (rows, cols) # all other cases are tuples of (rows, cols)
rows, cols = item rows, cols = item
if isinstance(cols, (int, slice)): if isinstance(cols, (int, slice, np.integer)):
cols = self.colnames[cols] cols = self.colnames[cols]
if isinstance(rows, int) and isinstance(cols, str): if isinstance(rows, int) and isinstance(cols, str):
@ -120,7 +132,7 @@ class DynamicTableMixin(BaseModel):
raise ValueError(f"Unsure how to get item with key {item}") raise ValueError(f"Unsure how to get item with key {item}")
def _slice_range( def _slice_range(
self, rows: Union[int, slice], cols: Optional[Union[str, List[str]]] = None self, rows: Union[int, slice, np.ndarray], cols: Optional[Union[str, List[str]]] = None
) -> Dict[str, Union[list, "NDArray", "VectorData"]]: ) -> Dict[str, Union[list, "NDArray", "VectorData"]]:
if cols is None: if cols is None:
cols = self.colnames cols = self.colnames
@ -128,7 +140,11 @@ class DynamicTableMixin(BaseModel):
cols = [cols] cols = [cols]
data = {} data = {}
for k in cols: for k in cols:
if isinstance(rows, np.ndarray):
val = [self._columns[k][i] for i in rows]
else:
val = self._columns[k][rows] val = self._columns[k][rows]
if isinstance(val, BaseModel): if isinstance(val, BaseModel):
# special case where pandas will unpack a pydantic model # special case where pandas will unpack a pydantic model
# into {n_fields} rows, rather than keeping it in a dict # into {n_fields} rows, rather than keeping it in a dict
@ -339,20 +355,20 @@ class VectorIndexMixin(BaseModel):
""" """
Mimicking :func:`hdmf.common.table.VectorIndex.__getitem_helper` Mimicking :func:`hdmf.common.table.VectorIndex.__getitem_helper`
""" """
start = 0 if arg == 0 else self.value[arg - 1] start = 0 if arg == 0 else self.value[arg - 1]
end = self.value[arg] end = self.value[arg]
return self.target.value[slice(start, end)] return self.target.value[slice(start, end)]
def __getitem__(self, item: Union[int, slice]) -> Any: def __getitem__(self, item: Union[int, slice, Iterable]) -> Any:
if self.target is None: if self.target is None:
return self.value[item] return self.value[item]
elif isinstance(self.target, VectorData): else:
if isinstance(item, int): if isinstance(item, int):
return self._getitem_helper(item) return self._getitem_helper(item)
else: elif isinstance(item, (slice, Iterable)):
idx = range(*item.indices(len(self.value))) if isinstance(item, slice):
return [self._getitem_helper(i) for i in idx] item = range(*item.indices(len(self.value)))
return [self._getitem_helper(i) for i in item]
else: else:
raise AttributeError(f"Could not index with {item}") raise AttributeError(f"Could not index with {item}")
@ -387,13 +403,40 @@ class DynamicTableRegionMixin(BaseModel):
Mixin to allow indexing references to regions of dynamictables Mixin to allow indexing references to regions of dynamictables
""" """
table: "DynamicTableMixin" _index: Optional["VectorIndex"] = None
def __getitem__(self, item: Union[str, int, slice, Tuple[Union[str, int, slice], ...]]) -> Any: table: "DynamicTableMixin"
return self.table[item] value: Optional[NDArray] = None
def __getitem__(self, item: Union[int, slice, Iterable]) -> Any:
"""
Use ``value`` to index the table. Works analogously to ``VectorIndex`` despite
this being a subclass of ``VectorData``
"""
if self._index:
if isinstance(item, int):
# index returns an array of indices,
# and indexing table with an array returns a list of rows
return self.table[self._index[item]]
elif isinstance(item, slice):
# index returns a list of arrays of indices,
# so we index table with an array to construct
# a list of lists of rows
return [self.table[idx] for idx in self._index[item]]
else:
raise ValueError(f"Dont know how to index with {item}, need an int or a slice")
else:
if isinstance(item, int):
return self.table[self.value[item]]
elif isinstance(item, (slice, Iterable)):
if isinstance(item, slice):
item = range(*item.indices(len(self.value)))
return [self.table[self.value[i]] for i in item]
else:
raise ValueError(f"Dont know how to index with {item}, need an int or a slice")
def __setitem__(self, key: Union[int, str, slice], value: Any) -> None: def __setitem__(self, key: Union[int, str, slice], value: Any) -> None:
self.table[key] = value self.table[self.value[key]] = value
DYNAMIC_TABLE_IMPORTS = Imports( DYNAMIC_TABLE_IMPORTS = Imports(
@ -405,8 +448,9 @@ DYNAMIC_TABLE_IMPORTS = Imports(
module="typing", module="typing",
objects=[ objects=[
ObjectImport(name="ClassVar"), ObjectImport(name="ClassVar"),
ObjectImport(name="overload"), ObjectImport(name="Iterable"),
ObjectImport(name="Tuple"), ObjectImport(name="Tuple"),
ObjectImport(name="overload"),
], ],
), ),
Import( Import(

View file

@ -5,7 +5,7 @@ from enum import Enum
import re import re
import sys import sys
from pandas import DataFrame, Series from pandas import DataFrame, Series
from typing import Any, ClassVar, List, Literal, Dict, Optional, Union, overload, Tuple from typing import Any, ClassVar, List, Literal, Dict, Optional, Union, Iterable, Tuple, overload
from numpydantic import NDArray, Shape from numpydantic import NDArray, Shape
from pydantic import ( from pydantic import (
BaseModel, BaseModel,
@ -128,20 +128,20 @@ class VectorIndexMixin(BaseModel):
""" """
Mimicking :func:`hdmf.common.table.VectorIndex.__getitem_helper` Mimicking :func:`hdmf.common.table.VectorIndex.__getitem_helper`
""" """
start = 0 if arg == 0 else self.value[arg - 1] start = 0 if arg == 0 else self.value[arg - 1]
end = self.value[arg] end = self.value[arg]
return self.target.value[slice(start, end)] return self.target.value[slice(start, end)]
def __getitem__(self, item: Union[int, slice]) -> Any: def __getitem__(self, item: Union[int, slice, Iterable]) -> Any:
if self.target is None: if self.target is None:
return self.value[item] return self.value[item]
elif isinstance(self.target, VectorData): else:
if isinstance(item, int): if isinstance(item, int):
return self._getitem_helper(item) return self._getitem_helper(item)
else: elif isinstance(item, (slice, Iterable)):
idx = range(*item.indices(len(self.value))) if isinstance(item, slice):
return [self._getitem_helper(i) for i in idx] item = range(*item.indices(len(self.value)))
return [self._getitem_helper(i) for i in item]
else: else:
raise AttributeError(f"Could not index with {item}") raise AttributeError(f"Could not index with {item}")
@ -176,13 +176,40 @@ class DynamicTableRegionMixin(BaseModel):
Mixin to allow indexing references to regions of dynamictables Mixin to allow indexing references to regions of dynamictables
""" """
table: "DynamicTableMixin" _index: Optional["VectorIndex"] = None
def __getitem__(self, item: Union[str, int, slice, Tuple[Union[str, int, slice], ...]]) -> Any: table: "DynamicTableMixin"
return self.table[item] value: Optional[NDArray] = None
def __getitem__(self, item: Union[int, slice, Iterable]) -> Any:
"""
Use ``value`` to index the table. Works analogously to ``VectorIndex`` despite
this being a subclass of ``VectorData``
"""
if self._index:
if isinstance(item, int):
# index returns an array of indices,
# and indexing table with an array returns a list of rows
return self.table[self._index[item]]
elif isinstance(item, slice):
# index returns a list of arrays of indices,
# so we index table with an array to construct
# a list of lists of rows
return [self.table[idx] for idx in self._index[item]]
else:
raise ValueError(f"Dont know how to index with {item}, need an int or a slice")
else:
if isinstance(item, int):
return self.table[self.value[item]]
elif isinstance(item, (slice, Iterable)):
if isinstance(item, slice):
item = range(*item.indices(len(self.value)))
return [self.table[self.value[i]] for i in item]
else:
raise ValueError(f"Dont know how to index with {item}, need an int or a slice")
def __setitem__(self, key: Union[int, str, slice], value: Any) -> None: def __setitem__(self, key: Union[int, str, slice], value: Any) -> None:
self.table[key] = value self.table[self.value[key]] = value
class DynamicTableMixin(BaseModel): class DynamicTableMixin(BaseModel):
@ -258,7 +285,7 @@ class DynamicTableMixin(BaseModel):
""" """
if isinstance(item, str): if isinstance(item, str):
return self._columns[item] return self._columns[item]
if isinstance(item, (int, slice)): if isinstance(item, (int, slice, np.integer, np.ndarray)):
return DataFrame.from_dict(self._slice_range(item)) return DataFrame.from_dict(self._slice_range(item))
elif isinstance(item, tuple): elif isinstance(item, tuple):
if len(item) != 2: if len(item) != 2:
@ -269,7 +296,7 @@ class DynamicTableMixin(BaseModel):
# all other cases are tuples of (rows, cols) # all other cases are tuples of (rows, cols)
rows, cols = item rows, cols = item
if isinstance(cols, (int, slice)): if isinstance(cols, (int, slice, np.integer)):
cols = self.colnames[cols] cols = self.colnames[cols]
if isinstance(rows, int) and isinstance(cols, str): if isinstance(rows, int) and isinstance(cols, str):
@ -282,7 +309,7 @@ class DynamicTableMixin(BaseModel):
raise ValueError(f"Unsure how to get item with key {item}") raise ValueError(f"Unsure how to get item with key {item}")
def _slice_range( def _slice_range(
self, rows: Union[int, slice], cols: Optional[Union[str, List[str]]] = None self, rows: Union[int, slice, np.ndarray], cols: Optional[Union[str, List[str]]] = None
) -> Dict[str, Union[list, "NDArray", "VectorData"]]: ) -> Dict[str, Union[list, "NDArray", "VectorData"]]:
if cols is None: if cols is None:
cols = self.colnames cols = self.colnames
@ -290,7 +317,11 @@ class DynamicTableMixin(BaseModel):
cols = [cols] cols = [cols]
data = {} data = {}
for k in cols: for k in cols:
if isinstance(rows, np.ndarray):
val = [self._columns[k][i] for i in rows]
else:
val = self._columns[k][rows] val = self._columns[k][rows]
if isinstance(val, BaseModel): if isinstance(val, BaseModel):
# special case where pandas will unpack a pydantic model # special case where pandas will unpack a pydantic model
# into {n_fields} rows, rather than keeping it in a dict # into {n_fields} rows, rather than keeping it in a dict
@ -515,7 +546,7 @@ class ElementIdentifiers(Data):
) )
class DynamicTableRegion(VectorData): class DynamicTableRegion(DynamicTableRegionMixin, VectorData):
""" """
DynamicTableRegion provides a link from one table to an index or region of another. The `table` attribute is a link to another `DynamicTable`, indicating which table is referenced, and the data is int(s) indicating the row(s) (0-indexed) of the target array. `DynamicTableRegion`s can be used to associate rows with repeated meta-data without data duplication. They can also be used to create hierarchical relationships between multiple `DynamicTable`s. `DynamicTableRegion` objects may be paired with a `VectorIndex` object to create ragged references, so a single cell of a `DynamicTable` can reference many rows of another `DynamicTable`. DynamicTableRegion provides a link from one table to an index or region of another. The `table` attribute is a link to another `DynamicTable`, indicating which table is referenced, and the data is int(s) indicating the row(s) (0-indexed) of the target array. `DynamicTableRegion`s can be used to associate rows with repeated meta-data without data duplication. They can also be used to create hierarchical relationships between multiple `DynamicTable`s. `DynamicTableRegion` objects may be paired with a `VectorIndex` object to create ragged references, so a single cell of a `DynamicTable` can reference many rows of another `DynamicTable`.
""" """

View file

@ -5,7 +5,7 @@ from enum import Enum
import re import re
import sys import sys
from pandas import DataFrame, Series from pandas import DataFrame, Series
from typing import Any, ClassVar, List, Literal, Dict, Optional, Union, overload, Tuple from typing import Any, ClassVar, List, Literal, Dict, Optional, Union, Iterable, Tuple, overload
from numpydantic import NDArray, Shape from numpydantic import NDArray, Shape
from pydantic import ( from pydantic import (
BaseModel, BaseModel,
@ -128,20 +128,20 @@ class VectorIndexMixin(BaseModel):
""" """
Mimicking :func:`hdmf.common.table.VectorIndex.__getitem_helper` Mimicking :func:`hdmf.common.table.VectorIndex.__getitem_helper`
""" """
start = 0 if arg == 0 else self.value[arg - 1] start = 0 if arg == 0 else self.value[arg - 1]
end = self.value[arg] end = self.value[arg]
return self.target.value[slice(start, end)] return self.target.value[slice(start, end)]
def __getitem__(self, item: Union[int, slice]) -> Any: def __getitem__(self, item: Union[int, slice, Iterable]) -> Any:
if self.target is None: if self.target is None:
return self.value[item] return self.value[item]
elif isinstance(self.target, VectorData): else:
if isinstance(item, int): if isinstance(item, int):
return self._getitem_helper(item) return self._getitem_helper(item)
else: elif isinstance(item, (slice, Iterable)):
idx = range(*item.indices(len(self.value))) if isinstance(item, slice):
return [self._getitem_helper(i) for i in idx] item = range(*item.indices(len(self.value)))
return [self._getitem_helper(i) for i in item]
else: else:
raise AttributeError(f"Could not index with {item}") raise AttributeError(f"Could not index with {item}")
@ -176,13 +176,40 @@ class DynamicTableRegionMixin(BaseModel):
Mixin to allow indexing references to regions of dynamictables Mixin to allow indexing references to regions of dynamictables
""" """
table: "DynamicTableMixin" _index: Optional["VectorIndex"] = None
def __getitem__(self, item: Union[str, int, slice, Tuple[Union[str, int, slice], ...]]) -> Any: table: "DynamicTableMixin"
return self.table[item] value: Optional[NDArray] = None
def __getitem__(self, item: Union[int, slice, Iterable]) -> Any:
"""
Use ``value`` to index the table. Works analogously to ``VectorIndex`` despite
this being a subclass of ``VectorData``
"""
if self._index:
if isinstance(item, int):
# index returns an array of indices,
# and indexing table with an array returns a list of rows
return self.table[self._index[item]]
elif isinstance(item, slice):
# index returns a list of arrays of indices,
# so we index table with an array to construct
# a list of lists of rows
return [self.table[idx] for idx in self._index[item]]
else:
raise ValueError(f"Dont know how to index with {item}, need an int or a slice")
else:
if isinstance(item, int):
return self.table[self.value[item]]
elif isinstance(item, (slice, Iterable)):
if isinstance(item, slice):
item = range(*item.indices(len(self.value)))
return [self.table[self.value[i]] for i in item]
else:
raise ValueError(f"Dont know how to index with {item}, need an int or a slice")
def __setitem__(self, key: Union[int, str, slice], value: Any) -> None: def __setitem__(self, key: Union[int, str, slice], value: Any) -> None:
self.table[key] = value self.table[self.value[key]] = value
class DynamicTableMixin(BaseModel): class DynamicTableMixin(BaseModel):
@ -258,7 +285,7 @@ class DynamicTableMixin(BaseModel):
""" """
if isinstance(item, str): if isinstance(item, str):
return self._columns[item] return self._columns[item]
if isinstance(item, (int, slice)): if isinstance(item, (int, slice, np.integer, np.ndarray)):
return DataFrame.from_dict(self._slice_range(item)) return DataFrame.from_dict(self._slice_range(item))
elif isinstance(item, tuple): elif isinstance(item, tuple):
if len(item) != 2: if len(item) != 2:
@ -269,7 +296,7 @@ class DynamicTableMixin(BaseModel):
# all other cases are tuples of (rows, cols) # all other cases are tuples of (rows, cols)
rows, cols = item rows, cols = item
if isinstance(cols, (int, slice)): if isinstance(cols, (int, slice, np.integer)):
cols = self.colnames[cols] cols = self.colnames[cols]
if isinstance(rows, int) and isinstance(cols, str): if isinstance(rows, int) and isinstance(cols, str):
@ -282,7 +309,7 @@ class DynamicTableMixin(BaseModel):
raise ValueError(f"Unsure how to get item with key {item}") raise ValueError(f"Unsure how to get item with key {item}")
def _slice_range( def _slice_range(
self, rows: Union[int, slice], cols: Optional[Union[str, List[str]]] = None self, rows: Union[int, slice, np.ndarray], cols: Optional[Union[str, List[str]]] = None
) -> Dict[str, Union[list, "NDArray", "VectorData"]]: ) -> Dict[str, Union[list, "NDArray", "VectorData"]]:
if cols is None: if cols is None:
cols = self.colnames cols = self.colnames
@ -290,7 +317,11 @@ class DynamicTableMixin(BaseModel):
cols = [cols] cols = [cols]
data = {} data = {}
for k in cols: for k in cols:
if isinstance(rows, np.ndarray):
val = [self._columns[k][i] for i in rows]
else:
val = self._columns[k][rows] val = self._columns[k][rows]
if isinstance(val, BaseModel): if isinstance(val, BaseModel):
# special case where pandas will unpack a pydantic model # special case where pandas will unpack a pydantic model
# into {n_fields} rows, rather than keeping it in a dict # into {n_fields} rows, rather than keeping it in a dict
@ -515,7 +546,7 @@ class ElementIdentifiers(Data):
) )
class DynamicTableRegion(VectorData): class DynamicTableRegion(DynamicTableRegionMixin, VectorData):
""" """
DynamicTableRegion provides a link from one table to an index or region of another. The `table` attribute is a link to another `DynamicTable`, indicating which table is referenced, and the data is int(s) indicating the row(s) (0-indexed) of the target array. `DynamicTableRegion`s can be used to associate rows with repeated meta-data without data duplication. They can also be used to create hierarchical relationships between multiple `DynamicTable`s. `DynamicTableRegion` objects may be paired with a `VectorIndex` object to create ragged references, so a single cell of a `DynamicTable` can reference many rows of another `DynamicTable`. DynamicTableRegion provides a link from one table to an index or region of another. The `table` attribute is a link to another `DynamicTable`, indicating which table is referenced, and the data is int(s) indicating the row(s) (0-indexed) of the target array. `DynamicTableRegion`s can be used to associate rows with repeated meta-data without data duplication. They can also be used to create hierarchical relationships between multiple `DynamicTable`s. `DynamicTableRegion` objects may be paired with a `VectorIndex` object to create ragged references, so a single cell of a `DynamicTable` can reference many rows of another `DynamicTable`.
""" """

View file

@ -5,7 +5,7 @@ from enum import Enum
import re import re
import sys import sys
from pandas import DataFrame, Series from pandas import DataFrame, Series
from typing import Any, ClassVar, List, Literal, Dict, Optional, Union, overload, Tuple from typing import Any, ClassVar, List, Literal, Dict, Optional, Union, Iterable, Tuple, overload
from numpydantic import NDArray, Shape from numpydantic import NDArray, Shape
from pydantic import ( from pydantic import (
BaseModel, BaseModel,
@ -128,20 +128,20 @@ class VectorIndexMixin(BaseModel):
""" """
Mimicking :func:`hdmf.common.table.VectorIndex.__getitem_helper` Mimicking :func:`hdmf.common.table.VectorIndex.__getitem_helper`
""" """
start = 0 if arg == 0 else self.value[arg - 1] start = 0 if arg == 0 else self.value[arg - 1]
end = self.value[arg] end = self.value[arg]
return self.target.value[slice(start, end)] return self.target.value[slice(start, end)]
def __getitem__(self, item: Union[int, slice]) -> Any: def __getitem__(self, item: Union[int, slice, Iterable]) -> Any:
if self.target is None: if self.target is None:
return self.value[item] return self.value[item]
elif isinstance(self.target, VectorData): else:
if isinstance(item, int): if isinstance(item, int):
return self._getitem_helper(item) return self._getitem_helper(item)
else: elif isinstance(item, (slice, Iterable)):
idx = range(*item.indices(len(self.value))) if isinstance(item, slice):
return [self._getitem_helper(i) for i in idx] item = range(*item.indices(len(self.value)))
return [self._getitem_helper(i) for i in item]
else: else:
raise AttributeError(f"Could not index with {item}") raise AttributeError(f"Could not index with {item}")
@ -176,13 +176,40 @@ class DynamicTableRegionMixin(BaseModel):
Mixin to allow indexing references to regions of dynamictables Mixin to allow indexing references to regions of dynamictables
""" """
table: "DynamicTableMixin" _index: Optional["VectorIndex"] = None
def __getitem__(self, item: Union[str, int, slice, Tuple[Union[str, int, slice], ...]]) -> Any: table: "DynamicTableMixin"
return self.table[item] value: Optional[NDArray] = None
def __getitem__(self, item: Union[int, slice, Iterable]) -> Any:
"""
Use ``value`` to index the table. Works analogously to ``VectorIndex`` despite
this being a subclass of ``VectorData``
"""
if self._index:
if isinstance(item, int):
# index returns an array of indices,
# and indexing table with an array returns a list of rows
return self.table[self._index[item]]
elif isinstance(item, slice):
# index returns a list of arrays of indices,
# so we index table with an array to construct
# a list of lists of rows
return [self.table[idx] for idx in self._index[item]]
else:
raise ValueError(f"Dont know how to index with {item}, need an int or a slice")
else:
if isinstance(item, int):
return self.table[self.value[item]]
elif isinstance(item, (slice, Iterable)):
if isinstance(item, slice):
item = range(*item.indices(len(self.value)))
return [self.table[self.value[i]] for i in item]
else:
raise ValueError(f"Dont know how to index with {item}, need an int or a slice")
def __setitem__(self, key: Union[int, str, slice], value: Any) -> None: def __setitem__(self, key: Union[int, str, slice], value: Any) -> None:
self.table[key] = value self.table[self.value[key]] = value
class DynamicTableMixin(BaseModel): class DynamicTableMixin(BaseModel):
@ -258,7 +285,7 @@ class DynamicTableMixin(BaseModel):
""" """
if isinstance(item, str): if isinstance(item, str):
return self._columns[item] return self._columns[item]
if isinstance(item, (int, slice)): if isinstance(item, (int, slice, np.integer, np.ndarray)):
return DataFrame.from_dict(self._slice_range(item)) return DataFrame.from_dict(self._slice_range(item))
elif isinstance(item, tuple): elif isinstance(item, tuple):
if len(item) != 2: if len(item) != 2:
@ -269,7 +296,7 @@ class DynamicTableMixin(BaseModel):
# all other cases are tuples of (rows, cols) # all other cases are tuples of (rows, cols)
rows, cols = item rows, cols = item
if isinstance(cols, (int, slice)): if isinstance(cols, (int, slice, np.integer)):
cols = self.colnames[cols] cols = self.colnames[cols]
if isinstance(rows, int) and isinstance(cols, str): if isinstance(rows, int) and isinstance(cols, str):
@ -282,7 +309,7 @@ class DynamicTableMixin(BaseModel):
raise ValueError(f"Unsure how to get item with key {item}") raise ValueError(f"Unsure how to get item with key {item}")
def _slice_range( def _slice_range(
self, rows: Union[int, slice], cols: Optional[Union[str, List[str]]] = None self, rows: Union[int, slice, np.ndarray], cols: Optional[Union[str, List[str]]] = None
) -> Dict[str, Union[list, "NDArray", "VectorData"]]: ) -> Dict[str, Union[list, "NDArray", "VectorData"]]:
if cols is None: if cols is None:
cols = self.colnames cols = self.colnames
@ -290,7 +317,11 @@ class DynamicTableMixin(BaseModel):
cols = [cols] cols = [cols]
data = {} data = {}
for k in cols: for k in cols:
if isinstance(rows, np.ndarray):
val = [self._columns[k][i] for i in rows]
else:
val = self._columns[k][rows] val = self._columns[k][rows]
if isinstance(val, BaseModel): if isinstance(val, BaseModel):
# special case where pandas will unpack a pydantic model # special case where pandas will unpack a pydantic model
# into {n_fields} rows, rather than keeping it in a dict # into {n_fields} rows, rather than keeping it in a dict
@ -526,7 +557,7 @@ class ElementIdentifiers(Data):
) )
class DynamicTableRegion(VectorData): class DynamicTableRegion(DynamicTableRegionMixin, VectorData):
""" """
DynamicTableRegion provides a link from one table to an index or region of another. The `table` attribute is a link to another `DynamicTable`, indicating which table is referenced, and the data is int(s) indicating the row(s) (0-indexed) of the target array. `DynamicTableRegion`s can be used to associate rows with repeated meta-data without data duplication. They can also be used to create hierarchical relationships between multiple `DynamicTable`s. `DynamicTableRegion` objects may be paired with a `VectorIndex` object to create ragged references, so a single cell of a `DynamicTable` can reference many rows of another `DynamicTable`. DynamicTableRegion provides a link from one table to an index or region of another. The `table` attribute is a link to another `DynamicTable`, indicating which table is referenced, and the data is int(s) indicating the row(s) (0-indexed) of the target array. `DynamicTableRegion`s can be used to associate rows with repeated meta-data without data duplication. They can also be used to create hierarchical relationships between multiple `DynamicTable`s. `DynamicTableRegion` objects may be paired with a `VectorIndex` object to create ragged references, so a single cell of a `DynamicTable` can reference many rows of another `DynamicTable`.
""" """

View file

@ -6,7 +6,7 @@ import re
import sys import sys
from ...hdmf_common.v1_2_0.hdmf_common_base import Data, Container from ...hdmf_common.v1_2_0.hdmf_common_base import Data, Container
from pandas import DataFrame, Series from pandas import DataFrame, Series
from typing import Any, ClassVar, List, Literal, Dict, Optional, Union, overload, Tuple from typing import Any, ClassVar, List, Literal, Dict, Optional, Union, Iterable, Tuple, overload
from numpydantic import NDArray, Shape from numpydantic import NDArray, Shape
from pydantic import ( from pydantic import (
BaseModel, BaseModel,
@ -129,20 +129,20 @@ class VectorIndexMixin(BaseModel):
""" """
Mimicking :func:`hdmf.common.table.VectorIndex.__getitem_helper` Mimicking :func:`hdmf.common.table.VectorIndex.__getitem_helper`
""" """
start = 0 if arg == 0 else self.value[arg - 1] start = 0 if arg == 0 else self.value[arg - 1]
end = self.value[arg] end = self.value[arg]
return self.target.value[slice(start, end)] return self.target.value[slice(start, end)]
def __getitem__(self, item: Union[int, slice]) -> Any: def __getitem__(self, item: Union[int, slice, Iterable]) -> Any:
if self.target is None: if self.target is None:
return self.value[item] return self.value[item]
elif isinstance(self.target, VectorData): else:
if isinstance(item, int): if isinstance(item, int):
return self._getitem_helper(item) return self._getitem_helper(item)
else: elif isinstance(item, (slice, Iterable)):
idx = range(*item.indices(len(self.value))) if isinstance(item, slice):
return [self._getitem_helper(i) for i in idx] item = range(*item.indices(len(self.value)))
return [self._getitem_helper(i) for i in item]
else: else:
raise AttributeError(f"Could not index with {item}") raise AttributeError(f"Could not index with {item}")
@ -177,13 +177,40 @@ class DynamicTableRegionMixin(BaseModel):
Mixin to allow indexing references to regions of dynamictables Mixin to allow indexing references to regions of dynamictables
""" """
table: "DynamicTableMixin" _index: Optional["VectorIndex"] = None
def __getitem__(self, item: Union[str, int, slice, Tuple[Union[str, int, slice], ...]]) -> Any: table: "DynamicTableMixin"
return self.table[item] value: Optional[NDArray] = None
def __getitem__(self, item: Union[int, slice, Iterable]) -> Any:
"""
Use ``value`` to index the table. Works analogously to ``VectorIndex`` despite
this being a subclass of ``VectorData``
"""
if self._index:
if isinstance(item, int):
# index returns an array of indices,
# and indexing table with an array returns a list of rows
return self.table[self._index[item]]
elif isinstance(item, slice):
# index returns a list of arrays of indices,
# so we index table with an array to construct
# a list of lists of rows
return [self.table[idx] for idx in self._index[item]]
else:
raise ValueError(f"Dont know how to index with {item}, need an int or a slice")
else:
if isinstance(item, int):
return self.table[self.value[item]]
elif isinstance(item, (slice, Iterable)):
if isinstance(item, slice):
item = range(*item.indices(len(self.value)))
return [self.table[self.value[i]] for i in item]
else:
raise ValueError(f"Dont know how to index with {item}, need an int or a slice")
def __setitem__(self, key: Union[int, str, slice], value: Any) -> None: def __setitem__(self, key: Union[int, str, slice], value: Any) -> None:
self.table[key] = value self.table[self.value[key]] = value
class DynamicTableMixin(BaseModel): class DynamicTableMixin(BaseModel):
@ -259,7 +286,7 @@ class DynamicTableMixin(BaseModel):
""" """
if isinstance(item, str): if isinstance(item, str):
return self._columns[item] return self._columns[item]
if isinstance(item, (int, slice)): if isinstance(item, (int, slice, np.integer, np.ndarray)):
return DataFrame.from_dict(self._slice_range(item)) return DataFrame.from_dict(self._slice_range(item))
elif isinstance(item, tuple): elif isinstance(item, tuple):
if len(item) != 2: if len(item) != 2:
@ -270,7 +297,7 @@ class DynamicTableMixin(BaseModel):
# all other cases are tuples of (rows, cols) # all other cases are tuples of (rows, cols)
rows, cols = item rows, cols = item
if isinstance(cols, (int, slice)): if isinstance(cols, (int, slice, np.integer)):
cols = self.colnames[cols] cols = self.colnames[cols]
if isinstance(rows, int) and isinstance(cols, str): if isinstance(rows, int) and isinstance(cols, str):
@ -283,7 +310,7 @@ class DynamicTableMixin(BaseModel):
raise ValueError(f"Unsure how to get item with key {item}") raise ValueError(f"Unsure how to get item with key {item}")
def _slice_range( def _slice_range(
self, rows: Union[int, slice], cols: Optional[Union[str, List[str]]] = None self, rows: Union[int, slice, np.ndarray], cols: Optional[Union[str, List[str]]] = None
) -> Dict[str, Union[list, "NDArray", "VectorData"]]: ) -> Dict[str, Union[list, "NDArray", "VectorData"]]:
if cols is None: if cols is None:
cols = self.colnames cols = self.colnames
@ -291,7 +318,11 @@ class DynamicTableMixin(BaseModel):
cols = [cols] cols = [cols]
data = {} data = {}
for k in cols: for k in cols:
if isinstance(rows, np.ndarray):
val = [self._columns[k][i] for i in rows]
else:
val = self._columns[k][rows] val = self._columns[k][rows]
if isinstance(val, BaseModel): if isinstance(val, BaseModel):
# special case where pandas will unpack a pydantic model # special case where pandas will unpack a pydantic model
# into {n_fields} rows, rather than keeping it in a dict # into {n_fields} rows, rather than keeping it in a dict
@ -506,7 +537,7 @@ class ElementIdentifiers(Data):
) )
class DynamicTableRegion(VectorData): class DynamicTableRegion(DynamicTableRegionMixin, VectorData):
""" """
DynamicTableRegion provides a link from one table to an index or region of another. The `table` attribute is a link to another `DynamicTable`, indicating which table is referenced, and the data is int(s) indicating the row(s) (0-indexed) of the target array. `DynamicTableRegion`s can be used to associate rows with repeated meta-data without data duplication. They can also be used to create hierarchical relationships between multiple `DynamicTable`s. `DynamicTableRegion` objects may be paired with a `VectorIndex` object to create ragged references, so a single cell of a `DynamicTable` can reference many rows of another `DynamicTable`. DynamicTableRegion provides a link from one table to an index or region of another. The `table` attribute is a link to another `DynamicTable`, indicating which table is referenced, and the data is int(s) indicating the row(s) (0-indexed) of the target array. `DynamicTableRegion`s can be used to associate rows with repeated meta-data without data duplication. They can also be used to create hierarchical relationships between multiple `DynamicTable`s. `DynamicTableRegion` objects may be paired with a `VectorIndex` object to create ragged references, so a single cell of a `DynamicTable` can reference many rows of another `DynamicTable`.
""" """

View file

@ -6,7 +6,7 @@ import re
import sys import sys
from ...hdmf_common.v1_2_1.hdmf_common_base import Data, Container from ...hdmf_common.v1_2_1.hdmf_common_base import Data, Container
from pandas import DataFrame, Series from pandas import DataFrame, Series
from typing import Any, ClassVar, List, Literal, Dict, Optional, Union, overload, Tuple from typing import Any, ClassVar, List, Literal, Dict, Optional, Union, Iterable, Tuple, overload
from numpydantic import NDArray, Shape from numpydantic import NDArray, Shape
from pydantic import ( from pydantic import (
BaseModel, BaseModel,
@ -129,20 +129,20 @@ class VectorIndexMixin(BaseModel):
""" """
Mimicking :func:`hdmf.common.table.VectorIndex.__getitem_helper` Mimicking :func:`hdmf.common.table.VectorIndex.__getitem_helper`
""" """
start = 0 if arg == 0 else self.value[arg - 1] start = 0 if arg == 0 else self.value[arg - 1]
end = self.value[arg] end = self.value[arg]
return self.target.value[slice(start, end)] return self.target.value[slice(start, end)]
def __getitem__(self, item: Union[int, slice]) -> Any: def __getitem__(self, item: Union[int, slice, Iterable]) -> Any:
if self.target is None: if self.target is None:
return self.value[item] return self.value[item]
elif isinstance(self.target, VectorData): else:
if isinstance(item, int): if isinstance(item, int):
return self._getitem_helper(item) return self._getitem_helper(item)
else: elif isinstance(item, (slice, Iterable)):
idx = range(*item.indices(len(self.value))) if isinstance(item, slice):
return [self._getitem_helper(i) for i in idx] item = range(*item.indices(len(self.value)))
return [self._getitem_helper(i) for i in item]
else: else:
raise AttributeError(f"Could not index with {item}") raise AttributeError(f"Could not index with {item}")
@ -177,13 +177,40 @@ class DynamicTableRegionMixin(BaseModel):
Mixin to allow indexing references to regions of dynamictables Mixin to allow indexing references to regions of dynamictables
""" """
table: "DynamicTableMixin" _index: Optional["VectorIndex"] = None
def __getitem__(self, item: Union[str, int, slice, Tuple[Union[str, int, slice], ...]]) -> Any: table: "DynamicTableMixin"
return self.table[item] value: Optional[NDArray] = None
def __getitem__(self, item: Union[int, slice, Iterable]) -> Any:
"""
Use ``value`` to index the table. Works analogously to ``VectorIndex`` despite
this being a subclass of ``VectorData``
"""
if self._index:
if isinstance(item, int):
# index returns an array of indices,
# and indexing table with an array returns a list of rows
return self.table[self._index[item]]
elif isinstance(item, slice):
# index returns a list of arrays of indices,
# so we index table with an array to construct
# a list of lists of rows
return [self.table[idx] for idx in self._index[item]]
else:
raise ValueError(f"Dont know how to index with {item}, need an int or a slice")
else:
if isinstance(item, int):
return self.table[self.value[item]]
elif isinstance(item, (slice, Iterable)):
if isinstance(item, slice):
item = range(*item.indices(len(self.value)))
return [self.table[self.value[i]] for i in item]
else:
raise ValueError(f"Dont know how to index with {item}, need an int or a slice")
def __setitem__(self, key: Union[int, str, slice], value: Any) -> None: def __setitem__(self, key: Union[int, str, slice], value: Any) -> None:
self.table[key] = value self.table[self.value[key]] = value
class DynamicTableMixin(BaseModel): class DynamicTableMixin(BaseModel):
@ -259,7 +286,7 @@ class DynamicTableMixin(BaseModel):
""" """
if isinstance(item, str): if isinstance(item, str):
return self._columns[item] return self._columns[item]
if isinstance(item, (int, slice)): if isinstance(item, (int, slice, np.integer, np.ndarray)):
return DataFrame.from_dict(self._slice_range(item)) return DataFrame.from_dict(self._slice_range(item))
elif isinstance(item, tuple): elif isinstance(item, tuple):
if len(item) != 2: if len(item) != 2:
@ -270,7 +297,7 @@ class DynamicTableMixin(BaseModel):
# all other cases are tuples of (rows, cols) # all other cases are tuples of (rows, cols)
rows, cols = item rows, cols = item
if isinstance(cols, (int, slice)): if isinstance(cols, (int, slice, np.integer)):
cols = self.colnames[cols] cols = self.colnames[cols]
if isinstance(rows, int) and isinstance(cols, str): if isinstance(rows, int) and isinstance(cols, str):
@ -283,7 +310,7 @@ class DynamicTableMixin(BaseModel):
raise ValueError(f"Unsure how to get item with key {item}") raise ValueError(f"Unsure how to get item with key {item}")
def _slice_range( def _slice_range(
self, rows: Union[int, slice], cols: Optional[Union[str, List[str]]] = None self, rows: Union[int, slice, np.ndarray], cols: Optional[Union[str, List[str]]] = None
) -> Dict[str, Union[list, "NDArray", "VectorData"]]: ) -> Dict[str, Union[list, "NDArray", "VectorData"]]:
if cols is None: if cols is None:
cols = self.colnames cols = self.colnames
@ -291,7 +318,11 @@ class DynamicTableMixin(BaseModel):
cols = [cols] cols = [cols]
data = {} data = {}
for k in cols: for k in cols:
if isinstance(rows, np.ndarray):
val = [self._columns[k][i] for i in rows]
else:
val = self._columns[k][rows] val = self._columns[k][rows]
if isinstance(val, BaseModel): if isinstance(val, BaseModel):
# special case where pandas will unpack a pydantic model # special case where pandas will unpack a pydantic model
# into {n_fields} rows, rather than keeping it in a dict # into {n_fields} rows, rather than keeping it in a dict
@ -506,7 +537,7 @@ class ElementIdentifiers(Data):
) )
class DynamicTableRegion(VectorData): class DynamicTableRegion(DynamicTableRegionMixin, VectorData):
""" """
DynamicTableRegion provides a link from one table to an index or region of another. The `table` attribute is a link to another `DynamicTable`, indicating which table is referenced, and the data is int(s) indicating the row(s) (0-indexed) of the target array. `DynamicTableRegion`s can be used to associate rows with repeated meta-data without data duplication. They can also be used to create hierarchical relationships between multiple `DynamicTable`s. `DynamicTableRegion` objects may be paired with a `VectorIndex` object to create ragged references, so a single cell of a `DynamicTable` can reference many rows of another `DynamicTable`. DynamicTableRegion provides a link from one table to an index or region of another. The `table` attribute is a link to another `DynamicTable`, indicating which table is referenced, and the data is int(s) indicating the row(s) (0-indexed) of the target array. `DynamicTableRegion`s can be used to associate rows with repeated meta-data without data duplication. They can also be used to create hierarchical relationships between multiple `DynamicTable`s. `DynamicTableRegion` objects may be paired with a `VectorIndex` object to create ragged references, so a single cell of a `DynamicTable` can reference many rows of another `DynamicTable`.
""" """

View file

@ -6,7 +6,7 @@ import re
import sys import sys
from ...hdmf_common.v1_3_0.hdmf_common_base import Data, Container from ...hdmf_common.v1_3_0.hdmf_common_base import Data, Container
from pandas import DataFrame, Series from pandas import DataFrame, Series
from typing import Any, ClassVar, List, Literal, Dict, Optional, Union, overload, Tuple from typing import Any, ClassVar, List, Literal, Dict, Optional, Union, Iterable, Tuple, overload
from numpydantic import NDArray, Shape from numpydantic import NDArray, Shape
from pydantic import ( from pydantic import (
BaseModel, BaseModel,
@ -129,20 +129,20 @@ class VectorIndexMixin(BaseModel):
""" """
Mimicking :func:`hdmf.common.table.VectorIndex.__getitem_helper` Mimicking :func:`hdmf.common.table.VectorIndex.__getitem_helper`
""" """
start = 0 if arg == 0 else self.value[arg - 1] start = 0 if arg == 0 else self.value[arg - 1]
end = self.value[arg] end = self.value[arg]
return self.target.value[slice(start, end)] return self.target.value[slice(start, end)]
def __getitem__(self, item: Union[int, slice]) -> Any: def __getitem__(self, item: Union[int, slice, Iterable]) -> Any:
if self.target is None: if self.target is None:
return self.value[item] return self.value[item]
elif isinstance(self.target, VectorData): else:
if isinstance(item, int): if isinstance(item, int):
return self._getitem_helper(item) return self._getitem_helper(item)
else: elif isinstance(item, (slice, Iterable)):
idx = range(*item.indices(len(self.value))) if isinstance(item, slice):
return [self._getitem_helper(i) for i in idx] item = range(*item.indices(len(self.value)))
return [self._getitem_helper(i) for i in item]
else: else:
raise AttributeError(f"Could not index with {item}") raise AttributeError(f"Could not index with {item}")
@ -177,13 +177,40 @@ class DynamicTableRegionMixin(BaseModel):
Mixin to allow indexing references to regions of dynamictables Mixin to allow indexing references to regions of dynamictables
""" """
table: "DynamicTableMixin" _index: Optional["VectorIndex"] = None
def __getitem__(self, item: Union[str, int, slice, Tuple[Union[str, int, slice], ...]]) -> Any: table: "DynamicTableMixin"
return self.table[item] value: Optional[NDArray] = None
def __getitem__(self, item: Union[int, slice, Iterable]) -> Any:
"""
Use ``value`` to index the table. Works analogously to ``VectorIndex`` despite
this being a subclass of ``VectorData``
"""
if self._index:
if isinstance(item, int):
# index returns an array of indices,
# and indexing table with an array returns a list of rows
return self.table[self._index[item]]
elif isinstance(item, slice):
# index returns a list of arrays of indices,
# so we index table with an array to construct
# a list of lists of rows
return [self.table[idx] for idx in self._index[item]]
else:
raise ValueError(f"Dont know how to index with {item}, need an int or a slice")
else:
if isinstance(item, int):
return self.table[self.value[item]]
elif isinstance(item, (slice, Iterable)):
if isinstance(item, slice):
item = range(*item.indices(len(self.value)))
return [self.table[self.value[i]] for i in item]
else:
raise ValueError(f"Dont know how to index with {item}, need an int or a slice")
def __setitem__(self, key: Union[int, str, slice], value: Any) -> None: def __setitem__(self, key: Union[int, str, slice], value: Any) -> None:
self.table[key] = value self.table[self.value[key]] = value
class DynamicTableMixin(BaseModel): class DynamicTableMixin(BaseModel):
@ -259,7 +286,7 @@ class DynamicTableMixin(BaseModel):
""" """
if isinstance(item, str): if isinstance(item, str):
return self._columns[item] return self._columns[item]
if isinstance(item, (int, slice)): if isinstance(item, (int, slice, np.integer, np.ndarray)):
return DataFrame.from_dict(self._slice_range(item)) return DataFrame.from_dict(self._slice_range(item))
elif isinstance(item, tuple): elif isinstance(item, tuple):
if len(item) != 2: if len(item) != 2:
@ -270,7 +297,7 @@ class DynamicTableMixin(BaseModel):
# all other cases are tuples of (rows, cols) # all other cases are tuples of (rows, cols)
rows, cols = item rows, cols = item
if isinstance(cols, (int, slice)): if isinstance(cols, (int, slice, np.integer)):
cols = self.colnames[cols] cols = self.colnames[cols]
if isinstance(rows, int) and isinstance(cols, str): if isinstance(rows, int) and isinstance(cols, str):
@ -283,7 +310,7 @@ class DynamicTableMixin(BaseModel):
raise ValueError(f"Unsure how to get item with key {item}") raise ValueError(f"Unsure how to get item with key {item}")
def _slice_range( def _slice_range(
self, rows: Union[int, slice], cols: Optional[Union[str, List[str]]] = None self, rows: Union[int, slice, np.ndarray], cols: Optional[Union[str, List[str]]] = None
) -> Dict[str, Union[list, "NDArray", "VectorData"]]: ) -> Dict[str, Union[list, "NDArray", "VectorData"]]:
if cols is None: if cols is None:
cols = self.colnames cols = self.colnames
@ -291,7 +318,11 @@ class DynamicTableMixin(BaseModel):
cols = [cols] cols = [cols]
data = {} data = {}
for k in cols: for k in cols:
if isinstance(rows, np.ndarray):
val = [self._columns[k][i] for i in rows]
else:
val = self._columns[k][rows] val = self._columns[k][rows]
if isinstance(val, BaseModel): if isinstance(val, BaseModel):
# special case where pandas will unpack a pydantic model # special case where pandas will unpack a pydantic model
# into {n_fields} rows, rather than keeping it in a dict # into {n_fields} rows, rather than keeping it in a dict
@ -506,7 +537,7 @@ class ElementIdentifiers(Data):
) )
class DynamicTableRegion(VectorData): class DynamicTableRegion(DynamicTableRegionMixin, VectorData):
""" """
DynamicTableRegion provides a link from one table to an index or region of another. The `table` attribute is a link to another `DynamicTable`, indicating which table is referenced, and the data is int(s) indicating the row(s) (0-indexed) of the target array. `DynamicTableRegion`s can be used to associate rows with repeated meta-data without data duplication. They can also be used to create hierarchical relationships between multiple `DynamicTable`s. `DynamicTableRegion` objects may be paired with a `VectorIndex` object to create ragged references, so a single cell of a `DynamicTable` can reference many rows of another `DynamicTable`. DynamicTableRegion provides a link from one table to an index or region of another. The `table` attribute is a link to another `DynamicTable`, indicating which table is referenced, and the data is int(s) indicating the row(s) (0-indexed) of the target array. `DynamicTableRegion`s can be used to associate rows with repeated meta-data without data duplication. They can also be used to create hierarchical relationships between multiple `DynamicTable`s. `DynamicTableRegion` objects may be paired with a `VectorIndex` object to create ragged references, so a single cell of a `DynamicTable` can reference many rows of another `DynamicTable`.
""" """

View file

@ -6,7 +6,7 @@ import re
import sys import sys
from ...hdmf_common.v1_4_0.hdmf_common_base import Data, Container from ...hdmf_common.v1_4_0.hdmf_common_base import Data, Container
from pandas import DataFrame, Series from pandas import DataFrame, Series
from typing import Any, ClassVar, List, Literal, Dict, Optional, Union, overload, Tuple from typing import Any, ClassVar, List, Literal, Dict, Optional, Union, Iterable, Tuple, overload
from numpydantic import NDArray, Shape from numpydantic import NDArray, Shape
from pydantic import ( from pydantic import (
BaseModel, BaseModel,
@ -129,20 +129,20 @@ class VectorIndexMixin(BaseModel):
""" """
Mimicking :func:`hdmf.common.table.VectorIndex.__getitem_helper` Mimicking :func:`hdmf.common.table.VectorIndex.__getitem_helper`
""" """
start = 0 if arg == 0 else self.value[arg - 1] start = 0 if arg == 0 else self.value[arg - 1]
end = self.value[arg] end = self.value[arg]
return self.target.value[slice(start, end)] return self.target.value[slice(start, end)]
def __getitem__(self, item: Union[int, slice]) -> Any: def __getitem__(self, item: Union[int, slice, Iterable]) -> Any:
if self.target is None: if self.target is None:
return self.value[item] return self.value[item]
elif isinstance(self.target, VectorData): else:
if isinstance(item, int): if isinstance(item, int):
return self._getitem_helper(item) return self._getitem_helper(item)
else: elif isinstance(item, (slice, Iterable)):
idx = range(*item.indices(len(self.value))) if isinstance(item, slice):
return [self._getitem_helper(i) for i in idx] item = range(*item.indices(len(self.value)))
return [self._getitem_helper(i) for i in item]
else: else:
raise AttributeError(f"Could not index with {item}") raise AttributeError(f"Could not index with {item}")
@ -177,13 +177,40 @@ class DynamicTableRegionMixin(BaseModel):
Mixin to allow indexing references to regions of dynamictables Mixin to allow indexing references to regions of dynamictables
""" """
table: "DynamicTableMixin" _index: Optional["VectorIndex"] = None
def __getitem__(self, item: Union[str, int, slice, Tuple[Union[str, int, slice], ...]]) -> Any: table: "DynamicTableMixin"
return self.table[item] value: Optional[NDArray] = None
def __getitem__(self, item: Union[int, slice, Iterable]) -> Any:
"""
Use ``value`` to index the table. Works analogously to ``VectorIndex`` despite
this being a subclass of ``VectorData``
"""
if self._index:
if isinstance(item, int):
# index returns an array of indices,
# and indexing table with an array returns a list of rows
return self.table[self._index[item]]
elif isinstance(item, slice):
# index returns a list of arrays of indices,
# so we index table with an array to construct
# a list of lists of rows
return [self.table[idx] for idx in self._index[item]]
else:
raise ValueError(f"Dont know how to index with {item}, need an int or a slice")
else:
if isinstance(item, int):
return self.table[self.value[item]]
elif isinstance(item, (slice, Iterable)):
if isinstance(item, slice):
item = range(*item.indices(len(self.value)))
return [self.table[self.value[i]] for i in item]
else:
raise ValueError(f"Dont know how to index with {item}, need an int or a slice")
def __setitem__(self, key: Union[int, str, slice], value: Any) -> None: def __setitem__(self, key: Union[int, str, slice], value: Any) -> None:
self.table[key] = value self.table[self.value[key]] = value
class DynamicTableMixin(BaseModel): class DynamicTableMixin(BaseModel):
@ -259,7 +286,7 @@ class DynamicTableMixin(BaseModel):
""" """
if isinstance(item, str): if isinstance(item, str):
return self._columns[item] return self._columns[item]
if isinstance(item, (int, slice)): if isinstance(item, (int, slice, np.integer, np.ndarray)):
return DataFrame.from_dict(self._slice_range(item)) return DataFrame.from_dict(self._slice_range(item))
elif isinstance(item, tuple): elif isinstance(item, tuple):
if len(item) != 2: if len(item) != 2:
@ -270,7 +297,7 @@ class DynamicTableMixin(BaseModel):
# all other cases are tuples of (rows, cols) # all other cases are tuples of (rows, cols)
rows, cols = item rows, cols = item
if isinstance(cols, (int, slice)): if isinstance(cols, (int, slice, np.integer)):
cols = self.colnames[cols] cols = self.colnames[cols]
if isinstance(rows, int) and isinstance(cols, str): if isinstance(rows, int) and isinstance(cols, str):
@ -283,7 +310,7 @@ class DynamicTableMixin(BaseModel):
raise ValueError(f"Unsure how to get item with key {item}") raise ValueError(f"Unsure how to get item with key {item}")
def _slice_range( def _slice_range(
self, rows: Union[int, slice], cols: Optional[Union[str, List[str]]] = None self, rows: Union[int, slice, np.ndarray], cols: Optional[Union[str, List[str]]] = None
) -> Dict[str, Union[list, "NDArray", "VectorData"]]: ) -> Dict[str, Union[list, "NDArray", "VectorData"]]:
if cols is None: if cols is None:
cols = self.colnames cols = self.colnames
@ -291,7 +318,11 @@ class DynamicTableMixin(BaseModel):
cols = [cols] cols = [cols]
data = {} data = {}
for k in cols: for k in cols:
if isinstance(rows, np.ndarray):
val = [self._columns[k][i] for i in rows]
else:
val = self._columns[k][rows] val = self._columns[k][rows]
if isinstance(val, BaseModel): if isinstance(val, BaseModel):
# special case where pandas will unpack a pydantic model # special case where pandas will unpack a pydantic model
# into {n_fields} rows, rather than keeping it in a dict # into {n_fields} rows, rather than keeping it in a dict
@ -506,7 +537,7 @@ class ElementIdentifiers(Data):
) )
class DynamicTableRegion(VectorData): class DynamicTableRegion(DynamicTableRegionMixin, VectorData):
""" """
DynamicTableRegion provides a link from one table to an index or region of another. The `table` attribute is a link to another `DynamicTable`, indicating which table is referenced, and the data is int(s) indicating the row(s) (0-indexed) of the target array. `DynamicTableRegion`s can be used to associate rows with repeated meta-data without data duplication. They can also be used to create hierarchical relationships between multiple `DynamicTable`s. `DynamicTableRegion` objects may be paired with a `VectorIndex` object to create ragged references, so a single cell of a `DynamicTable` can reference many rows of another `DynamicTable`. DynamicTableRegion provides a link from one table to an index or region of another. The `table` attribute is a link to another `DynamicTable`, indicating which table is referenced, and the data is int(s) indicating the row(s) (0-indexed) of the target array. `DynamicTableRegion`s can be used to associate rows with repeated meta-data without data duplication. They can also be used to create hierarchical relationships between multiple `DynamicTable`s. `DynamicTableRegion` objects may be paired with a `VectorIndex` object to create ragged references, so a single cell of a `DynamicTable` can reference many rows of another `DynamicTable`.
""" """

View file

@ -6,7 +6,7 @@ import re
import sys import sys
from ...hdmf_common.v1_5_0.hdmf_common_base import Data, Container from ...hdmf_common.v1_5_0.hdmf_common_base import Data, Container
from pandas import DataFrame, Series from pandas import DataFrame, Series
from typing import Any, ClassVar, List, Literal, Dict, Optional, Union, overload, Tuple from typing import Any, ClassVar, List, Literal, Dict, Optional, Union, Iterable, Tuple, overload
from pydantic import ( from pydantic import (
BaseModel, BaseModel,
ConfigDict, ConfigDict,
@ -129,20 +129,20 @@ class VectorIndexMixin(BaseModel):
""" """
Mimicking :func:`hdmf.common.table.VectorIndex.__getitem_helper` Mimicking :func:`hdmf.common.table.VectorIndex.__getitem_helper`
""" """
start = 0 if arg == 0 else self.value[arg - 1] start = 0 if arg == 0 else self.value[arg - 1]
end = self.value[arg] end = self.value[arg]
return self.target.value[slice(start, end)] return self.target.value[slice(start, end)]
def __getitem__(self, item: Union[int, slice]) -> Any: def __getitem__(self, item: Union[int, slice, Iterable]) -> Any:
if self.target is None: if self.target is None:
return self.value[item] return self.value[item]
elif isinstance(self.target, VectorData): else:
if isinstance(item, int): if isinstance(item, int):
return self._getitem_helper(item) return self._getitem_helper(item)
else: elif isinstance(item, (slice, Iterable)):
idx = range(*item.indices(len(self.value))) if isinstance(item, slice):
return [self._getitem_helper(i) for i in idx] item = range(*item.indices(len(self.value)))
return [self._getitem_helper(i) for i in item]
else: else:
raise AttributeError(f"Could not index with {item}") raise AttributeError(f"Could not index with {item}")
@ -177,13 +177,40 @@ class DynamicTableRegionMixin(BaseModel):
Mixin to allow indexing references to regions of dynamictables Mixin to allow indexing references to regions of dynamictables
""" """
table: "DynamicTableMixin" _index: Optional["VectorIndex"] = None
def __getitem__(self, item: Union[str, int, slice, Tuple[Union[str, int, slice], ...]]) -> Any: table: "DynamicTableMixin"
return self.table[item] value: Optional[NDArray] = None
def __getitem__(self, item: Union[int, slice, Iterable]) -> Any:
"""
Use ``value`` to index the table. Works analogously to ``VectorIndex`` despite
this being a subclass of ``VectorData``
"""
if self._index:
if isinstance(item, int):
# index returns an array of indices,
# and indexing table with an array returns a list of rows
return self.table[self._index[item]]
elif isinstance(item, slice):
# index returns a list of arrays of indices,
# so we index table with an array to construct
# a list of lists of rows
return [self.table[idx] for idx in self._index[item]]
else:
raise ValueError(f"Dont know how to index with {item}, need an int or a slice")
else:
if isinstance(item, int):
return self.table[self.value[item]]
elif isinstance(item, (slice, Iterable)):
if isinstance(item, slice):
item = range(*item.indices(len(self.value)))
return [self.table[self.value[i]] for i in item]
else:
raise ValueError(f"Dont know how to index with {item}, need an int or a slice")
def __setitem__(self, key: Union[int, str, slice], value: Any) -> None: def __setitem__(self, key: Union[int, str, slice], value: Any) -> None:
self.table[key] = value self.table[self.value[key]] = value
class DynamicTableMixin(BaseModel): class DynamicTableMixin(BaseModel):
@ -259,7 +286,7 @@ class DynamicTableMixin(BaseModel):
""" """
if isinstance(item, str): if isinstance(item, str):
return self._columns[item] return self._columns[item]
if isinstance(item, (int, slice)): if isinstance(item, (int, slice, np.integer, np.ndarray)):
return DataFrame.from_dict(self._slice_range(item)) return DataFrame.from_dict(self._slice_range(item))
elif isinstance(item, tuple): elif isinstance(item, tuple):
if len(item) != 2: if len(item) != 2:
@ -270,7 +297,7 @@ class DynamicTableMixin(BaseModel):
# all other cases are tuples of (rows, cols) # all other cases are tuples of (rows, cols)
rows, cols = item rows, cols = item
if isinstance(cols, (int, slice)): if isinstance(cols, (int, slice, np.integer)):
cols = self.colnames[cols] cols = self.colnames[cols]
if isinstance(rows, int) and isinstance(cols, str): if isinstance(rows, int) and isinstance(cols, str):
@ -283,7 +310,7 @@ class DynamicTableMixin(BaseModel):
raise ValueError(f"Unsure how to get item with key {item}") raise ValueError(f"Unsure how to get item with key {item}")
def _slice_range( def _slice_range(
self, rows: Union[int, slice], cols: Optional[Union[str, List[str]]] = None self, rows: Union[int, slice, np.ndarray], cols: Optional[Union[str, List[str]]] = None
) -> Dict[str, Union[list, "NDArray", "VectorData"]]: ) -> Dict[str, Union[list, "NDArray", "VectorData"]]:
if cols is None: if cols is None:
cols = self.colnames cols = self.colnames
@ -291,7 +318,11 @@ class DynamicTableMixin(BaseModel):
cols = [cols] cols = [cols]
data = {} data = {}
for k in cols: for k in cols:
if isinstance(rows, np.ndarray):
val = [self._columns[k][i] for i in rows]
else:
val = self._columns[k][rows] val = self._columns[k][rows]
if isinstance(val, BaseModel): if isinstance(val, BaseModel):
# special case where pandas will unpack a pydantic model # special case where pandas will unpack a pydantic model
# into {n_fields} rows, rather than keeping it in a dict # into {n_fields} rows, rather than keeping it in a dict
@ -506,7 +537,7 @@ class ElementIdentifiers(Data):
) )
class DynamicTableRegion(VectorData): class DynamicTableRegion(DynamicTableRegionMixin, VectorData):
""" """
DynamicTableRegion provides a link from one table to an index or region of another. The `table` attribute is a link to another `DynamicTable`, indicating which table is referenced, and the data is int(s) indicating the row(s) (0-indexed) of the target array. `DynamicTableRegion`s can be used to associate rows with repeated meta-data without data duplication. They can also be used to create hierarchical relationships between multiple `DynamicTable`s. `DynamicTableRegion` objects may be paired with a `VectorIndex` object to create ragged references, so a single cell of a `DynamicTable` can reference many rows of another `DynamicTable`. DynamicTableRegion provides a link from one table to an index or region of another. The `table` attribute is a link to another `DynamicTable`, indicating which table is referenced, and the data is int(s) indicating the row(s) (0-indexed) of the target array. `DynamicTableRegion`s can be used to associate rows with repeated meta-data without data duplication. They can also be used to create hierarchical relationships between multiple `DynamicTable`s. `DynamicTableRegion` objects may be paired with a `VectorIndex` object to create ragged references, so a single cell of a `DynamicTable` can reference many rows of another `DynamicTable`.
""" """

View file

@ -6,7 +6,7 @@ import re
import sys import sys
from ...hdmf_common.v1_5_1.hdmf_common_base import Data, Container from ...hdmf_common.v1_5_1.hdmf_common_base import Data, Container
from pandas import DataFrame, Series from pandas import DataFrame, Series
from typing import Any, ClassVar, List, Literal, Dict, Optional, Union, overload, Tuple from typing import Any, ClassVar, List, Literal, Dict, Optional, Union, Iterable, Tuple, overload
from pydantic import ( from pydantic import (
BaseModel, BaseModel,
ConfigDict, ConfigDict,
@ -129,20 +129,20 @@ class VectorIndexMixin(BaseModel):
""" """
Mimicking :func:`hdmf.common.table.VectorIndex.__getitem_helper` Mimicking :func:`hdmf.common.table.VectorIndex.__getitem_helper`
""" """
start = 0 if arg == 0 else self.value[arg - 1] start = 0 if arg == 0 else self.value[arg - 1]
end = self.value[arg] end = self.value[arg]
return self.target.value[slice(start, end)] return self.target.value[slice(start, end)]
def __getitem__(self, item: Union[int, slice]) -> Any: def __getitem__(self, item: Union[int, slice, Iterable]) -> Any:
if self.target is None: if self.target is None:
return self.value[item] return self.value[item]
elif isinstance(self.target, VectorData): else:
if isinstance(item, int): if isinstance(item, int):
return self._getitem_helper(item) return self._getitem_helper(item)
else: elif isinstance(item, (slice, Iterable)):
idx = range(*item.indices(len(self.value))) if isinstance(item, slice):
return [self._getitem_helper(i) for i in idx] item = range(*item.indices(len(self.value)))
return [self._getitem_helper(i) for i in item]
else: else:
raise AttributeError(f"Could not index with {item}") raise AttributeError(f"Could not index with {item}")
@ -177,13 +177,40 @@ class DynamicTableRegionMixin(BaseModel):
Mixin to allow indexing references to regions of dynamictables Mixin to allow indexing references to regions of dynamictables
""" """
table: "DynamicTableMixin" _index: Optional["VectorIndex"] = None
def __getitem__(self, item: Union[str, int, slice, Tuple[Union[str, int, slice], ...]]) -> Any: table: "DynamicTableMixin"
return self.table[item] value: Optional[NDArray] = None
def __getitem__(self, item: Union[int, slice, Iterable]) -> Any:
"""
Use ``value`` to index the table. Works analogously to ``VectorIndex`` despite
this being a subclass of ``VectorData``
"""
if self._index:
if isinstance(item, int):
# index returns an array of indices,
# and indexing table with an array returns a list of rows
return self.table[self._index[item]]
elif isinstance(item, slice):
# index returns a list of arrays of indices,
# so we index table with an array to construct
# a list of lists of rows
return [self.table[idx] for idx in self._index[item]]
else:
raise ValueError(f"Dont know how to index with {item}, need an int or a slice")
else:
if isinstance(item, int):
return self.table[self.value[item]]
elif isinstance(item, (slice, Iterable)):
if isinstance(item, slice):
item = range(*item.indices(len(self.value)))
return [self.table[self.value[i]] for i in item]
else:
raise ValueError(f"Dont know how to index with {item}, need an int or a slice")
def __setitem__(self, key: Union[int, str, slice], value: Any) -> None: def __setitem__(self, key: Union[int, str, slice], value: Any) -> None:
self.table[key] = value self.table[self.value[key]] = value
class DynamicTableMixin(BaseModel): class DynamicTableMixin(BaseModel):
@ -259,7 +286,7 @@ class DynamicTableMixin(BaseModel):
""" """
if isinstance(item, str): if isinstance(item, str):
return self._columns[item] return self._columns[item]
if isinstance(item, (int, slice)): if isinstance(item, (int, slice, np.integer, np.ndarray)):
return DataFrame.from_dict(self._slice_range(item)) return DataFrame.from_dict(self._slice_range(item))
elif isinstance(item, tuple): elif isinstance(item, tuple):
if len(item) != 2: if len(item) != 2:
@ -270,7 +297,7 @@ class DynamicTableMixin(BaseModel):
# all other cases are tuples of (rows, cols) # all other cases are tuples of (rows, cols)
rows, cols = item rows, cols = item
if isinstance(cols, (int, slice)): if isinstance(cols, (int, slice, np.integer)):
cols = self.colnames[cols] cols = self.colnames[cols]
if isinstance(rows, int) and isinstance(cols, str): if isinstance(rows, int) and isinstance(cols, str):
@ -283,7 +310,7 @@ class DynamicTableMixin(BaseModel):
raise ValueError(f"Unsure how to get item with key {item}") raise ValueError(f"Unsure how to get item with key {item}")
def _slice_range( def _slice_range(
self, rows: Union[int, slice], cols: Optional[Union[str, List[str]]] = None self, rows: Union[int, slice, np.ndarray], cols: Optional[Union[str, List[str]]] = None
) -> Dict[str, Union[list, "NDArray", "VectorData"]]: ) -> Dict[str, Union[list, "NDArray", "VectorData"]]:
if cols is None: if cols is None:
cols = self.colnames cols = self.colnames
@ -291,7 +318,11 @@ class DynamicTableMixin(BaseModel):
cols = [cols] cols = [cols]
data = {} data = {}
for k in cols: for k in cols:
if isinstance(rows, np.ndarray):
val = [self._columns[k][i] for i in rows]
else:
val = self._columns[k][rows] val = self._columns[k][rows]
if isinstance(val, BaseModel): if isinstance(val, BaseModel):
# special case where pandas will unpack a pydantic model # special case where pandas will unpack a pydantic model
# into {n_fields} rows, rather than keeping it in a dict # into {n_fields} rows, rather than keeping it in a dict
@ -506,7 +537,7 @@ class ElementIdentifiers(Data):
) )
class DynamicTableRegion(VectorData): class DynamicTableRegion(DynamicTableRegionMixin, VectorData):
""" """
DynamicTableRegion provides a link from one table to an index or region of another. The `table` attribute is a link to another `DynamicTable`, indicating which table is referenced, and the data is int(s) indicating the row(s) (0-indexed) of the target array. `DynamicTableRegion`s can be used to associate rows with repeated meta-data without data duplication. They can also be used to create hierarchical relationships between multiple `DynamicTable`s. `DynamicTableRegion` objects may be paired with a `VectorIndex` object to create ragged references, so a single cell of a `DynamicTable` can reference many rows of another `DynamicTable`. DynamicTableRegion provides a link from one table to an index or region of another. The `table` attribute is a link to another `DynamicTable`, indicating which table is referenced, and the data is int(s) indicating the row(s) (0-indexed) of the target array. `DynamicTableRegion`s can be used to associate rows with repeated meta-data without data duplication. They can also be used to create hierarchical relationships between multiple `DynamicTable`s. `DynamicTableRegion` objects may be paired with a `VectorIndex` object to create ragged references, so a single cell of a `DynamicTable` can reference many rows of another `DynamicTable`.
""" """

View file

@ -6,7 +6,7 @@ import re
import sys import sys
from ...hdmf_common.v1_6_0.hdmf_common_base import Data, Container from ...hdmf_common.v1_6_0.hdmf_common_base import Data, Container
from pandas import DataFrame, Series from pandas import DataFrame, Series
from typing import Any, ClassVar, List, Literal, Dict, Optional, Union, overload, Tuple from typing import Any, ClassVar, List, Literal, Dict, Optional, Union, Iterable, Tuple, overload
from pydantic import ( from pydantic import (
BaseModel, BaseModel,
ConfigDict, ConfigDict,
@ -129,20 +129,20 @@ class VectorIndexMixin(BaseModel):
""" """
Mimicking :func:`hdmf.common.table.VectorIndex.__getitem_helper` Mimicking :func:`hdmf.common.table.VectorIndex.__getitem_helper`
""" """
start = 0 if arg == 0 else self.value[arg - 1] start = 0 if arg == 0 else self.value[arg - 1]
end = self.value[arg] end = self.value[arg]
return self.target.value[slice(start, end)] return self.target.value[slice(start, end)]
def __getitem__(self, item: Union[int, slice]) -> Any: def __getitem__(self, item: Union[int, slice, Iterable]) -> Any:
if self.target is None: if self.target is None:
return self.value[item] return self.value[item]
elif isinstance(self.target, VectorData): else:
if isinstance(item, int): if isinstance(item, int):
return self._getitem_helper(item) return self._getitem_helper(item)
else: elif isinstance(item, (slice, Iterable)):
idx = range(*item.indices(len(self.value))) if isinstance(item, slice):
return [self._getitem_helper(i) for i in idx] item = range(*item.indices(len(self.value)))
return [self._getitem_helper(i) for i in item]
else: else:
raise AttributeError(f"Could not index with {item}") raise AttributeError(f"Could not index with {item}")
@ -177,13 +177,40 @@ class DynamicTableRegionMixin(BaseModel):
Mixin to allow indexing references to regions of dynamictables Mixin to allow indexing references to regions of dynamictables
""" """
table: "DynamicTableMixin" _index: Optional["VectorIndex"] = None
def __getitem__(self, item: Union[str, int, slice, Tuple[Union[str, int, slice], ...]]) -> Any: table: "DynamicTableMixin"
return self.table[item] value: Optional[NDArray] = None
def __getitem__(self, item: Union[int, slice, Iterable]) -> Any:
"""
Use ``value`` to index the table. Works analogously to ``VectorIndex`` despite
this being a subclass of ``VectorData``
"""
if self._index:
if isinstance(item, int):
# index returns an array of indices,
# and indexing table with an array returns a list of rows
return self.table[self._index[item]]
elif isinstance(item, slice):
# index returns a list of arrays of indices,
# so we index table with an array to construct
# a list of lists of rows
return [self.table[idx] for idx in self._index[item]]
else:
raise ValueError(f"Dont know how to index with {item}, need an int or a slice")
else:
if isinstance(item, int):
return self.table[self.value[item]]
elif isinstance(item, (slice, Iterable)):
if isinstance(item, slice):
item = range(*item.indices(len(self.value)))
return [self.table[self.value[i]] for i in item]
else:
raise ValueError(f"Dont know how to index with {item}, need an int or a slice")
def __setitem__(self, key: Union[int, str, slice], value: Any) -> None: def __setitem__(self, key: Union[int, str, slice], value: Any) -> None:
self.table[key] = value self.table[self.value[key]] = value
class DynamicTableMixin(BaseModel): class DynamicTableMixin(BaseModel):
@ -259,7 +286,7 @@ class DynamicTableMixin(BaseModel):
""" """
if isinstance(item, str): if isinstance(item, str):
return self._columns[item] return self._columns[item]
if isinstance(item, (int, slice)): if isinstance(item, (int, slice, np.integer, np.ndarray)):
return DataFrame.from_dict(self._slice_range(item)) return DataFrame.from_dict(self._slice_range(item))
elif isinstance(item, tuple): elif isinstance(item, tuple):
if len(item) != 2: if len(item) != 2:
@ -270,7 +297,7 @@ class DynamicTableMixin(BaseModel):
# all other cases are tuples of (rows, cols) # all other cases are tuples of (rows, cols)
rows, cols = item rows, cols = item
if isinstance(cols, (int, slice)): if isinstance(cols, (int, slice, np.integer)):
cols = self.colnames[cols] cols = self.colnames[cols]
if isinstance(rows, int) and isinstance(cols, str): if isinstance(rows, int) and isinstance(cols, str):
@ -283,7 +310,7 @@ class DynamicTableMixin(BaseModel):
raise ValueError(f"Unsure how to get item with key {item}") raise ValueError(f"Unsure how to get item with key {item}")
def _slice_range( def _slice_range(
self, rows: Union[int, slice], cols: Optional[Union[str, List[str]]] = None self, rows: Union[int, slice, np.ndarray], cols: Optional[Union[str, List[str]]] = None
) -> Dict[str, Union[list, "NDArray", "VectorData"]]: ) -> Dict[str, Union[list, "NDArray", "VectorData"]]:
if cols is None: if cols is None:
cols = self.colnames cols = self.colnames
@ -291,7 +318,11 @@ class DynamicTableMixin(BaseModel):
cols = [cols] cols = [cols]
data = {} data = {}
for k in cols: for k in cols:
if isinstance(rows, np.ndarray):
val = [self._columns[k][i] for i in rows]
else:
val = self._columns[k][rows] val = self._columns[k][rows]
if isinstance(val, BaseModel): if isinstance(val, BaseModel):
# special case where pandas will unpack a pydantic model # special case where pandas will unpack a pydantic model
# into {n_fields} rows, rather than keeping it in a dict # into {n_fields} rows, rather than keeping it in a dict
@ -506,7 +537,7 @@ class ElementIdentifiers(Data):
) )
class DynamicTableRegion(VectorData): class DynamicTableRegion(DynamicTableRegionMixin, VectorData):
""" """
DynamicTableRegion provides a link from one table to an index or region of another. The `table` attribute is a link to another `DynamicTable`, indicating which table is referenced, and the data is int(s) indicating the row(s) (0-indexed) of the target array. `DynamicTableRegion`s can be used to associate rows with repeated meta-data without data duplication. They can also be used to create hierarchical relationships between multiple `DynamicTable`s. `DynamicTableRegion` objects may be paired with a `VectorIndex` object to create ragged references, so a single cell of a `DynamicTable` can reference many rows of another `DynamicTable`. DynamicTableRegion provides a link from one table to an index or region of another. The `table` attribute is a link to another `DynamicTable`, indicating which table is referenced, and the data is int(s) indicating the row(s) (0-indexed) of the target array. `DynamicTableRegion`s can be used to associate rows with repeated meta-data without data duplication. They can also be used to create hierarchical relationships between multiple `DynamicTable`s. `DynamicTableRegion` objects may be paired with a `VectorIndex` object to create ragged references, so a single cell of a `DynamicTable` can reference many rows of another `DynamicTable`.
""" """

View file

@ -6,7 +6,7 @@ import re
import sys import sys
from ...hdmf_common.v1_7_0.hdmf_common_base import Data, Container from ...hdmf_common.v1_7_0.hdmf_common_base import Data, Container
from pandas import DataFrame, Series from pandas import DataFrame, Series
from typing import Any, ClassVar, List, Literal, Dict, Optional, Union, overload, Tuple from typing import Any, ClassVar, List, Literal, Dict, Optional, Union, Iterable, Tuple, overload
from pydantic import ( from pydantic import (
BaseModel, BaseModel,
ConfigDict, ConfigDict,
@ -129,20 +129,20 @@ class VectorIndexMixin(BaseModel):
""" """
Mimicking :func:`hdmf.common.table.VectorIndex.__getitem_helper` Mimicking :func:`hdmf.common.table.VectorIndex.__getitem_helper`
""" """
start = 0 if arg == 0 else self.value[arg - 1] start = 0 if arg == 0 else self.value[arg - 1]
end = self.value[arg] end = self.value[arg]
return self.target.value[slice(start, end)] return self.target.value[slice(start, end)]
def __getitem__(self, item: Union[int, slice]) -> Any: def __getitem__(self, item: Union[int, slice, Iterable]) -> Any:
if self.target is None: if self.target is None:
return self.value[item] return self.value[item]
elif isinstance(self.target, VectorData): else:
if isinstance(item, int): if isinstance(item, int):
return self._getitem_helper(item) return self._getitem_helper(item)
else: elif isinstance(item, (slice, Iterable)):
idx = range(*item.indices(len(self.value))) if isinstance(item, slice):
return [self._getitem_helper(i) for i in idx] item = range(*item.indices(len(self.value)))
return [self._getitem_helper(i) for i in item]
else: else:
raise AttributeError(f"Could not index with {item}") raise AttributeError(f"Could not index with {item}")
@ -177,13 +177,40 @@ class DynamicTableRegionMixin(BaseModel):
Mixin to allow indexing references to regions of dynamictables Mixin to allow indexing references to regions of dynamictables
""" """
table: "DynamicTableMixin" _index: Optional["VectorIndex"] = None
def __getitem__(self, item: Union[str, int, slice, Tuple[Union[str, int, slice], ...]]) -> Any: table: "DynamicTableMixin"
return self.table[item] value: Optional[NDArray] = None
def __getitem__(self, item: Union[int, slice, Iterable]) -> Any:
"""
Use ``value`` to index the table. Works analogously to ``VectorIndex`` despite
this being a subclass of ``VectorData``
"""
if self._index:
if isinstance(item, int):
# index returns an array of indices,
# and indexing table with an array returns a list of rows
return self.table[self._index[item]]
elif isinstance(item, slice):
# index returns a list of arrays of indices,
# so we index table with an array to construct
# a list of lists of rows
return [self.table[idx] for idx in self._index[item]]
else:
raise ValueError(f"Dont know how to index with {item}, need an int or a slice")
else:
if isinstance(item, int):
return self.table[self.value[item]]
elif isinstance(item, (slice, Iterable)):
if isinstance(item, slice):
item = range(*item.indices(len(self.value)))
return [self.table[self.value[i]] for i in item]
else:
raise ValueError(f"Dont know how to index with {item}, need an int or a slice")
def __setitem__(self, key: Union[int, str, slice], value: Any) -> None: def __setitem__(self, key: Union[int, str, slice], value: Any) -> None:
self.table[key] = value self.table[self.value[key]] = value
class DynamicTableMixin(BaseModel): class DynamicTableMixin(BaseModel):
@ -259,7 +286,7 @@ class DynamicTableMixin(BaseModel):
""" """
if isinstance(item, str): if isinstance(item, str):
return self._columns[item] return self._columns[item]
if isinstance(item, (int, slice)): if isinstance(item, (int, slice, np.integer, np.ndarray)):
return DataFrame.from_dict(self._slice_range(item)) return DataFrame.from_dict(self._slice_range(item))
elif isinstance(item, tuple): elif isinstance(item, tuple):
if len(item) != 2: if len(item) != 2:
@ -270,7 +297,7 @@ class DynamicTableMixin(BaseModel):
# all other cases are tuples of (rows, cols) # all other cases are tuples of (rows, cols)
rows, cols = item rows, cols = item
if isinstance(cols, (int, slice)): if isinstance(cols, (int, slice, np.integer)):
cols = self.colnames[cols] cols = self.colnames[cols]
if isinstance(rows, int) and isinstance(cols, str): if isinstance(rows, int) and isinstance(cols, str):
@ -283,7 +310,7 @@ class DynamicTableMixin(BaseModel):
raise ValueError(f"Unsure how to get item with key {item}") raise ValueError(f"Unsure how to get item with key {item}")
def _slice_range( def _slice_range(
self, rows: Union[int, slice], cols: Optional[Union[str, List[str]]] = None self, rows: Union[int, slice, np.ndarray], cols: Optional[Union[str, List[str]]] = None
) -> Dict[str, Union[list, "NDArray", "VectorData"]]: ) -> Dict[str, Union[list, "NDArray", "VectorData"]]:
if cols is None: if cols is None:
cols = self.colnames cols = self.colnames
@ -291,7 +318,11 @@ class DynamicTableMixin(BaseModel):
cols = [cols] cols = [cols]
data = {} data = {}
for k in cols: for k in cols:
if isinstance(rows, np.ndarray):
val = [self._columns[k][i] for i in rows]
else:
val = self._columns[k][rows] val = self._columns[k][rows]
if isinstance(val, BaseModel): if isinstance(val, BaseModel):
# special case where pandas will unpack a pydantic model # special case where pandas will unpack a pydantic model
# into {n_fields} rows, rather than keeping it in a dict # into {n_fields} rows, rather than keeping it in a dict
@ -506,7 +537,7 @@ class ElementIdentifiers(Data):
) )
class DynamicTableRegion(VectorData): class DynamicTableRegion(DynamicTableRegionMixin, VectorData):
""" """
DynamicTableRegion provides a link from one table to an index or region of another. The `table` attribute is a link to another `DynamicTable`, indicating which table is referenced, and the data is int(s) indicating the row(s) (0-indexed) of the target array. `DynamicTableRegion`s can be used to associate rows with repeated meta-data without data duplication. They can also be used to create hierarchical relationships between multiple `DynamicTable`s. `DynamicTableRegion` objects may be paired with a `VectorIndex` object to create ragged references, so a single cell of a `DynamicTable` can reference many rows of another `DynamicTable`. DynamicTableRegion provides a link from one table to an index or region of another. The `table` attribute is a link to another `DynamicTable`, indicating which table is referenced, and the data is int(s) indicating the row(s) (0-indexed) of the target array. `DynamicTableRegion`s can be used to associate rows with repeated meta-data without data duplication. They can also be used to create hierarchical relationships between multiple `DynamicTable`s. `DynamicTableRegion` objects may be paired with a `VectorIndex` object to create ragged references, so a single cell of a `DynamicTable` can reference many rows of another `DynamicTable`.
""" """

View file

@ -6,7 +6,7 @@ import re
import sys import sys
from ...hdmf_common.v1_8_0.hdmf_common_base import Data, Container from ...hdmf_common.v1_8_0.hdmf_common_base import Data, Container
from pandas import DataFrame, Series from pandas import DataFrame, Series
from typing import Any, ClassVar, List, Literal, Dict, Optional, Union, overload, Tuple from typing import Any, ClassVar, List, Literal, Dict, Optional, Union, Iterable, Tuple, overload
from pydantic import ( from pydantic import (
BaseModel, BaseModel,
ConfigDict, ConfigDict,
@ -129,20 +129,20 @@ class VectorIndexMixin(BaseModel):
""" """
Mimicking :func:`hdmf.common.table.VectorIndex.__getitem_helper` Mimicking :func:`hdmf.common.table.VectorIndex.__getitem_helper`
""" """
start = 0 if arg == 0 else self.value[arg - 1] start = 0 if arg == 0 else self.value[arg - 1]
end = self.value[arg] end = self.value[arg]
return self.target.value[slice(start, end)] return self.target.value[slice(start, end)]
def __getitem__(self, item: Union[int, slice]) -> Any: def __getitem__(self, item: Union[int, slice, Iterable]) -> Any:
if self.target is None: if self.target is None:
return self.value[item] return self.value[item]
elif isinstance(self.target, VectorData): else:
if isinstance(item, int): if isinstance(item, int):
return self._getitem_helper(item) return self._getitem_helper(item)
else: elif isinstance(item, (slice, Iterable)):
idx = range(*item.indices(len(self.value))) if isinstance(item, slice):
return [self._getitem_helper(i) for i in idx] item = range(*item.indices(len(self.value)))
return [self._getitem_helper(i) for i in item]
else: else:
raise AttributeError(f"Could not index with {item}") raise AttributeError(f"Could not index with {item}")
@ -177,13 +177,40 @@ class DynamicTableRegionMixin(BaseModel):
Mixin to allow indexing references to regions of dynamictables Mixin to allow indexing references to regions of dynamictables
""" """
table: "DynamicTableMixin" _index: Optional["VectorIndex"] = None
def __getitem__(self, item: Union[str, int, slice, Tuple[Union[str, int, slice], ...]]) -> Any: table: "DynamicTableMixin"
return self.table[item] value: Optional[NDArray] = None
def __getitem__(self, item: Union[int, slice, Iterable]) -> Any:
"""
Use ``value`` to index the table. Works analogously to ``VectorIndex`` despite
this being a subclass of ``VectorData``
"""
if self._index:
if isinstance(item, int):
# index returns an array of indices,
# and indexing table with an array returns a list of rows
return self.table[self._index[item]]
elif isinstance(item, slice):
# index returns a list of arrays of indices,
# so we index table with an array to construct
# a list of lists of rows
return [self.table[idx] for idx in self._index[item]]
else:
raise ValueError(f"Dont know how to index with {item}, need an int or a slice")
else:
if isinstance(item, int):
return self.table[self.value[item]]
elif isinstance(item, (slice, Iterable)):
if isinstance(item, slice):
item = range(*item.indices(len(self.value)))
return [self.table[self.value[i]] for i in item]
else:
raise ValueError(f"Dont know how to index with {item}, need an int or a slice")
def __setitem__(self, key: Union[int, str, slice], value: Any) -> None: def __setitem__(self, key: Union[int, str, slice], value: Any) -> None:
self.table[key] = value self.table[self.value[key]] = value
class DynamicTableMixin(BaseModel): class DynamicTableMixin(BaseModel):
@ -259,7 +286,7 @@ class DynamicTableMixin(BaseModel):
""" """
if isinstance(item, str): if isinstance(item, str):
return self._columns[item] return self._columns[item]
if isinstance(item, (int, slice)): if isinstance(item, (int, slice, np.integer, np.ndarray)):
return DataFrame.from_dict(self._slice_range(item)) return DataFrame.from_dict(self._slice_range(item))
elif isinstance(item, tuple): elif isinstance(item, tuple):
if len(item) != 2: if len(item) != 2:
@ -270,7 +297,7 @@ class DynamicTableMixin(BaseModel):
# all other cases are tuples of (rows, cols) # all other cases are tuples of (rows, cols)
rows, cols = item rows, cols = item
if isinstance(cols, (int, slice)): if isinstance(cols, (int, slice, np.integer)):
cols = self.colnames[cols] cols = self.colnames[cols]
if isinstance(rows, int) and isinstance(cols, str): if isinstance(rows, int) and isinstance(cols, str):
@ -283,7 +310,7 @@ class DynamicTableMixin(BaseModel):
raise ValueError(f"Unsure how to get item with key {item}") raise ValueError(f"Unsure how to get item with key {item}")
def _slice_range( def _slice_range(
self, rows: Union[int, slice], cols: Optional[Union[str, List[str]]] = None self, rows: Union[int, slice, np.ndarray], cols: Optional[Union[str, List[str]]] = None
) -> Dict[str, Union[list, "NDArray", "VectorData"]]: ) -> Dict[str, Union[list, "NDArray", "VectorData"]]:
if cols is None: if cols is None:
cols = self.colnames cols = self.colnames
@ -291,7 +318,11 @@ class DynamicTableMixin(BaseModel):
cols = [cols] cols = [cols]
data = {} data = {}
for k in cols: for k in cols:
if isinstance(rows, np.ndarray):
val = [self._columns[k][i] for i in rows]
else:
val = self._columns[k][rows] val = self._columns[k][rows]
if isinstance(val, BaseModel): if isinstance(val, BaseModel):
# special case where pandas will unpack a pydantic model # special case where pandas will unpack a pydantic model
# into {n_fields} rows, rather than keeping it in a dict # into {n_fields} rows, rather than keeping it in a dict
@ -506,7 +537,7 @@ class ElementIdentifiers(Data):
) )
class DynamicTableRegion(VectorData): class DynamicTableRegion(DynamicTableRegionMixin, VectorData):
""" """
DynamicTableRegion provides a link from one table to an index or region of another. The `table` attribute is a link to another `DynamicTable`, indicating which table is referenced, and the data is int(s) indicating the row(s) (0-indexed) of the target array. `DynamicTableRegion`s can be used to associate rows with repeated meta-data without data duplication. They can also be used to create hierarchical relationships between multiple `DynamicTable`s. `DynamicTableRegion` objects may be paired with a `VectorIndex` object to create ragged references, so a single cell of a `DynamicTable` can reference many rows of another `DynamicTable`. DynamicTableRegion provides a link from one table to an index or region of another. The `table` attribute is a link to another `DynamicTable`, indicating which table is referenced, and the data is int(s) indicating the row(s) (0-indexed) of the target array. `DynamicTableRegion`s can be used to associate rows with repeated meta-data without data duplication. They can also be used to create hierarchical relationships between multiple `DynamicTable`s. `DynamicTableRegion` objects may be paired with a `VectorIndex` object to create ragged references, so a single cell of a `DynamicTable` can reference many rows of another `DynamicTable`.
""" """

View file

@ -6,11 +6,13 @@ import pytest
# FIXME: Make this just be the output of the provider by patching into import machinery # FIXME: Make this just be the output of the provider by patching into import machinery
from nwb_linkml.models.pydantic.core.v2_7_0.namespace import ( from nwb_linkml.models.pydantic.core.v2_7_0.namespace import (
Device, Device,
DynamicTable,
DynamicTableRegion, DynamicTableRegion,
ElectricalSeries, ElectricalSeries,
ElectrodeGroup, ElectrodeGroup,
ExtracellularEphysElectrodes, ExtracellularEphysElectrodes,
Units, Units,
VectorIndex,
) )
@ -49,7 +51,10 @@ def electrical_series() -> Tuple["ElectricalSeries", "ExtracellularEphysElectrod
electrical_series = ElectricalSeries( electrical_series = ElectricalSeries(
name="my recording!", name="my recording!",
electrodes=DynamicTableRegion( electrodes=DynamicTableRegion(
table=electrodes, value=np.arange(0, n_electrodes), name="electrodes", description="hey" table=electrodes,
value=np.arange(n_electrodes - 1, -1, step=-1),
name="electrodes",
description="hey",
), ),
timestamps=timestamps, timestamps=timestamps,
data=data, data=data,
@ -57,16 +62,7 @@ def electrical_series() -> Tuple["ElectricalSeries", "ExtracellularEphysElectrod
return electrical_series, electrodes return electrical_series, electrodes
@pytest.fixture(params=[True, False]) def _ragged_array(n_units: int) -> tuple[list[np.ndarray], np.ndarray]:
def units(request) -> Tuple[Units, list[np.ndarray], np.ndarray]:
"""
Test case for units
Parameterized by extra_column because pandas likes to pivot dataframes
to long when there is only one column and it's not len() == 1
"""
n_units = 24
generator = np.random.default_rng() generator = np.random.default_rng()
spike_times = [ spike_times = [
np.full(shape=generator.integers(10, 50), fill_value=i, dtype=float) for i in range(n_units) np.full(shape=generator.integers(10, 50), fill_value=i, dtype=float) for i in range(n_units)
@ -78,6 +74,18 @@ def units(request) -> Tuple[Units, list[np.ndarray], np.ndarray]:
else: else:
spike_idx.append(len(spike_times[i]) + spike_idx[i - 1]) spike_idx.append(len(spike_times[i]) + spike_idx[i - 1])
spike_idx = np.array(spike_idx) spike_idx = np.array(spike_idx)
return spike_times, spike_idx
@pytest.fixture(params=[True, False])
def units(request) -> Tuple[Units, list[np.ndarray], np.ndarray]:
"""
Test case for units
Parameterized by extra_column because pandas likes to pivot dataframes
to long when there is only one column and it's not len() == 1
"""
spike_times, spike_idx = _ragged_array(24)
spike_times_flat = np.concatenate(spike_times) spike_times_flat = np.concatenate(spike_times)
@ -87,7 +95,7 @@ def units(request) -> Tuple[Units, list[np.ndarray], np.ndarray]:
"spike_times_index": spike_idx, "spike_times_index": spike_idx,
} }
if request.param: if request.param:
kwargs["extra_column"] = ["hey!"] * n_units kwargs["extra_column"] = ["hey!"] * 24
units = Units(**kwargs) units = Units(**kwargs)
return units, spike_times, spike_idx return units, spike_times, spike_idx
@ -142,20 +150,75 @@ def test_dynamictable_indexing(electrical_series):
assert subsection.dtypes.values.tolist() == dtypes[0:3] assert subsection.dtypes.values.tolist() == dtypes[0:3]
def test_dynamictable_region(electrical_series): def test_dynamictable_region_basic(electrical_series):
""" """
Dynamictableregion should DynamicTableRegion should be able to refer to a row or rows of another table
Args: itself as a column within a table
electrical_series:
Returns:
""" """
series, electrodes = electrical_series series, electrodes = electrical_series
row = series.electrodes[0]
# check that we correctly got the 4th row instead of the 0th row,
# since the indexed table was constructed with inverted indexes because it's a test, ya dummy.
# we will only vaguely check the basic functionality here bc
# a) the indexing behavior of the indexed objects is tested above, and
# b) every other object in the chain is strictly validated,
# so we assume if we got a right shaped df that it is the correct one.
# feel free to @ me when i am wrong about this
assert row.id == 4
assert row.shape == (1, 7)
# and we should still be preserving the model that is the contents of the cell of this row
# so this is a dataframe row with a column "group" that contains an array of ElectrodeGroup
# objects and that's as far as we are going to chase the recursion in this basic indexing test
# ElectrodeGroup is strictly validating so an instance check is all we need.
assert isinstance(row.group.values[0], ElectrodeGroup)
# getting a list of table rows is actually correct behavior here because
# this list of table rows is actually the cell of another table
rows = series.electrodes[0:3]
assert all([row.id == idx for row, idx in zip(rows, [4, 3, 2])])
def test_dynamictable_region_ragged():
"""
Dynamictables can also have indexes so that they are ragged arrays of column rows
"""
spike_times, spike_idx = _ragged_array(24)
spike_times_flat = np.concatenate(spike_times)
def test_dynamictable_ragged_arrays(units): # construct a secondary index that selects overlapping segments of the first table
value = np.array([0, 1, 2, 1, 2, 3, 2, 3, 4])
idx = np.array([3, 6, 9])
table = DynamicTable(
name="table",
description="a table what else would it be",
id=np.arange(len(spike_idx)),
timeseries=spike_times,
timeseries_index=spike_idx,
)
region = DynamicTableRegion(
name="dynamictableregion",
description="this field should be optional",
table=table,
value=value,
)
index = VectorIndex(name="index", description="hgggggggjjjj", target=region, value=idx)
region._index = index
rows = region[1]
# i guess this is right?
# the region should be a set of three rows of the table, with a ragged array column timeseries
# like...
#
# id timeseries
# 0 1 [1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, ...
# 1 2 [2.0, 2.0, 2.0, 2.0, 2.0, 2.0, 2.0, 2.0, 2.0, ...
# 2 3 [3.0, 3.0, 3.0, 3.0, 3.0, 3.0, 3.0, 3.0, 3.0, ...
assert rows.shape(3, 2)
assert all(rows.id == [1, 2, 3])
assert all([all(row[1].timeseries == i) for i, row in zip([1, 2, 3], rows.iterrows())])
def test_dynamictable_ragged(units):
""" """
Should be able to index ragged arrays using an implicit _index column Should be able to index ragged arrays using an implicit _index column

View file

@ -3,10 +3,9 @@ import sys
from pathlib import Path from pathlib import Path
from typing import Optional from typing import Optional
import numpy as np
import pytest import pytest
from numpydantic import NDArray, Shape from numpydantic import NDArray, Shape
import numpy as np
import nwb_linkml import nwb_linkml
from nwb_linkml.maps.naming import version_module_case from nwb_linkml.maps.naming import version_module_case