Source code for static_frame.core.node_iter

"""
Tools for iterators in Series and Frame. These components are imported by both series.py and frame.py; these components also need to be able to return Series and Frame, and thus use deferred, function-based imports.
"""

from __future__ import annotations

from enum import Enum
from functools import partial

import numpy as np
import typing_extensions as tp
from arraykit import name_filter

from static_frame.core.container_util import group_from_container
from static_frame.core.doc_str import doc_inject

# from static_frame.core.util import TUFunc
from static_frame.core.util import (
    KEY_ITERABLE_TYPES,
    IterNodeType,
    TCallableAny,
    TDepthLevel,
    TDtypeSpecifier,
    TIndexCtorSpecifier,
    TLabel,
    TMapping,
    TMpContext,
    TName,
    TTupleCtor,
    get_concurrent_executor,
    iterable_to_array_1d,
)

if tp.TYPE_CHECKING:
    from static_frame.core.bus import Bus
    from static_frame.core.frame import Frame
    from static_frame.core.index import Index
    from static_frame.core.quilt import Quilt
    from static_frame.core.reduce import ReduceDispatch
    from static_frame.core.series import Series
    from static_frame.core.yarn import Yarn

    TNDArrayAny = np.ndarray[tp.Any, tp.Any]
    TSeriesAny = Series[tp.Any, tp.Any]
    TFrameAny = Frame[tp.Any, tp.Any, tp.Unpack[tp.Tuple[tp.Any, ...]]]
    TBusAny = Bus[tp.Any]
    TYarnAny = Yarn[tp.Any]
    TFrameOrSeries = tp.Union[TSeriesAny, TFrameAny]
    TFrameOrArray = tp.Union[Frame, TNDArrayAny]

TContainerAny = tp.TypeVar(
    'TContainerAny',
    'Frame[tp.Any, tp.Any, tp.Unpack[tp.Tuple[tp.Any, ...]]]',
    'Series[tp.Any, tp.Any]',
    'Bus[tp.Any]',
    'Quilt',
    'Yarn[tp.Any]',
)


class IterNodeApplyType(Enum):
    SERIES_VALUES = 0
    SERIES_ITEMS = 1  # only used for iter_window_*
    SERIES_ITEMS_GROUP_VALUES = 2
    SERIES_ITEMS_GROUP_LABELS = 3
    FRAME_ELEMENTS = 4
    INDEX_LABELS = 5

    @classmethod
    def is_items(cls, apply_type: 'IterNodeApplyType') -> bool:
        """Return True if the apply_constructor to be used consumes items; otherwise, the apply_constructor consumes values alone."""
        if apply_type is cls.SERIES_VALUES or apply_type is cls.INDEX_LABELS:
            return False
        return True


