bencher.results.holoview_results.tabular_spec ============================================= .. py:module:: bencher.results.holoview_results.tabular_spec .. autoapi-nested-parse:: Opt-in helpers for renderers that interpret stored data as a table. ``ResultDataSet`` is an arbitrary per-sample data store; it has no tabular contract. A :class:`TabularSpec` is one possible renderer for that data. It coerces supported table-like payloads to a DataFrame, validates columns, and builds a HoloViews element. Other renderers can interpret the same generic store without depending on this module. A spec is a frozen dataclass rather than a closure because a renderer declared on a result variable is part of ``BenchCfg`` and must survive the result cache and collect/render split. Classes ------- .. autoapisummary:: bencher.results.holoview_results.tabular_spec.TabularSpec Functions --------- .. autoapisummary:: bencher.results.holoview_results.tabular_spec.promote_named_index bencher.results.holoview_results.tabular_spec.to_dataframe bencher.results.holoview_results.tabular_spec.check_column bencher.results.holoview_results.tabular_spec.resolve_axes bencher.results.holoview_results.tabular_spec.resolve_columns bencher.results.holoview_results.tabular_spec.plot_frame Module Contents --------------- .. py:function:: promote_named_index(df: pandas.DataFrame) -> pandas.DataFrame Move any *named* index level into a column, so it can be plotted. ``xr.Dataset.to_pandas()`` puts the dimension coordinate in the index and only the data variables in the columns, so a ``ResultDataSet`` built the idiomatic way from xarray holds its x axis there and no chart could reach it. An ordinary frame has an unnamed ``RangeIndex``, which is row position rather than data, and is left alone — as is a level whose name is already a column, since promoting it would collide. Promoted levels land at the front of the frame, which is also what column inference wants: for a one-variable timeseries the x axis comes first. A level whose name is already a column cannot be promoted — it would collide — and cannot be left named either: pandas rejects any lookup of a label that is both an index level and a column as ambiguous, so ``sort_values`` on it would raise. The column is what a chart asked for, so the level keeps its values and loses its name. .. py:function:: to_dataframe(obj: Any, chart: str) -> pandas.DataFrame Coerce a stored payload to a DataFrame with plottable columns. .. py:function:: check_column(df: pandas.DataFrame, name: collections.abc.Hashable, role: str, chart: str) -> collections.abc.Hashable .. py:function:: resolve_axes(df: pandas.DataFrame, x: collections.abc.Hashable | None, y: collections.abc.Hashable | None, chart: str) -> tuple[collections.abc.Hashable, collections.abc.Hashable] The x and y columns, inferring unspecified ones from the numeric columns. Inference takes the first two numeric columns in frame order, which is only meaningful for a frame that holds exactly the pair being plotted — name the columns whenever the frame carries anything else (an index, a z coordinate). Labels come back exactly as the frame holds them: a column label is only a string by convention, and an xarray-derived frame can hand back integers or timestamps, which must still be usable to look the column back up. .. py:function:: resolve_columns(df: pandas.DataFrame, columns: collections.abc.Hashable | collections.abc.Sequence[collections.abc.Hashable] | None, role: str, chart: str) -> list[collections.abc.Hashable] The one-or-more columns a single-axis chart plots, inferred when omitted. A bare label is accepted as a single column, so the common case reads as ``column="dx_mm"`` rather than ``column=["dx_mm"]``. Inference takes *every* numeric column, which is the useful default for a frame holding only the measurement — name the columns whenever the frame carries anything else. .. py:function:: plot_frame(df: pandas.DataFrame, columns: collections.abc.Sequence[collections.abc.Hashable], chart: str) -> tuple[pandas.DataFrame, dict[collections.abc.Hashable, str]] The plotted columns alone, renamed so every dimension name is a string. A holoviews ``Dimension`` name has to be a string, but a column label does not, so every lookup above works with the frame's own labels and the conversion happens here, at the holoviews boundary, on a copy of the columns being plotted. Returns the frame to plot and the label -> dimension name mapping. .. py:class:: TabularSpec Base for a picklable spec that renders one sample's table as an element. Subclasses add the columns and per-chart options they need and implement :meth:`build`; the fields here are the ones every chart accepts. Build one with the chart's factory function rather than constructing it directly — the factory documents the options and is where the keyword-only boundary is. Fields are declared with defaults so a subclass can add its own, which means they land *before* the subclass's in the generated ``__init__``; the factory functions pass everything by keyword, so that ordering is never visible. .. py:attribute:: chart_name :type: ClassVar[str] :value: 'tabular chart' .. py:attribute:: title :type: str | None :value: None .. py:attribute:: xlabel :type: str | None :value: None .. py:attribute:: ylabel :type: str | None :value: None .. py:attribute:: hover :type: bool :value: True .. py:attribute:: data_aspect :type: float | None :value: None .. py:attribute:: opts :type: collections.abc.Mapping[str, Any] .. py:method:: __call__(obj: Any) -> Any Render *obj*, which is the stored object alone. This is the whole contract a ``ResultDataSet`` container has to satisfy, so a spec can be declared on a result var, attached to a single sample, or handed to a chart type, and the same code runs in all three cases. .. py:method:: build(df: pandas.DataFrame) -> Any :abstractmethod: Turn one sample's table into something panel can display. .. py:method:: element_opts(xlabel: str | None = None, ylabel: str | None = None, **chart_opts: Any) -> dict[str, Any] The options to apply to the built element. *xlabel* and *ylabel* are the defaults to use when the spec does not override them — normally the plotted column names. *chart_opts* are the per-chart options; the spec's own ``opts`` are applied last, so anything holoviews accepts can be passed through the factory without a wrapper. The shared fields win over *chart_opts* for the keys they own, so a chart must not pass ``title``, ``data_aspect`` or ``tools`` as a chart option: the first two are only overridden when the field is set, but ``tools`` is always written (see below) and a chart option would be dropped silently. Pass such a value through ``opts`` instead, which is applied last. .. py:method:: check(df: pandas.DataFrame, name: collections.abc.Hashable, role: str) -> collections.abc.Hashable Validate that *name* is a column of *df*, naming this chart on failure. .. py:method:: axes(df: pandas.DataFrame, x: collections.abc.Hashable | None, y: collections.abc.Hashable | None) The x and y columns for this chart (see :func:`resolve_axes`). .. py:method:: columns(df: pandas.DataFrame, columns: collections.abc.Hashable | collections.abc.Sequence[collections.abc.Hashable] | None, role: str) The one-or-more columns for this chart (see :func:`resolve_columns`). .. py:method:: frame(df: pandas.DataFrame, columns: collections.abc.Sequence[collections.abc.Hashable]) The plotted columns with string dimension names (see :func:`plot_frame`). .. py:method:: value_columns(df: pandas.DataFrame, vdims: collections.abc.Sequence[collections.abc.Hashable], *extra: collections.abc.Hashable | None) -> list[collections.abc.Hashable] The validated value dimensions: *vdims* plus any *extra* not already in them. *extra* is for columns a chart option implies (the one it colours by, say), which have to be carried as value dimensions to be usable by the plot but must not be listed twice. Every column returned is checked against *df*, including *extra*, so a chart that names one gets this module's available-columns message rather than a bare pandas ``KeyError`` from :meth:`frame`. ``None`` entries in *extra* are skipped, so an unset option needs no guard at the call site.