Source code for mmm_framework.reporting.charts.roi

"""
ROI chart functions for MMM reporting.

Contains ROI forest plots and channel performance visualizations.
"""

from __future__ import annotations

import numpy as np

from ..config import ChartConfig, ReportConfig
from .base import create_plotly_div


[docs] def create_roi_forest_plot( channels: list[str], roi_mean: np.ndarray, roi_lower: np.ndarray, roi_upper: np.ndarray, config: ReportConfig, chart_config: ChartConfig | None = None, div_id: str = "roiForestPlot", reference_line: float = 1.0, ) -> str: """ Create forest plot showing channel ROI with credible intervals. Parameters ---------- channels : list[str] Channel names roi_mean : ndarray Posterior mean ROI for each channel roi_lower : ndarray Lower bound of credible interval roi_upper : ndarray Upper bound of credible interval config : ReportConfig Report configuration chart_config : ChartConfig, optional Chart-specific configuration div_id : str HTML div ID reference_line : float Value for vertical reference line (default 1.0 for break-even) Returns ------- str HTML string with embedded Plotly chart """ chart_config = chart_config or ChartConfig( height=max(250, 50 * len(channels)), x_title="ROI", ) colors = config.color_scheme channel_colors = config.channel_colors # Sort by mean ROI descending sort_idx = np.argsort(roi_mean)[::-1] channels_sorted = [channels[i] for i in sort_idx] roi_mean_sorted = roi_mean[sort_idx] roi_lower_sorted = roi_lower[sort_idx] roi_upper_sorted = roi_upper[sort_idx] # Create colors list marker_colors = [channel_colors.get(ch) for ch in channels_sorted] traces = [ # Error bars { "type": "scatter", "x": list(roi_mean_sorted), "y": channels_sorted, "error_x": { "type": "data", "symmetric": False, "array": list(roi_upper_sorted - roi_mean_sorted), "arrayminus": list(roi_mean_sorted - roi_lower_sorted), "color": colors.text_muted, "thickness": 2, "width": 8, }, "mode": "markers", "marker": {"color": marker_colors, "size": 14}, "name": "ROI", "hovertemplate": ( "%{y}<br>ROI: %{x:.2f}<br>" "CI: [%{customdata[0]:.2f}, %{customdata[1]:.2f}]<extra></extra>" ), "customdata": [ [lo, hi] for lo, hi in zip(roi_lower_sorted, roi_upper_sorted) ], }, ] layout = chart_config.to_plotly_layout(colors) layout["title"] = {"text": "Channel ROI with Uncertainty", "font": {"size": 16}} layout["yaxis"]["autorange"] = "reversed" # Highest ROI at top # Add reference line at break-even layout["shapes"] = [ { "type": "line", "x0": reference_line, "x1": reference_line, "y0": -0.5, "y1": len(channels) - 0.5, "line": {"color": colors.text_muted, "width": 1, "dash": "dash"}, } ] layout["annotations"] = [ { "x": reference_line, "y": -0.3, "xref": "x", "yref": "y", # "Break-even" only means something against a dollar cost (ROI=1); # for an efficiency metric the reference is 0 (any positive is good). "text": "Break-even" if reference_line == 1.0 else "Zero", "showarrow": False, "font": {"size": 10, "color": colors.text_muted}, } ] return create_plotly_div(traces, layout, div_id)
__all__ = [ "create_roi_forest_plot", ]