Source code for modelly.core.agentset.groupby

"""GroupBy: the result of AgentSet.groupby."""

from __future__ import annotations

import operator
from collections.abc import Callable, Hashable
from typing import TYPE_CHECKING, Any

from modelly.core.agentset._utils import _MISSING

if TYPE_CHECKING:
    from modelly.core.agentset.abstract import AbstractAgentSet


[docs] class GroupBy: """Helper class for AgentSet.groupby. Attributes: groups (dict): A dictionary with the group_name as key and group as values """ def __init__(self, groups: dict[Any, list | AbstractAgentSet]): """Initialize a GroupBy instance. Args: groups (dict): A dictionary with the group_name as key and group as values """ self.groups: dict[Any, list | AbstractAgentSet] = dict(groups)
[docs] def get_group( self, name: Hashable, default: Any = _MISSING ) -> list | AbstractAgentSet | Any: """Return the group for the given name. Args: name (Hashable): The group name to retrieve. default (Any, optional): Value to return when the group is missing. Raises: KeyError: If the group does not exist and no default is provided. """ try: return self.groups[name] except KeyError as err: if default is not _MISSING: return default raise KeyError(f"No group found with name: {name}") from err
[docs] def map(self, method: Callable | str, *args, **kwargs) -> dict[Any, Any]: """Apply the specified callable to each group and return the results. Args: method (Callable, str): The callable to apply to each group, * if ``method`` is a callable, it will be called it will be called with the group as first argument * if ``method`` is a str, it should refer to a method on the group Additional arguments and keyword arguments will be passed on to the callable. args: arguments to pass to the callable kwargs: keyword arguments to pass to the callable Returns: dict with group_name as key and the return of the method as value Notes: this method is useful for methods or functions that do return something. It will break method chaining. For that, use ``do`` instead. """ if isinstance(method, str): return { k: getattr(v, method)(*args, **kwargs) for k, v in self.groups.items() } else: return {k: method(v, *args, **kwargs) for k, v in self.groups.items()}
[docs] def do(self, method: Callable | str, *args, **kwargs) -> GroupBy: """Apply the specified callable to each group. Args: method (Callable, str): The callable to apply to each group, * if ``method`` is a callable, it will be called it will be called with the group as first argument * if ``method`` is a str, it should refer to a method on the group Additional arguments and keyword arguments will be passed on to the callable. args: arguments to pass to the callable kwargs: keyword arguments to pass to the callable Returns: the original GroupBy instance Notes: this method is useful for methods or functions that don't return anything and/or if you want to chain multiple do calls """ if isinstance(method, str): for v in self.groups.values(): getattr(v, method)(*args, **kwargs) else: for v in self.groups.values(): method(v, *args, **kwargs) return self
[docs] def count(self) -> dict[Any, int]: """Return the count of agents in each group. Returns: dict: A dictionary mapping group names to the number of agents in each group. """ return {k: len(v) for k, v in self.groups.items()}
[docs] def agg(self, attr_name: str, func: Callable) -> dict[Hashable, Any]: """Aggregate the values of a specific attribute across each group using the provided function. Args: attr_name (str): The name of the attribute to aggregate. func (Callable): The function to apply (e.g., sum, min, max, mean). Returns: dict[Hashable, Any]: A dictionary mapping group names to the result of applying the aggregation function. """ getter = operator.attrgetter(attr_name) return { group_name: func(list(map(getter, group))) for group_name, group in self.groups.items() }
def __iter__(self): # noqa: D105 return iter(self.groups.items()) def __len__(self): # noqa: D105 return len(self.groups) def __repr__(self) -> str: """Return a representation showing each group's identifier and size.""" sizes = {name: len(group) for name, group in self.groups.items()} return f"{type(self).__name__}({len(self)} groups: {sizes})"