# NOTE: the generic type here is the type returned from calls to apply()
class IterNodeDelegate(tp.Generic[TContainerAny]):
    """
    Delegate returned from :obj:`static_frame.IterNode`, providing iteration as well as a family of apply methods.
    """

    __slots__ = (
        '_func_values',
        '_func_items',
        '_yield_type',
        '_apply_constructor',
        '_apply_type',
        '_container',
    )

    _INTERFACE: tp.Tuple[str, ...] = (
        'apply',
        'apply_iter',
        'apply_iter_items',
        'apply_pool',
    )  # should include __iter__() ?

    def __init__(
        self,
        func_values: tp.Callable[..., tp.Iterable[tp.Any]],
        func_items: tp.Callable[..., tp.Iterable[tp.Tuple[tp.Any, tp.Any]]],
        yield_type: IterNodeType,
        apply_constructor: tp.Callable[..., TContainerAny],
        apply_type: IterNodeApplyType,
        container: TFrameOrSeries,
    ) -> None:
        """
        Args:
            apply_constructor: Callable (generally a class) used to construct the object returned from apply(); must take an iterator of items.
        """
        self._func_values = func_values
        self._func_items = func_items
        self._yield_type = yield_type
        self._apply_constructor: tp.Callable[..., TContainerAny] = apply_constructor
        self._apply_type = apply_type
        self._container = container

    # ---------------------------------------------------------------------------

    def _apply_iter_items_parallel(
        self,
        func: TCallableAny,
        *,
        max_workers: tp.Optional[int] = None,
        chunksize: int = 1,
        use_threads: bool = False,
        mp_context: TMpContext = None,
    ) -> tp.Iterator[tp.Tuple[tp.Any, tp.Any]]:
        if not callable(func):  # support array, Series mapping
            func = func.__getitem__

        # use side effect list population to create keys when iterating over values
        func_keys = []

        if self._yield_type is IterNodeType.VALUES:

            def arg_gen() -> tp.Iterator[tp.Any]:
                for k, v in self._func_items():
                    func_keys.append(k)
                    yield v
        else:

            def arg_gen() -> tp.Iterator[tp.Any]:
                for k, v in self._func_items():
                    func_keys.append(k)
                    yield k, v

        pool_executor = get_concurrent_executor(
            use_threads=use_threads,
            max_workers=max_workers,
            mp_context=mp_context,
        )

        with pool_executor() as executor:
            yield from zip(func_keys, executor.map(func, arg_gen(), chunksize=chunksize))

    def _apply_iter_parallel(
        self,
        func: TCallableAny,
        *,
        max_workers: tp.Optional[int] = None,
        chunksize: int = 1,
        use_threads: bool = False,
        mp_context: TMpContext = None,
    ) -> tp.Iterator[tp.Any]:
        if not callable(func):  # support array, Series mapping
            func = func.__getitem__

        # use side effect list population to create keys when iterating over values
        arg_gen = (
            self._func_values
            if self._yield_type is IterNodeType.VALUES
            else self._func_items
        )

        pool_executor = get_concurrent_executor(
            use_threads=use_threads,
            max_workers=max_workers,
            mp_context=mp_context,
        )

        with pool_executor() as executor:
            yield from executor.map(func, arg_gen(), chunksize=chunksize)

    # ---------------------------------------------------------------------------
