Source code for arviz_base.labels

# pylint: disable=unused-argument
"""Utilities to generate labels from xarray objects."""

from collections.abc import Hashable, Iterable, Mapping, Sequence
from typing import Protocol

__all__ = [
    "mix_labellers",
    "Labeller",
    "BaseLabeller",
    "DimCoordLabeller",
    "IdxLabeller",
    "DimIdxLabeller",
    "MapLabeller",
    "NoVarLabeller",
]


class Labeller(Protocol):
    """Placeholder for type checking ``labeller`` used in docstrings."""

    def make_label_vert(  # noqa: D102
        self,
        var_name: str | None,
        sel: Mapping[Hashable, Hashable],
        isel: Mapping[Hashable, int | Sequence[int]],
    ) -> str: ...
    def make_label_flat(  # noqa: D102
        self,
        var_name: str | None,
        sel: Mapping[Hashable, Hashable],
        isel: Mapping[Hashable, int | Sequence[int]],
    ) -> str: ...


[docs] def mix_labellers(labellers, class_name="MixtureLabeller"): """Combine Labeller classes dynamically. The Labeller class aims to split plot labeling in ArviZ into atomic tasks to maximize extensibility, and the few classes provided are designed with small deviations from the base class, in many cases only one method is modified by the child class. It is to be expected then to want to use multiple classes "at once". This functions helps combine classes dynamically. For a general overview of ArviZ label customization, including ``mix_labellers``, see the :ref:`label_guide` page. Parameters ---------- labellers : iterable of type Iterable of Labeller types to combine class_name : str, optional The name of the generated class Returns ------- type Mixture class object. **It is not initialized**, and it should be initialized before passing it to ArviZ functions. Examples -------- Combine the :class:`~arviz.labels.DimCoordLabeller` with the :class:`~arviz.labels.MapLabeller` to generate labels in the style of the ``DimCoordLabeller`` but using the mappings defined by ``MapLabeller``. Note that this works even though both modify the same methods because ``MapLabeller`` implements the mapping and then calls `super().method`. .. jupyter-execute:: from arviz_base.labels import mix_labellers, DimCoordLabeller, MapLabeller l1 = DimCoordLabeller() sel = {"dim1": "a", "dim2": "top"} print(f"Output of DimCoordLabeller alone > {l1.sel_to_str(sel, sel)}") l2 = MapLabeller(dim_map={"dim1": "$d_1$", "dim2": r"$d_2$"}) print(f"Output of MapLabeller alone > {l2.sel_to_str(sel, sel)}") l3 = mix_labellers( (MapLabeller, DimCoordLabeller) )(dim_map={"dim1": "$d_1$", "dim2": r"$d_2$"}) print(f"Output of mixture labeller > {l3.sel_to_str(sel, sel)}") We can see how the mappings are taken into account as well as the dim+coord style. However, he order in the ``labellers`` arg iterator is important! See for yourself: .. jupyter-execute:: l4 = mix_labellers( (DimCoordLabeller, MapLabeller) )(dim_map={"dim1": "$d_1$", "dim2": r"$d_2$"}) print(f"Output of inverted mixture labeller > {l4.sel_to_str(sel, sel)}") """ return type(class_name, labellers, {})
[docs] class BaseLabeller: """Base labeller class. The default labels for "theta" variable on the subset corresponding to "chain" 0 and "school" "Name" are: .. code-block:: :caption: Single line label theta[0, Name] .. code-block:: :caption: Multi-line label theta 0, Name See Also -------- :ref:`label_guide`: Tutorial page on using labellers with ArviZ """ def dim_coord_to_str(self, dim, coord_val, coord_idx): # pylint: disable=no-self-use """Format a single dimension name, its value and positional indexes as a string. Parameters ---------- dim : hashable Dimension name coord_val : hashable Coordinate label coord_idx : int or sequence of int Positions along `dim` where its coordinate is `coord_val` Returns ------- str """ return f"{coord_val}" def sel_to_str(self, sel, isel): """Format selection dictionaries as a string. Parameters ---------- sel : mapping of {hashable : hashable} isel : mapping of {hashable : int or sequence of int} Returns ------- str """ if sel: return ", ".join( [ self.dim_coord_to_str(dim, v, i) for (dim, v), (_, i) in zip(sel.items(), isel.items()) ] ) return "" def var_name_to_str(self, var_name): # pylint: disable=no-self-use """Format a variable name as a string. Parameters ---------- var_name : str or None The variable name. It should accept ``None`` Returns ------- str or None """ return var_name def var_pp_to_str(self, var_name, pp_var_name): """Format the corresponding variable names for observation and posterior predictive. Parameters ---------- var_name : str or None pp_var_name : str or None Returns ------- str or None """ var_name_str = self.var_name_to_str(var_name) pp_var_name_str = self.var_name_to_str(pp_var_name) if var_name_str is None and pp_var_name_str is None: return None if var_name_str == pp_var_name_str: return var_name_str return f"{var_name_str} / {pp_var_name_str}" def make_label_vert(self, var_name, sel, isel): """Format variable name and corresponding subset as a multiline string. Parameters ---------- var_name : str or None sel : mapping of {hashable : hashable} isel : mapping of {hashable : int or sequence of int} Returns ------- str """ var_name_str = self.var_name_to_str(var_name) sel_str = self.sel_to_str(sel, isel) if not sel_str: return "" if var_name_str is None else var_name_str if var_name_str is None: return sel_str return f"{var_name_str}\n{sel_str}" def make_label_flat(self, var_name: str, sel: dict, isel: dict): """Format variable name and corresponding subset as a single line string. Parameters ---------- var_name : str or None sel : mapping of {hashable : hashable} isel : mapping of {hashable : int or sequence of int} Returns ------- str """ var_name_str = self.var_name_to_str(var_name) sel_str = self.sel_to_str(sel, isel) if not sel_str: return "" if var_name_str is None else var_name_str if var_name_str is None: return sel_str return f"{var_name_str}[{sel_str}]" def make_pp_label(self, var_name, pp_var_name, sel, isel): """Format obs+pp variable name plus corresponding subsets as a multiline string. Parameters ---------- var_name, pp_var_name : str or None sel : mapping of {hashable : hashable} isel : mapping of {hashable : int or sequence of int} Returns ------- str """ names = self.var_pp_to_str(var_name, pp_var_name) return self.make_label_vert(names, sel, isel)
[docs] class DimCoordLabeller(BaseLabeller): """Labeller class to show both dimension and coordinate value information. The default labels for "theta" variable on the subset corresponding to "chain" 0 and "school" "Name" are: .. code-block:: :caption: Single line label theta[chain: 0, school: Name] .. code-block:: :caption: Multi-line label theta chain: 0, school: Name See Also -------- :ref:`label_guide`: Tutorial page on using labellers with ArviZ """ def dim_coord_to_str(self, dim, coord_val, coord_idx): """Format a single dimension name, its value and positional indexes as a string. Parameters ---------- dim : hashable Dimension name coord_val : hashable Coordinate label coord_idx : int or sequence of int Positions along `dim` where its coordinate is `coord_val` Returns ------- str """ return f"{dim}: {coord_val}"
[docs] class IdxLabeller(BaseLabeller): """Labeller class to show positional index information. The default labels for "theta" variable on the subset corresponding to "chain" 0 and "school" "Name" (3rd and 5th element in the school coordinate values) are: .. code-block:: :caption: Single line label theta[0, 2,4] .. code-block:: :caption: Multi-line label theta 0, 2,4 See Also -------- :ref:`label_guide`: Tutorial page on using labellers with ArviZ """ def dim_coord_to_str(self, dim, coord_val, coord_idx): """Format a single dimension name, its value and positional indexes as a string. Parameters ---------- dim : hashable Dimension name coord_val : hashable Coordinate label coord_idx : int or sequence of int Positions along `dim` where its coordinate is `coord_val` Returns ------- str """ if not isinstance(coord_idx, Iterable): return f"{coord_idx}" return f"{','.join(str(idx) for idx in coord_idx)}"
[docs] class DimIdxLabeller(BaseLabeller): """Labeller class to show both dimension and positional index information. The default labels for "theta" variable on the subset corresponding to "chain" 0 and "school" "Name" (3rd and 5th element in the school coordinate values) are: .. code-block:: :caption: Single line label theta[chain#0, school#2,4] .. code-block:: :caption: Multi-line label theta chain#0, school#2,4 See Also -------- :ref:`label_guide`: Tutorial page on using labellers with ArviZ """ def dim_coord_to_str(self, dim, coord_val, coord_idx): """Format a single dimension name, its value and positional indexes as a string. Parameters ---------- dim : hashable Dimension name coord_val : hashable Coordinate label coord_idx : int or sequence of int Positions along `dim` where its coordinate is `coord_val` Returns ------- str """ if not isinstance(coord_idx, Iterable): return f"{dim}#{coord_idx}" return f"{dim}#{','.join(str(idx) for idx in coord_idx)}"
[docs] class MapLabeller(BaseLabeller): """Labeller class to perform provided replacements to elements when converting to string. It is a subclass of :class:`BaseLabeller` so the base behaviour is the same, but we can define replacements through dictionaries when initializing the class. The default labels for "theta" variable on the subset corresponding to "chain" 0 and "school" "Name" with a replacement mapping on the variable name ``{"theta": "θ"}`` and one on the coordinate values ``{"Name": "𝑁𝑎𝑚𝑒"}`` .. code-block:: :caption: Single line label θ[0, 𝑁𝑎𝑚𝑒] .. code-block:: :caption: Multi-line label θ 0, 𝑁𝑎𝑚𝑒 See Also -------- :ref:`label_guide`: Tutorial page on using labellers with ArviZ """
[docs] def __init__(self, var_name_map=None, dim_map=None, coord_map=None): """Initialize a MapLabeller class. Parameters ---------- var_name_map, dim_map : mapping of {hashable : hashable}, optional Keys are existing names and values are their respective desired labels. coord_map : mapping of {hashable : mapping of {hashable : hashable}}, optional The keys of the first level dictionary are dimension names, the inner dictionary has existing coord names as keys and their corresponding desired label as values. """ self.var_name_map = {} if var_name_map is None else var_name_map self.dim_map = {} if dim_map is None else dim_map self.coord_map = {} if coord_map is None else coord_map
def dim_coord_to_str(self, dim, coord_val, coord_idx): """Format a single dimension name, its value and positional indexes as a string. Parameters ---------- dim : hashable Dimension name coord_val : hashable Coordinate label coord_idx : int or sequence of int Positions along `dim` where its coordinate is `coord_val` Returns ------- str """ dim_str = self.dim_map.get(dim, dim) coord_str = self.coord_map.get(dim, {}).get(coord_val, coord_val) return super().dim_coord_to_str(dim_str, coord_str, coord_idx) def var_name_to_str(self, var_name): """Format a variable name as a string. Parameters ---------- var_name : str or None The variable name. It should accept ``None`` Returns ------- str or None """ var_name_str = self.var_name_map.get(var_name, var_name) return super().var_name_to_str(var_name_str)
[docs] class NoVarLabeller(BaseLabeller): """Labeller class to exclude the variable name from the generated labels. The default labels for "theta" variable on the subset corresponding to "chain" 0 and "school" "Name" are: .. code-block:: :caption: Single line label 0, Name .. code-block:: :caption: Multi-line label 0, Name See Also -------- :ref:`label_guide`: Tutorial page on using labellers with ArviZ """ def var_name_to_str(self, var_name): """Format a variable name as a string. Parameters ---------- var_name : str or None The variable name. It should accept ``None`` Returns ------- str or None """ return None