Source code for regime_shap.report

"""Assemble the regime-aware SHAP results into shareable reports.

The analysis produces a handful of tables (global importance, per-regime
rankings, the stability matrix and its band classifications, sample sizes, and
any bootstrap confidence intervals). This module turns that collection into a
self-contained HTML report as the headline output, and exposes the same tables
as a plain dictionary and as CSV files so the results stay machine-readable.
Callers pass a mapping of ``{name: table}``; the module does not assume any
particular set of tables, so it stays general.
"""

from __future__ import annotations

from collections.abc import Mapping
from pathlib import Path

import pandas as pd

Tables = Mapping[str, "pd.DataFrame | pd.Series"]

_STYLE = (
    "body{font-family:sans-serif;margin:2rem;color:#222;}"
    "h1{font-size:1.6rem;}h2{font-size:1.2rem;margin-top:1.5rem;}"
    "table{border-collapse:collapse;margin-top:0.5rem;}"
    "th,td{border:1px solid #ccc;padding:4px 10px;text-align:right;}"
    "th{background:#f4f4f4;}"
    ".notes{color:#555;}"
)


[docs] def to_dict(tables: Tables) -> dict: """Return the result tables as JSON-serialisable nested dicts. Args: tables: A mapping of table name to DataFrame or Series. Returns: A dict keyed by table name; each DataFrame becomes a ``{column: {index: value}}`` dict and each Series an ``{index: value}`` dict. """ return {name: table.to_dict() for name, table in tables.items()}
[docs] def save_csv(tables: Tables, output_dir: str | Path) -> list[Path]: """Write each table to ``output_dir/<name>.csv``. Args: tables: A mapping of table name to DataFrame or Series. output_dir: Directory to write into; created if it does not exist. Returns: The list of written file paths, in the order of ``tables``. """ directory = Path(output_dir) directory.mkdir(parents=True, exist_ok=True) paths = [] for name, table in tables.items(): path = directory / f"{name}.csv" table.to_csv(path) paths.append(path) return paths
[docs] def to_html( tables: Tables, *, title: str = "regime-shap stability report", notes: list[str] | None = None, ) -> str: """Render the tables as a single self-contained HTML report. Args: tables: A mapping of table name to DataFrame or Series, rendered in order under headings. title: The page and top heading title. notes: Optional caveats shown near the top, for example that small regimes should be read with their bootstrap confidence intervals. Returns: A complete HTML document as a string. """ parts = [ "<!doctype html>", "<html lang='en'>", "<head>", "<meta charset='utf-8'>", f"<title>{title}</title>", f"<style>{_STYLE}</style>", "</head>", "<body>", f"<h1>{title}</h1>", ] if notes: parts.append("<ul class='notes'>") parts.extend(f"<li>{note}</li>" for note in notes) parts.append("</ul>") for name, table in tables.items(): frame = table.to_frame() if isinstance(table, pd.Series) else table parts.append(f"<h2>{name}</h2>") parts.append(frame.to_html(border=0, float_format=lambda x: f"{x:.4f}")) parts.extend(["</body>", "</html>"]) return "\n".join(parts)