[docs] @doc_inject(selector='apply') def apply_iter_items( self, func: TCallableAny, /, ) -> tp.Iterator[tp.Tuple[tp.Any, tp.Any]]: """ {doc} A generator of resulting key, value pairs. Args: {func} Yields: Pairs of label, value after function application. """ # depend on yield type, we determine what the passed in function expects to if self._yield_type is IterNodeType.VALUES: yield from ((k, func(v)) for k, v in self._func_items()) else: yield from ((k, func(k, v)) for k, v in self._func_items())
[docs] @doc_inject(selector='apply') def apply_iter( self, func: TCallableAny, /, ) -> tp.Iterator[tp.Any]: """ {doc} A generator of resulting values. Args: {func} Yields: Values after function application. """ if self._yield_type is IterNodeType.VALUES: yield from (func(v) for v in self._func_values()) else: yield from (func(k, v) for k, v in self._func_items())
[docs] @doc_inject(selector='apply') def apply( self, func: TCallableAny, /, *, dtype: TDtypeSpecifier = None, name: TName = None, index_constructor: tp.Optional[TIndexCtorSpecifier] = None, columns_constructor: tp.Optional[TIndexCtorSpecifier] = None, ) -> TContainerAny: """ {doc} Returns a new container. Args: {func} {dtype} """ if not callable(func): raise RuntimeError( 'use map_fill(), map_any(), or map_all() for applying a mapping type' ) if IterNodeApplyType.is_items(self._apply_type): apply_func = self.apply_iter_items else: apply_func = self.apply_iter if self._apply_type is IterNodeApplyType.FRAME_ELEMENTS: # can always pass columns_constructor return self._apply_constructor( apply_func(func), dtype=dtype, name=name, index_constructor=index_constructor, columns_constructor=columns_constructor, ) if columns_constructor is not None: raise RuntimeError('Cannot use `columns_constructor` in this type of apply.') return self._apply_constructor( apply_func(func), dtype=dtype, name=name, index_constructor=index_constructor, )
[docs] @doc_inject(selector='apply') def apply_pool( self, func: TCallableAny, /, *, dtype: TDtypeSpecifier = None, name: TName = None, index_constructor: tp.Optional[TIndexCtorSpecifier] = None, max_workers: tp.Optional[int] = None, chunksize: int = 1, use_threads: bool = False, ) -> TContainerAny: """ {doc} Employ parallel processing with either the ProcessPoolExecutor or ThreadPoolExecutor. Args: {func} * {dtype} {name} {max_workers} {chunksize} {use_threads} """ # only use when we need pairs of values to dynamically create an Index if IterNodeApplyType.is_items(self._apply_type): apply_func = self._apply_iter_items_parallel else: apply_func = self._apply_iter_parallel return self._apply_constructor( apply_func( func, max_workers=max_workers, chunksize=chunksize, use_threads=use_threads, ), dtype=dtype, name=name, index_constructor=index_constructor, )
# --------------------------------------------------------------------------- def __iter__( self, ) -> tp.Union[tp.Iterator[tp.Any], tp.Iterator[tp.Tuple[tp.Any, tp.Any]]]: """ Return a generator based on the yield type. """ if self._yield_type is IterNodeType.VALUES: yield from self._func_values() else: yield from self._func_items() class IterNodeDelegateReducible(IterNodeDelegate[TContainerAny]): """ Delegate returned from :obj:`static_frame.IterNode`, providing iteration as well as a family of apply methods. """ __slots__ = () _INTERFACE = IterNodeDelegate._INTERFACE + ('reduce',) @property def reduce(self) -> ReduceDispatch: """For each iterated compoent, apply a function per column.""" from static_frame.core.bus import Bus from static_frame.core.reduce import ( ReduceDispatchAligned, ReduceDispatchUnaligned, ) from static_frame.core.yarn import Yarn if self._container.ndim == 1: if not isinstance(self._container, (Bus, Yarn)): raise NotImplementedError( 'No support for 1D containers.' ) # pragma: no cover return ReduceDispatchUnaligned( self._func_items(), yield_type=self._yield_type, ) # self._func_items is partialed with kwargs specific to that function if self._func_items.keywords.get('drop', False): # type: ignore key = self._func_items.keywords['key'] # type: ignore axis_labels = self._container.columns.drop.loc[key] # type: ignore else: axis_labels = self._container.columns # type: ignore # always use the items iterator, as we always want labelled values return ReduceDispatchAligned( self._func_items(), axis_labels, yield_type=self._yield_type, ) class IterNodeDelegateMapable(IterNodeDelegate[TContainerAny]): """ Delegate returned from :obj:`static_frame.IterNode`, providing iteration as well as a family of apply methods. """ __slots__ = () _INTERFACE = IterNodeDelegate._INTERFACE + ( 'map_all', 'map_all_iter', 'map_all_iter_items', 'map_any', 'map_any_iter', 'map_any_iter_items', 'map_fill', 'map_fill_iter', 'map_fill_iter_items', )
[docs] @doc_inject(selector='map_any') def map_any_iter_items( self, mapping: TMapping, /, ) -> tp.Iterator[tp.Tuple[tp.Any, tp.Any]]: """ {doc} A generator of resulting key, value pairs. Args: {mapping} """ get = mapping.get if self._yield_type is IterNodeType.VALUES: yield from ((k, get(v, v)) for k, v in self._func_items()) else: yield from ((k, get((k, v), v)) for k, v in self._func_items())
[docs] @doc_inject(selector='map_any') def map_any_iter( self, mapping: TMapping, /, ) -> tp.Iterator[tp.Any]: """ {doc} A generator of resulting values. Args: {mapping} """ get = mapping.get if self._yield_type is IterNodeType.VALUES: yield from (get(v, v) for v in self._func_values()) else: yield from (get((k, v), v) for k, v in self._func_items())
[docs] @doc_inject(selector='map_any') def map_any( self, mapping: TMapping, /, *, dtype: TDtypeSpecifier = None, name: TName = None, index_constructor: tp.Optional[TIndexCtorSpecifier] = None, ) -> TContainerAny: """ {doc} Returns a new container. Args: {mapping} {dtype} """ if IterNodeApplyType.is_items(self._apply_type): return self._apply_constructor( self.map_any_iter_items(mapping), dtype=dtype, index_constructor=index_constructor, name=name, ) return self._apply_constructor( self.map_any_iter(mapping), dtype=dtype, index_constructor=index_constructor, name=name, )
# ---------------------------------------------------------------------------
[docs] @doc_inject(selector='map_fill') def map_fill_iter_items( self, mapping: TMapping, /, *, fill_value: tp.Any = np.nan, ) -> tp.Iterator[tp.Tuple[tp.Any, tp.Any]]: """ {doc} A generator of resulting key, value pairs. Args: {mapping} {fill_value} """ get = mapping.get if self._yield_type is IterNodeType.VALUES: yield from ((k, get(v, fill_value)) for k, v in self._func_items()) else: yield from ((k, get((k, v), fill_value)) for k, v in self._func_items())
[docs] @doc_inject(selector='map_fill') def map_fill_iter( self, mapping: TMapping, /, *, fill_value: tp.Any = np.nan, ) -> tp.Iterator[tp.Any]: """ {doc} A generator of resulting values. Args: {mapping} {fill_value} """ get = mapping.get if self._yield_type is IterNodeType.VALUES: yield from (get(v, fill_value) for v in self._func_values()) else: yield from (get((k, v), fill_value) for k, v in self._func_items())
[docs] @doc_inject(selector='map_fill') def map_fill( self, mapping: TMapping, /, *, fill_value: tp.Any = np.nan, dtype: TDtypeSpecifier = None, name: TName = None, index_constructor: tp.Optional[TIndexCtorSpecifier] = None, ) -> TContainerAny: """ {doc} Returns a new container. Args: {mapping} {fill_value} {dtype} """ if IterNodeApplyType.is_items(self._apply_type): return self._apply_constructor( self.map_fill_iter_items(mapping, fill_value=fill_value), dtype=dtype, name=name, index_constructor=index_constructor, ) return self._apply_constructor( self.map_fill_iter(mapping, fill_value=fill_value), dtype=dtype, name=name, index_constructor=index_constructor, )
# ---------------------------------------------------------------------------
[docs] @doc_inject(selector='map_all') def map_all_iter_items( self, mapping: TMapping, /, ) -> tp.Iterator[tp.Tuple[tp.Any, tp.Any]]: """ {doc} A generator of resulting key, value pairs. Args: {mapping} """ # want exception to raise if key not found func = mapping.__getitem__ if self._yield_type is IterNodeType.VALUES: yield from ((k, func(v)) for k, v in self._func_items()) else: yield from ((k, func((k, v))) for k, v in self._func_items())
[docs] @doc_inject(selector='map_all') def map_all_iter( self, mapping: TMapping, /, ) -> tp.Iterator[tp.Any]: """ {doc} A generator of resulting values. Args: {mapping} """ func = mapping.__getitem__ if self._yield_type is IterNodeType.VALUES: yield from (func(v) for v in self._func_values()) else: yield from (func((k, v)) for k, v in self._func_items())
[docs] @doc_inject(selector='map_all') def map_all( self, mapping: TMapping, /, *, dtype: TDtypeSpecifier = None, name: TName = None, index_constructor: tp.Optional[TIndexCtorSpecifier] = None, ) -> TContainerAny: """ {doc} Returns a new container. Args: {mapping} {dtype} """ if IterNodeApplyType.is_items(self._apply_type): return self._apply_constructor( self.map_all_iter_items(mapping), dtype=dtype, name=name, index_constructor=index_constructor, ) return self._apply_constructor( self.map_all_iter(mapping), dtype=dtype, name=name, index_constructor=index_constructor, )
# ------------------------------------------------------------------------------- class IterNode(tp.Generic[TContainerAny]): """Interface to a type of iteration on :obj:`static_frame.Series` and :obj:`static_frame.Frame`.""" # Stores two version of a generator function: one to yield single values, another to yield items pairs. The latter is needed in all cases, as when we use apply we return a Series, and need to have recourse to an index. __slots__ = ( '_container', '_func_values', '_func_items', '_yield_type', '_apply_type', ) CLS_DELEGATE = IterNodeDelegate def __init__( self, *, container: TContainerAny, function_values: tp.Callable[..., tp.Iterable[tp.Any]], function_items: tp.Callable[..., tp.Iterable[tp.Tuple[tp.Any, tp.Any]]], yield_type: IterNodeType, apply_type: IterNodeApplyType, ) -> None: """ Args: function_values: will be partialed with arguments given with __call__. function_items: will be partialed with arguments given with __call__. """ self._container: TContainerAny = container self._func_values = function_values self._func_items = function_items self._yield_type = yield_type self._apply_type = apply_type # --------------------------------------------------------------------------- # apply constructors def to_series_from_values( self, values: tp.Iterator[tp.Any], /, *, dtype: TDtypeSpecifier, name: TName = None, index_constructor: tp.Optional[TIndexCtorSpecifier] = None, axis: int = 0, ) -> TSeriesAny: from static_frame.core.series import Series # Creating a Series that will have the same index as source container if self._container._NDIM == 2 and axis == 0: index = self._container._columns # type: ignore own_index = False else: index = self._container._index own_index = True if index_constructor is not None: index = index_constructor(index) # PERF: passing count here permits faster generator realization array, _ = iterable_to_array_1d( values, count=index.shape[0], dtype=dtype, ) return Series( array, name=name, index=index, own_index=own_index, ) def to_series_from_items( self, pairs: tp.Iterable[tp.Tuple[TLabel, tp.Any]], /, *, dtype: TDtypeSpecifier = None, name: TName = None, index_constructor: tp.Optional[TIndexCtorSpecifier] = None, axis: int = 0, ) -> TSeriesAny: from static_frame.core.series import Series # apply_constructor should be implemented to take a pairs of label, value; only used for iter_window # axis 0 iters windows labelled by the index, axis 1 iters windows labelled by the columns if self._container._NDIM == 2 and axis == 1: index_constructor = ( index_constructor if index_constructor is not None else self._container._columns.from_labels # type: ignore ) name_index = self._container._columns._name # type: ignore else: index_constructor = ( index_constructor if index_constructor is not None else self._container._index.from_labels ) name_index = self._container._index._name index_constructor_final = partial( index_constructor, name=name_index, ) # always return a Series return Series.from_items( pairs, dtype=dtype, name=name, index_constructor=index_constructor_final, ) def to_series_from_group_items( self, pairs: tp.Iterable[tp.Tuple[TLabel, tp.Any]], /, *, dtype: TDtypeSpecifier = None, name: TName = None, index_constructor: tp.Optional[TIndexCtorSpecifier] = None, name_index: TName = None, ) -> TSeriesAny: from static_frame.core.index import Index from static_frame.core.series import Series # NOTE: when used on labels, this key is given; when used on labels (indices) depth_level is given; only take the key if it is a hashable (a string or a tuple, not a slice, list, or array) index_constructor = partial( Index if index_constructor is None else index_constructor, name=name_index, ) return Series.from_items( pairs, dtype=dtype, name=name, index_constructor=index_constructor ) def to_frame_from_elements( self, items: tp.Iterable[tp.Tuple[tp.Tuple[TLabel, TLabel], tp.Any]], /, *, dtype: TDtypeSpecifier = None, name: TName = None, index_constructor: tp.Optional[TIndexCtorSpecifier] = None, columns_constructor: tp.Optional[TIndexCtorSpecifier] = None, axis: int = 0, ) -> TFrameAny: # NOTE: this is only called from `Frame` to produce a new `Frame` from static_frame.core.frame import Frame if index_constructor is not None: index = index_constructor(self._container._index) else: index = self._container._index assert isinstance(self._container, Frame) # mypy if columns_constructor is not None: columns = columns_constructor(self._container._columns) else: columns = self._container._columns return self._container.__class__.from_element_items( items, index=index, columns=columns, dtype=dtype, axis=axis, own_index=True, own_columns=True, name=name, ) def to_index_from_labels( self, values: tp.Iterator[TLabel], dtype: TDtypeSpecifier = None, name: TName = None, index_constructor: tp.Optional[TIndexCtorSpecifier] = None, ) -> TNDArrayAny: # NOTE: name argument is for common interface if index_constructor is not None: raise RuntimeError('index_constructor not supported with this interface') # PERF: passing count here permits faster generator realization shape = self._container.shape array, _ = iterable_to_array_1d(values, count=shape[0], dtype=dtype) return array # --------------------------------------------------------------------------- def _get_delegate_kwargs( self, **kwargs: object, ) -> tp.Dict[str, tp.Any]: """ In usage as an iteator, the args passed here are expected to be argument for the core iterators, i.e., axis arguments. Args: kwargs: kwarg args to be passed to both self._func_values and self._func_items """ from static_frame.core.frame import Frame # all kwargs, like ``drop```, are partialed into func_values, func_items func_values = partial(self._func_values, **kwargs) func_items = partial(self._func_items, **kwargs) axis: int = kwargs.get('axis', 0) # type: ignore apply_constructor: tp.Callable[..., tp.Union[TFrameAny, TSeriesAny]] if self._apply_type is IterNodeApplyType.SERIES_VALUES: apply_constructor = partial(self.to_series_from_values, axis=axis) elif self._apply_type is IterNodeApplyType.SERIES_ITEMS: apply_constructor = partial(self.to_series_from_items, axis=axis) elif self._apply_type is IterNodeApplyType.SERIES_ITEMS_GROUP_VALUES: try: name_index = name_filter(kwargs.get('key', None)) except TypeError: name_index = None apply_constructor = partial( self.to_series_from_group_items, name_index=name_index, ) elif self._apply_type is IterNodeApplyType.SERIES_ITEMS_GROUP_LABELS: # will always have `depth_level` in kwargs, and for Frame an axis; could attempt to get name from the index if it has a name name_index = None apply_constructor = partial( self.to_series_from_group_items, name_index=name_index, ) elif self._apply_type is IterNodeApplyType.FRAME_ELEMENTS: assert isinstance(self._container, Frame) # for typing apply_constructor = partial(self.to_frame_from_elements, axis=axis) elif self._apply_type is IterNodeApplyType.INDEX_LABELS: apply_constructor = self.to_index_from_labels # type: ignore else: raise NotImplementedError(self._apply_type) # pragma: no cover return dict( func_values=func_values, func_items=func_items, yield_type=self._yield_type, apply_constructor=tp.cast(tp.Callable[..., TContainerAny], apply_constructor), apply_type=self._apply_type, container=self._container, ) def get_delegate( self, **kwargs: object, ) -> IterNodeDelegate[TContainerAny]: return IterNodeDelegate(**self._get_delegate_kwargs(**kwargs)) def get_delegate_reducible( self, **kwargs: object, ) -> IterNodeDelegateReducible[TContainerAny]: return IterNodeDelegateReducible(**self._get_delegate_kwargs(**kwargs)) def get_delegate_mapable( self, **kwargs: object, ) -> IterNodeDelegateMapable[TContainerAny]: return IterNodeDelegateMapable(**self._get_delegate_kwargs(**kwargs)) # ------------------------------------------------------------------------------- # specialize IterNode based on arguments given to __call__ class IterNodeNoArg(IterNode[TContainerAny]): __slots__ = () CLS_DELEGATE = IterNodeDelegate def __call__( self, ) -> IterNodeDelegate[TContainerAny]: return IterNode.get_delegate(self) class IterNodeNoArgMapable(IterNode[TContainerAny]): __slots__ = () CLS_DELEGATE = IterNodeDelegateMapable def __call__( self, ) -> IterNodeDelegateMapable[TContainerAny]: return IterNode.get_delegate_mapable(self) class IterNodeNoArgReducible(IterNode[TContainerAny]): __slots__ = () CLS_DELEGATE = IterNodeDelegateReducible def __call__( self, ) -> IterNodeDelegateReducible[TContainerAny]: return IterNode.get_delegate_reducible(self) class IterNodeAxisElement(IterNode[TContainerAny]): __slots__ = () CLS_DELEGATE = IterNodeDelegateMapable def __call__(self, *, axis: int = 0) -> IterNodeDelegateMapable[TContainerAny]: return IterNode.get_delegate_mapable(self, axis=axis) class IterNodeAxis(IterNode[TContainerAny]): __slots__ = () def __call__(self, *, axis: int = 0) -> IterNodeDelegateMapable[TContainerAny]: return IterNode.get_delegate_mapable(self, axis=axis) class IterNodeConstructorAxis(IterNode[TContainerAny]): __slots__ = () CLS_DELEGATE = IterNodeDelegateMapable def __call__( self, *, axis: int = 0, constructor: tp.Optional[TTupleCtor] = None, ) -> IterNodeDelegateMapable[TContainerAny]: return IterNode.get_delegate_mapable( self, axis=axis, constructor=constructor, ) class IterNodeGroup(IterNode[TContainerAny]): """ Iterator on 1D groupings where no args are required (but axis is retained for compatibility) """ __slots__ = () def __call__(self, *, axis: int = 0) -> IterNodeDelegate[TContainerAny]: return IterNode.get_delegate(self, axis=axis) class IterNodeGroupAxis(IterNode[TContainerAny]): """ Iterator on 2D groupings where key and axis are required. """ __slots__ = () CLS_DELEGATE = IterNodeDelegateReducible def __call__( self, key: KEY_ITERABLE_TYPES, # type: ignore /, *, axis: int = 0, drop: bool = False, ) -> IterNodeDelegateReducible[TContainerAny]: return IterNode.get_delegate_reducible(self, key=key, axis=axis, drop=drop) class IterNodeGroupOther(IterNode[TContainerAny]): """ Iterator on 1D groupings where group values are provided. """ __slots__ = () CLS_DELEGATE = IterNodeDelegate def __call__( self, other: tp.Union[TNDArrayAny, Index[tp.Any], TSeriesAny, tp.Iterable[tp.Any]], /, *, fill_value: tp.Any = np.nan, axis: int = 0, ) -> IterNodeDelegate[TContainerAny]: index_ref = self._container._index if axis == 0 else self._container._columns # type: ignore group_source = group_from_container( index=index_ref, group_source=other, fill_value=fill_value, axis=axis, ) # kwargs are partialed into func_values, func_items return IterNode.get_delegate( self, axis=axis, group_source=group_source, ) class IterNodeGroupOtherReducible(IterNode[TContainerAny]): """ Iterator on 1D groupings where group values are provided. """ __slots__ = () CLS_DELEGATE = IterNodeDelegateReducible def __call__( self, other: tp.Union[TNDArrayAny, Index[tp.Any], TSeriesAny, tp.Iterable[tp.Any]], /, *, fill_value: tp.Any = np.nan, axis: int = 0, ) -> IterNodeDelegateReducible[TContainerAny]: index_ref = self._container._index if axis == 0 else self._container._columns # type: ignore group_source = group_from_container( index=index_ref, group_source=other, fill_value=fill_value, axis=axis, ) # kwargs are partialed into func_values, func_items return IterNode.get_delegate_reducible( self, axis=axis, group_source=group_source, ) class IterNodeDepthLevel(IterNode[TContainerAny]): __slots__ = () def __call__( self, depth_level: tp.Optional[TDepthLevel] = None, /, ) -> IterNodeDelegateMapable[TContainerAny]: return IterNode.get_delegate_mapable(self, depth_level=depth_level) class IterNodeDepthLevelAxis(IterNode[TContainerAny]): __slots__ = () def __call__( self, depth_level: TDepthLevel = 0, /, *, axis: int = 0 ) -> IterNodeDelegate[TContainerAny]: return IterNode.get_delegate(self, depth_level=depth_level, axis=axis) class IterNodeWindow(IterNode[TContainerAny]): __slots__ = () CLS_DELEGATE = IterNodeDelegate def __call__( self, *, size: int, axis: int = 0, step: int = 1, window_sized: bool = True, window_func: tp.Optional[TCallableAny] = None, window_valid: tp.Optional[TCallableAny] = None, label_shift: int = 0, label_missing_skips: bool = True, label_missing_raises: bool = False, start_shift: int = 0, size_increment: int = 0, ) -> IterNodeDelegate[TContainerAny]: return IterNode.get_delegate( self, axis=axis, size=size, step=step, window_sized=window_sized, window_func=window_func, window_valid=window_valid, label_shift=label_shift, label_missing_skips=label_missing_skips, label_missing_raises=label_missing_raises, start_shift=start_shift, size_increment=size_increment, ) class IterNodeWindowReducible(IterNode[TContainerAny]): __slots__ = () CLS_DELEGATE = IterNodeDelegateReducible def __call__( self, *, size: int, axis: int = 0, step: int = 1, window_sized: bool = True, window_func: tp.Optional[TCallableAny] = None, window_valid: tp.Optional[TCallableAny] = None, label_shift: int = 0, label_missing_skips: bool = True, label_missing_raises: bool = False, start_shift: int = 0, size_increment: int = 0, ) -> IterNodeDelegateReducible[TContainerAny]: return IterNode.get_delegate_reducible( self, axis=axis, size=size, step=step, window_sized=window_sized, window_func=window_func, window_valid=window_valid, label_shift=label_shift, label_missing_skips=label_missing_skips, label_missing_raises=label_missing_raises, start_shift=start_shift, size_increment=size_increment, )