Source code for pyphi.models.pandas

# models/pandas.py
"""Utilities for working with Pandas data structures."""

from collections.abc import Iterable
from collections.abc import Mapping
from typing import Any

import numpy as np
import pandas as pd


[docs] def try_to_dict(obj: object) -> dict[str, Any] | object: to_dict = getattr(obj, "to_dict", None) if callable(to_dict): return to_dict() return obj
[docs] class ToDictFromExplicitAttrsMixin: """Mixin class for converting a class to a dict from the `_dict_attrs` list.""" _dict_attrs: list[str]
[docs] def to_dict(self) -> dict[str, Any]: """Convert the object to a dict.""" if hasattr(self, "_dict_attrs"): return {attr: try_to_dict(getattr(self, attr)) for attr in self._dict_attrs} raise NotImplementedError("no `_dict_attrs` attribute")
[docs] class ToDictMixin: """Mixin class for converting a class to a dict from the object's ___dict___."""
[docs] def to_dict(self) -> dict[str, Any]: """Convert the object to a dict.""" return { attr: try_to_dict(value) for attr, value in self.__dict__.items() if not attr.startswith("_") }
_DISTRIBUTION_COLUMNS = ["direction", "kind", "purview", "state", "probability"]
[docs] def record_to_series(record: Mapping[str, Any], name: str | None = None) -> pd.Series: """Build a Series from an ordered field-to-value mapping.""" return pd.Series(dict(record), name=name)
[docs] def records_to_frame( rows: Iterable[Mapping[str, Any]], index: str | None = None, columns: list[str] | None = None, ) -> pd.DataFrame: """Stack record mappings into a DataFrame, optionally moving one column to the index. ``columns`` fixes the column set so an empty ``rows`` still produces the right schema.""" frame = pd.DataFrame( list(rows), columns=None if columns is None else pd.Index(columns) ) if index is not None: frame = frame.set_index(index) return frame
[docs] def state_multiindex(node_labels, indices, alphabet=None) -> pd.MultiIndex: """A MultiIndex over all states of ``indices``, level-named by label. ``alphabet`` is the per-unit cardinality sequence (k-ary); if ``None`` the units are binary. """ from pyphi.utils import all_states spec = alphabet if alphabet is not None else len(indices) states = list(all_states(spec)) names = list(node_labels.coerce_to_labels(indices)) return pd.MultiIndex.from_tuples(states, names=names)
[docs] def distribution_rows( direction, kind, purview, repertoire, node_labels=None ) -> list[dict[str, Any]]: """Tidy ``{direction, kind, purview, state, probability}`` rows for one repertoire. States are enumerated from the repertoire's per-purview-unit cardinality (k-ary aware). ``purview`` renders as labels when ``node_labels`` is given, else as integer indices. Returns ``[]`` for a ``None`` repertoire. """ from pyphi import distribution from pyphi.utils import all_states if repertoire is None: return [] repertoire = np.asarray(repertoire) alphabet = [repertoire.shape[i] for i in purview] flat = distribution.flatten(repertoire) assert flat is not None states = list(all_states(alphabet)) if alphabet else [()] if node_labels is None: purview_labels: tuple[Any, ...] = tuple(purview) else: purview_labels = tuple(node_labels.coerce_to_labels(purview)) direction_label = str(direction) return [ { "direction": direction_label, "kind": kind, "purview": purview_labels, "state": tuple(state), "probability": float(prob), } for state, prob in zip(states, flat, strict=True) ]
[docs] class ToPandasMixin: """Export a result object to a labeled Pandas structure. ``to_pandas()`` returns a ``Series`` for scalar-record types and a ``DataFrame`` with a labeled index for collections and distributions. Units render as labels. Subclasses implement ``_pandas_record()`` (record types, which inherit the Series-building ``_to_pandas``) or override ``_to_pandas()`` (collections and distributions). """
[docs] def to_pandas(self) -> pd.Series | pd.DataFrame: """Return a labeled Pandas view of this object.""" return self._to_pandas()
def _to_pandas(self) -> pd.Series | pd.DataFrame: return record_to_series(self._pandas_record(), name=type(self).__name__) def _pandas_record(self) -> Mapping[str, Any]: raise NotImplementedError(type(self).__name__)