"""Rendering helpers for per-CS ``*_model_analysis.py`` and chapter insight notebooks. Composes with :mod:`case_studies.utils.analytics` (registry queries) to produce uncertainty-aware tables and figures. All helpers return either polars DataFrames or matplotlib Figures. Tables (return polars): - :func:`holdout_decay_table` — val IC ± CI vs holdout IC ± CI per family - :func:`selection_adjusted_leader_table` — DSR / PBO / RC / k-variant per leader Figures (return matplotlib Figure): - :func:`headline_forest_plot` — forest plot of IC ± CI sorted by point estimate - :func:`fold_heatmap_with_ci` — fold × family IC heatmap with significance shading Axes overlays (mutate caller-supplied ax): - :func:`regime_coverage_strip` — color-coded strip below fold-IC distribution Classification-aware helpers (filled at Phase C start): - :func:`classification_triple` — AUC ± CI, accuracy ± CI, IC-on-continuous ± CI - :func:`cross_task_matrix` — {regression model, classification model} × {IC, AUC} 2×2 """ from __future__ import annotations import matplotlib.pyplot as plt import numpy as np import polars as pl from matplotlib.axes import Axes from matplotlib.figure import Figure from case_studies.utils.analytics import ( PRIMARY_LABELS, SHORT_NAMES, _query, _registry_path, ) from case_studies.utils.notebook_contracts import degenerate_prediction_sql from utils.style import COLORS # --------------------------------------------------------------------------- # Tables # --------------------------------------------------------------------------- def holdout_decay_table( case_study: str, *, label: str | None = None, families: list[str] | None = None, ) -> pl.DataFrame: """Validation vs holdout IC with 95% CIs and decay for the rank-1 leader. By design (Ch16 selection workflow) each case study has at most one holdout-retrained model — the signal-stage rank-1 leader. This table reports its val→holdout decay with a row per family: families that were not selected at rank-1 show null holdout columns. Always loads fresh from ``prediction_sets`` — no pre-computed artifact. Prefers ``ic_mean_daily`` + HAC CI; falls back to legacy ``ic_mean`` where the daily-pooled backfill hasn't run (currently: all holdout splits). Returns columns: family, config_name, label, val_ic, val_ci_lo, val_ci_hi, val_ic_source, ho_ic, ho_ci_lo, ho_ci_hi, ho_ic_source, decay_pp, decay_pct """ label = label or PRIMARY_LABELS[case_study] db = _registry_path(case_study) if not db.exists(): return pl.DataFrame() family_clause = "" params: list = [label] if families: placeholders = ",".join("?" * len(families)) family_clause = f"AND t.family IN ({placeholders})" params.extend(families) # Prefer daily-pooled IC + HAC CI; fall back to legacy ic_mean where the # daily-pooled backfill hasn't run (notably: holdout splits, as of 2026-04-30). sql = f""" SELECT t.family, t.config_name, t.label, p.split, COALESCE(pm.ic_mean_daily, pm.ic_mean) AS ic, pm.ic_ci_lo, pm.ic_ci_hi, pm.ic_n_days, CASE WHEN pm.ic_mean_daily IS NOT NULL THEN 'daily_hac' ELSE 'fold_mean' END AS ic_source FROM training_runs t JOIN prediction_sets p ON t.training_hash = p.training_hash JOIN prediction_metrics pm ON p.prediction_hash = pm.prediction_hash WHERE t.label = ? AND p.split IN ('validation', 'holdout') {family_clause} AND COALESCE(pm.ic_mean_daily, pm.ic_mean) IS NOT NULL """ rows = _query(db, sql, tuple(params)) if rows.is_empty(): return pl.DataFrame() # Holdout retrains are at most one per family (the signal-stage rank-1 # leader). For those families the row's config_name and val_ic must come # from the SAME config that was retrained — not the validation IC rank-1 # config, which need not be the same model. Joining only on `family` # would mix two different configs into a single row (e.g. an ETF case # where validation IC rank-1 = nlinear and the holdout retrain target = # lstm_h64) and mis-attribute lstm_h64's holdout IC to nlinear. ho_leaders = ( rows.filter(pl.col("split") == "holdout") .sort("ic", descending=True, nulls_last=True) .group_by("family") .first() .select( "family", "config_name", pl.col("ic").alias("ho_ic"), pl.col("ic_ci_lo").alias("ho_ci_lo"), pl.col("ic_ci_hi").alias("ho_ci_hi"), pl.col("ic_source").alias("ho_ic_source"), ) ) # For families with a holdout retrain, pull val IC for the same (family, config). # Multiple validation predictions can exist for one config (e.g. DL checkpoints); # take the highest-IC one to mirror the validation rank-1 selection logic. val_rows = rows.filter(pl.col("split") == "validation") val_for_ho = ( val_rows.join( ho_leaders.select("family", "config_name"), on=["family", "config_name"], how="inner", ) .sort("ic", descending=True, nulls_last=True) .group_by(["family", "config_name"]) .first() .select( "family", "config_name", "label", pl.col("ic").alias("val_ic"), pl.col("ic_ci_lo").alias("val_ci_lo"), pl.col("ic_ci_hi").alias("val_ci_hi"), pl.col("ic_source").alias("val_ic_source"), ) ) # Families without a holdout retrain still surface their validation IC # rank-1 leader; ho_* columns will be null after the left-join below. ho_families = ho_leaders.select("family") val_no_ho = ( val_rows.join(ho_families, on="family", how="anti") .sort("ic", descending=True, nulls_last=True) .group_by("family") .first() .select( "family", "config_name", "label", pl.col("ic").alias("val_ic"), pl.col("ic_ci_lo").alias("val_ci_lo"), pl.col("ic_ci_hi").alias("val_ci_hi"), pl.col("ic_source").alias("val_ic_source"), ) ) val = pl.concat([val_for_ho, val_no_ho]) out = val.join(ho_leaders.drop("config_name"), on="family", how="left") out = out.with_columns( decay_pp=(pl.col("ho_ic") - pl.col("val_ic")), decay_pct=pl.when(pl.col("val_ic").abs() > 0) .then((pl.col("ho_ic") - pl.col("val_ic")) / pl.col("val_ic").abs() * 100.0) .otherwise(None), ) return out.sort("val_ic", descending=True, nulls_last=True) def selection_adjusted_leader_table( case_study: str, *, stage: str = "signal", label: str | None = None, ) -> pl.DataFrame: """Per-family rank-1 backtest with selection-adjusted statistics. Joins ``backtest_metrics`` with ``training_runs`` and LEFT JOINs the persisted ``cohort_metrics`` (cohort_type='family') for the leader-hash. The legacy column names (``dsr``, ``dsr_pvalue``, ``expected_max_sharpe``) carry the **effective-rank (ER) DSR** — the library maintainer's recommended default. ``dsr_mp`` and ``dsr_raw`` are surfaced alongside for sensitivity. Non-leader family rows have NULL selection-bias columns. Returns columns: family, config_name, label, sharpe, sharpe_ci95_lo, sharpe_ci95_hi, psr_pvalue, dsr, dsr_pvalue, expected_max_sharpe, dsr_mp, dsr_mp_pvalue, dsr_raw, dsr_raw_pvalue, n_trials_effective_er, n_trials_effective_mp, ras_leader, ras_pvalue, reality_check_pvalue, pbo, k_variants """ db = _registry_path(case_study) if not db.exists(): return pl.DataFrame() label_clause = "" params: list = [stage] if label: label_clause = "AND t.label = ?" params.append(label) sql = f""" SELECT t.family, t.config_name, t.label, bm.sharpe, bm.sharpe_ci95_lo, bm.sharpe_ci95_hi, bm.psr_pvalue, cm.dsr_er AS dsr, cm.dsr_er_pvalue AS dsr_pvalue, cm.expected_max_sharpe_er AS expected_max_sharpe, cm.dsr_mp, cm.dsr_mp_pvalue, cm.dsr_raw, cm.dsr_raw_pvalue, cm.n_trials_effective_er, cm.n_trials_effective_mp, cm.ras_leader, cm.ras_pvalue, cm.reality_check_pvalue, cm.pbo, cm.k_variants FROM backtest_runs b JOIN backtest_metrics bm ON b.backtest_hash = bm.backtest_hash JOIN prediction_sets p ON b.prediction_hash = p.prediction_hash JOIN training_runs t ON p.training_hash = t.training_hash LEFT JOIN cohort_metrics cm ON cm.cohort_type = 'family' AND cm.stage = b.stage AND cm.label = t.label AND cm.family = t.family AND cm.leader_hash = b.backtest_hash WHERE b.stage = ? {label_clause} AND bm.sharpe IS NOT NULL {degenerate_prediction_sql("p.prediction_hash")} """ rows = _query(db, sql, tuple(params)) if rows.is_empty(): return pl.DataFrame() # Force Float64 dtype on numeric columns that can come back as all-NULL # under the LEFT JOIN (polars infers Null dtype otherwise, which breaks # downstream ``.round()`` calls). float_cols = [ "dsr", "dsr_pvalue", "expected_max_sharpe", "dsr_mp", "dsr_mp_pvalue", "dsr_raw", "dsr_raw_pvalue", "n_trials_effective_er", "n_trials_effective_mp", "ras_leader", "ras_pvalue", "reality_check_pvalue", "pbo", ] casts = [pl.col(c).cast(pl.Float64) for c in float_cols if c in rows.columns] if casts: rows = rows.with_columns(casts) leaders = rows.sort("sharpe", descending=True, nulls_last=True).group_by("family").first() return leaders.sort("sharpe", descending=True, nulls_last=True) # --------------------------------------------------------------------------- # Figures # --------------------------------------------------------------------------- def headline_forest_plot( df: pl.DataFrame, *, ic_col: str = "ic_mean_daily", ci_lo_col: str = "ic_ci_lo", ci_hi_col: str = "ic_ci_hi", label_col: str = "config_name", family_col: str = "family", task_type_col: str | None = None, title: str = "", figsize: tuple[float, float] = (8.0, None), # type: ignore[assignment] ) -> Figure: """Forest plot of point estimates with 95% CIs, sorted by point estimate. CI bars that include zero are drawn in muted gray; non-zero-crossing CIs use the family color from :data:`utils.style.COLORS`. The zero line is drawn as a dashed reference. Parameters ---------- df : pl.DataFrame Must contain ``ic_col``, ``ci_lo_col``, ``ci_hi_col``, ``label_col``, ``family_col``. Optional ``task_type_col`` adds a task-type tag. """ required = {ic_col, ci_lo_col, ci_hi_col, label_col, family_col} missing = required - set(df.columns) if missing: raise ValueError(f"forest plot missing columns: {missing}") sorted_df = df.sort(ic_col, descending=False, nulls_last=True).drop_nulls(ic_col) n = sorted_df.height if n == 0: raise ValueError("forest plot received empty (or fully-null) data") height = figsize[1] if figsize[1] is not None else max(2.5, 0.32 * n + 1.0) fig, ax = plt.subplots(figsize=(figsize[0], height), constrained_layout=True) ic = sorted_df[ic_col].to_numpy() lo = sorted_df[ci_lo_col].to_numpy() hi = sorted_df[ci_hi_col].to_numpy() families = sorted_df[family_col].to_list() labels = sorted_df[label_col].to_list() if task_type_col and task_type_col in sorted_df.columns: task_types = sorted_df[task_type_col].to_list() labels = [f"{lbl} [{tt}]" if tt else lbl for lbl, tt in zip(labels, task_types)] family_palette = { "linear": COLORS.get("blue", "C0"), "gbm": COLORS.get("orange", "C1"), "deep_learning": COLORS.get("green", "C2"), "tabular_dl": COLORS.get("purple", "C3"), "latent_factors": COLORS.get("red", "C4"), "causal": COLORS.get("brown", "C5"), "benchmark": COLORS.get("gray", "C7"), } y_positions = np.arange(n) for i, (point, lo_i, hi_i, fam) in enumerate(zip(ic, lo, hi, families)): # numpy float arrays carry NaN for nulls — np.isfinite catches both # None-cast-to-NaN and explicit NaNs; bare ``is not None`` would # always be True after ``to_numpy()`` and let NaN values reach plot. ci_valid = bool(np.isfinite(lo_i) and np.isfinite(hi_i)) crosses_zero = ci_valid and lo_i <= 0 <= hi_i color = "#999999" if (not ci_valid or crosses_zero) else family_palette.get(fam, "#444444") if ci_valid: ax.plot([lo_i, hi_i], [i, i], color=color, linewidth=2.0, alpha=0.85) if np.isfinite(point): ax.plot(point, i, marker="o", color=color, markersize=6, zorder=3) ax.axvline(0.0, color="black", linestyle="--", linewidth=0.8, alpha=0.5) ax.set_yticks(y_positions) ax.set_yticklabels(labels, fontsize=8) ax.set_xlabel("Information Coefficient (daily-pooled, 95% HAC CI)") if title: ax.set_title(title) ax.grid(True, axis="x", linestyle=":", alpha=0.3) return fig def fold_heatmap_with_ci( case_study: str, label: str | None = None, *, families: list[str] | None = None, significance_threshold: float = 0.05, title: str = "", ) -> Figure: """Heatmap of fold IC × family with cells where p-value > threshold dimmed. Pulls from ``fold_metrics`` joined to ``training_runs``. Each (family, fold) cell shows the rank-1 config IC for that (family, fold). Cells whose HAC t-statistic gives p > ``significance_threshold`` are rendered in gray. Returns ------- matplotlib.figure.Figure """ label = label or PRIMARY_LABELS[case_study] db = _registry_path(case_study) if not db.exists(): raise FileNotFoundError(f"no registry for {case_study}") family_clause = "" params: list = [label] if families: placeholders = ",".join("?" * len(families)) family_clause = f"AND t.family IN ({placeholders})" params.extend(families) sql = f""" SELECT t.family, t.config_name, fm.fold_id, fm.ic, fm.ic_std, fm.n_entities FROM training_runs t JOIN prediction_sets p ON t.training_hash = p.training_hash JOIN fold_metrics fm ON p.prediction_hash = fm.prediction_hash WHERE t.label = ? AND p.split = 'validation' {family_clause} AND fm.ic IS NOT NULL {degenerate_prediction_sql("p.prediction_hash")} """ df = _query(db, sql, tuple(params)) if df.is_empty(): raise ValueError(f"no fold metrics for {case_study} / {label}") leaders = ( df.group_by(["family", "config_name"]) .agg(pl.col("ic").mean().alias("avg_ic")) .sort("avg_ic", descending=True, nulls_last=True) .group_by("family") .first() .select("family", "config_name") ) df = df.join(leaders, on=["family", "config_name"], how="inner") # Approx |t| = |ic| / SE(ic), with SE = ic_std / sqrt(n_entities). Folds with # missing ic_std or n_entities fall back to a non-significant cell. df = df.with_columns( t_approx=pl.when((pl.col("ic_std") > 0) & (pl.col("n_entities") > 0)) .then(pl.col("ic").abs() / (pl.col("ic_std") / pl.col("n_entities").sqrt())) .otherwise(0.0) ) pivot = df.pivot(values="ic", index="family", on="fold_id", aggregate_function="first") family_order = pivot["family"].to_list() fold_cols = [c for c in pivot.columns if c != "family"] fold_cols.sort(key=lambda c: int(c) if str(c).isdigit() else c) matrix = pivot.select(fold_cols).to_numpy() sig_pivot = df.pivot( values="t_approx", index="family", on="fold_id", aggregate_function="first" ).select(fold_cols) t_matrix = sig_pivot.to_numpy() with np.errstate(invalid="ignore"): p_matrix = 2.0 * (1.0 - _phi(np.abs(t_matrix))) significant = p_matrix <= significance_threshold fig, ax = plt.subplots( figsize=(max(6.0, 0.5 * len(fold_cols) + 2.0), 0.5 * len(family_order) + 1.5), constrained_layout=True, ) vmax = float(np.nanmax(np.abs(matrix))) if np.isfinite(matrix).any() else 0.05 cmap = plt.get_cmap("RdBu_r") for i, fam in enumerate(family_order): for j, fold in enumerate(fold_cols): value = matrix[i, j] sig = significant[i, j] if significant.shape == matrix.shape else False if value is None or np.isnan(value): continue color = cmap(0.5 + 0.5 * (value / vmax) if vmax > 0 else 0.5) if not sig: color = (0.85, 0.85, 0.85, 1.0) ax.add_patch(plt.Rectangle((j, i), 1, 1, facecolor=color, edgecolor="white")) ax.text( j + 0.5, i + 0.5, f"{value:+.2f}", ha="center", va="center", fontsize=7, color="black", ) ax.set_xlim(0, len(fold_cols)) ax.set_ylim(0, len(family_order)) ax.invert_yaxis() ax.set_xticks(np.arange(len(fold_cols)) + 0.5) ax.set_xticklabels([str(c) for c in fold_cols]) ax.set_yticks(np.arange(len(family_order)) + 0.5) ax.set_yticklabels(family_order) ax.set_xlabel("Fold") if title: ax.set_title(title) return fig def _phi(x: np.ndarray) -> np.ndarray: """Standard normal CDF (kept private; avoid scipy dependency).""" # Abramowitz & Stegun 26.2.17 erf approximation; max abs error 1.5e-7 a1, a2, a3, a4, a5, p = ( 0.254829592, -0.284496736, 1.421413741, -1.453152027, 1.061405429, 0.3275911, ) sign = np.where(x < 0, -1.0, 1.0) ax = np.abs(x) / np.sqrt(2.0) t = 1.0 / (1.0 + p * ax) y = 1.0 - (((((a5 * t + a4) * t) + a3) * t + a2) * t + a1) * t * np.exp(-ax * ax) return 0.5 * (1.0 + sign * y) # --------------------------------------------------------------------------- # Axes overlays # --------------------------------------------------------------------------- def regime_coverage_strip( ax: Axes, fold_dates: list[tuple[str, str]], regime_lookup: dict[str, str], *, palette: dict[str, str] | None = None, strip_height_frac: float = 0.06, ) -> None: """Draw a color-coded regime strip below an existing fold-IC distribution. Mutates ``ax`` in place. Adds a thin horizontal strip below the existing plot area, color-coded by which regime each fold falls into. Parameters ---------- ax : matplotlib Axes The existing fold-IC axes (e.g. boxplot or strip plot of fold ICs). fold_dates : list of (start_iso, end_iso) One entry per fold, in the same order as the x-axis. regime_lookup : dict[str, str] Maps a fold key (e.g. ``"2020-Q1"`` or fold start date) to a regime label. palette : dict[str, str], optional Maps regime labels to colors. Defaults to a categorical palette. """ n_folds = len(fold_dates) if n_folds == 0: return palette = palette or { "calm": COLORS.get("blue", "#1f77b4"), "stress": COLORS.get("red", "#d62728"), "drift": COLORS.get("orange", "#ff7f0e"), "structural_break": COLORS.get("purple", "#9467bd"), } y0, y1 = ax.get_ylim() span = y1 - y0 strip_top = y0 strip_bot = y0 - strip_height_frac * span for i, (start, end) in enumerate(fold_dates): regime = regime_lookup.get(start, regime_lookup.get(end, "calm")) color = palette.get(regime, "#cccccc") ax.add_patch( plt.Rectangle( (i + 0.5, strip_bot), 1.0, strip_top - strip_bot, facecolor=color, edgecolor="white", linewidth=0.5, clip_on=False, ) ) ax.set_ylim(strip_bot - 0.02 * span, y1) # --------------------------------------------------------------------------- # Classification-aware (Phase C) # --------------------------------------------------------------------------- def classification_triple( case_study: str, classification_label: str, regression_label: str, *, families: list[str] | None = None, n_boot: int = 2000, block_length: int | None = None, ) -> pl.DataFrame: """AUC ± CI, accuracy ± CI, and IC-on-continuous ± CI per family rank-1 config. Loads the rank-1 prediction set per family for ``classification_label``, pulls AUC and accuracy from ``prediction_metrics``, and computes the IC-vs- continuous-return on the matched continuous label. Returns columns: family, config_name, classification_label, regression_label, auc, auc_ci_lo, auc_ci_hi, accuracy, accuracy_ci_lo, accuracy_ci_hi, ic_continuous, ic_ci_lo, ic_ci_hi Status: stub — block-bootstrap CI for AUC/accuracy not yet wired. Implemented at the start of Phase C (us_firm / sp500_eo / crypto). """ raise NotImplementedError( "classification_triple is a Phase-C helper; implementation is queued for the " "us_firm / sp500_eo / crypto_perps walkthrough." ) def cross_task_matrix( case_study: str, regression_label: str, classification_label: str, *, families: list[str] | None = None, ) -> pl.DataFrame: """{regression model, classification model} × {IC vs continuous, AUC vs binary}. Empirical curiosity table — does the regression model accidentally classify well? Does the classification model's continuous score correlate with the continuous return? Coverage caveat: only feasible for families that trained on both label types. Confirmed coverage: ``linear`` and ``gbm``. ``latent_factors`` has 1 classification run for ``us_firm_characteristics`` only. ``deep_learning`` has zero classification runs across all CSs. Returns columns: family, config_name, ic_from_regression_model, ic_reg_ci_lo, ic_reg_ci_hi, ic_from_classification_model, ic_cls_ci_lo, ic_cls_ci_hi, auc_from_regression_model, auc_reg_ci_lo, auc_reg_ci_hi, auc_from_classification_model, auc_cls_ci_lo, auc_cls_ci_hi Status: stub — implementation queued for Phase C. """ raise NotImplementedError( "cross_task_matrix is a Phase-C helper; implementation is queued for the " "us_firm / sp500_eo / crypto_perps walkthrough." ) # --------------------------------------------------------------------------- # Conformal coverage diagnostic (spine v2 §7) # --------------------------------------------------------------------------- def conformal_coverage_diagnostic( case_study: str, label: str | None = None, *, levels: tuple[float, ...] = (0.80, 0.90, 0.95), families: list[str] | None = None, ) -> pl.DataFrame: """Per-family inductive split-conformal coverage at nominal levels. For each family's rank-1 validation config (by ``ic_mean_daily``), loads OOF predictions and uses **fold 0** as a calibration set to derive a symmetric absolute-residual quantile, then measures empirical coverage on the remaining folds at each nominal level. Interval width is reported as a fraction of the actuals' standard deviation, so families with different return scales are comparable. Returns columns: family, config_name, nominal_level, empirical_coverage, mean_interval_width_frac_std, n_test """ label = label or PRIMARY_LABELS[case_study] db = _registry_path(case_study) if not db.exists(): return pl.DataFrame() family_clause = "" params: list = [label] if families: placeholders = ",".join("?" * len(families)) family_clause = f"AND t.family IN ({placeholders})" params.extend(families) sql = f""" SELECT t.family, t.config_name, p.prediction_hash, pm.ic_mean_daily FROM training_runs t JOIN prediction_sets p ON t.training_hash = p.training_hash JOIN prediction_metrics pm ON p.prediction_hash = pm.prediction_hash WHERE t.label = ? AND p.split = 'validation' AND pm.ic_mean_daily IS NOT NULL {family_clause} """ rows = _query(db, sql, tuple(params)) if rows.is_empty(): return pl.DataFrame() leaders = ( rows.sort("ic_mean_daily", descending=True, nulls_last=True).group_by("family").first() ) pred_dir = db.parent / "predictions" out_rows: list[dict] = [] for fam, cfg, p_hash in zip( leaders["family"].to_list(), leaders["config_name"].to_list(), leaders["prediction_hash"].to_list(), ): pq = pred_dir / p_hash / "predictions.parquet" if not pq.exists(): continue df = pl.read_parquet(pq) ren = {} if "actual" in df.columns and "y_true" not in df.columns: ren["actual"] = "y_true" if "prediction" in df.columns and "y_score" not in df.columns: ren["prediction"] = "y_score" if "fold" in df.columns and "fold_id" not in df.columns: ren["fold"] = "fold_id" if ren: df = df.rename(ren) if "y_true" not in df.columns or "y_score" not in df.columns: continue df = df.drop_nulls(["y_true", "y_score"]) if df.height == 0 or "fold_id" not in df.columns: continue df = df.with_columns((pl.col("y_true") - pl.col("y_score")).abs().alias("abs_resid")) scale = float(df["y_true"].std() or 0.0) if not np.isfinite(scale) or scale == 0: continue fold_ids = sorted(df["fold_id"].unique().to_list()) if len(fold_ids) < 2: continue cal = df.filter(pl.col("fold_id") == fold_ids[0]) tst = df.filter(pl.col("fold_id").is_in(fold_ids[1:])) if cal.height < 30 or tst.height < 30: continue cal_res = cal["abs_resid"].to_numpy() tst_res = tst["abs_resid"].to_numpy() n_cal = len(cal_res) for level in levels: alpha = 1.0 - level q_level = min(np.ceil((n_cal + 1) * (1.0 - alpha)) / n_cal, 1.0) q_hat = float(np.quantile(cal_res, q_level)) cov = float((tst_res <= q_hat).mean()) width_std = (2.0 * q_hat) / scale out_rows.append( { "family": fam, "config_name": cfg, "nominal_level": float(level), "empirical_coverage": cov, "mean_interval_width_frac_std": float(width_std), "n_test": int(len(tst_res)), } ) if not out_rows: return pl.DataFrame() return pl.DataFrame(out_rows).sort(["family", "nominal_level"]) __all__ = [ "holdout_decay_table", "selection_adjusted_leader_table", "headline_forest_plot", "fold_heatmap_with_ci", "regime_coverage_strip", "classification_triple", "cross_task_matrix", "conformal_coverage_diagnostic", ]