Skip to content

[NO-ISSUE] feat: Adding the plot function for experiment analysis - #267

Open
gae-ric wants to merge 1 commit into
mainfrom
NO-ISSUE/plotting_function
Open

[NO-ISSUE] feat: Adding the plot function for experiment analysis#267
gae-ric wants to merge 1 commit into
mainfrom
NO-ISSUE/plotting_function

Conversation

@gae-ric

@gae-ric gae-ric commented May 21, 2026

Copy link
Copy Markdown
Collaborator

Add plot_experiment_results — scoreboard-style plotting for experiment results

Summary

Adds a minimal, dependency-light plotting utility (plot_experiment_results) for
AnalysisPlanResults. It generalizes the prior single-treatment relative-only
helper to support both relative and absolute lift, both A/B and
A/B/n designs, and stacks one card per treatment-vs-control comparison in a
single figure.

matplotlib remains an optional dependency: imported lazily inside the
function, with a clear ImportError when missing.

Motivation

Experiment readouts in this library currently stop at the AnalysisPlanResults
table. Users have to roll their own plots every time. This PR closes that gap
with a single function that:

  • accepts AnalysisPlanResults directly (no adapter layer),
  • supports the two metric framings users actually need (relative %, absolute units),
  • works for any number of treatment variants without extra configuration,
  • handles common analysis edge cases (zero control mean, NaN, missing CI/p,
    infinite lift) without crashing.

What's included

Implementation

  • cluster_experiments/plotting.py — new module exposing
    plot_experiment_results(result, metric_type="relative", title=None, ax=None).

    • Scoreboard-style layout: control circle + mean, treatment circle + mean,
      colored lift indicator (▲ green / ▼ red / ● gray), centered CI bar with
      -r% / 0% / +r% ticks.
    • Multi-variant: stacks one card per non-control variant in a single figure.
    • Lazy matplotlib.pyplot import inside the function. No global import.
    • No seaborn, plotly, or pandas plotting wrappers introduced.
    • Returns the Axes object for further customization.
  • cluster_experiments/__init__.py — re-exports plot_experiment_results
    and adds it to __all__.

Tests

  • tests/test_plotting.py — 16 pytest cases covering:
    • Single-treatment relative and absolute rendering.
    • Multi-variant (A/B/C, A/B/C/D) for both metric types.
    • Edge cases: zero control mean → +inf/-inf label + x marker; NaN values;
      missing CI bounds; missing p-values; negative-significant; positive-significant;
      non-significant.
    • Color/icon assertions for ▲ green / ▼ red / ● gray.
    • matplotlib missing raises the documented ImportError.
    • Invalid metric_type raises ValueError.
    • Empty AnalysisPlanResults raises ValueError.
    • ax= parameter is honored.
    • Backend forced to Agg; autouse fixture closes all figures between tests.

Example outputs

  • examples/plotting_outputs/generate_plots.py — reproducible script that
    writes 16 PNGs covering every scenario above.

  • examples/plotting_outputs/*.png — 16 generated examples:
    2/3/4/5-variant, relative & absolute, all-significant, none-significant,
    mixed signs, zero control mean (infinite lift), NaN values, missing CI,
    missing p-values, large effects, custom ax with stacked subplots, strict
    alpha.

    Regenerate any time with:

    uv run --extra dev python examples/plotting_outputs/generate_plots.py

Documentation

  • docs/plotting.md — full tutorial in the project's existing
    quickstart.md style: installation note, numbered sections, mental-model
    table, relative vs absolute table, multi-variant, filtering results,
    dimension slicing, scorecard layout, headless save, edge-case table,
    embedded API reference via mkdocstrings, and "Next Steps".
  • docs/api/plotting.md — API stub matching the existing
    docs/api/*.md convention (::: cluster_experiments.plotting).
  • mkdocs.yml — registers the tutorial under
    Examples → Basic Usage → Plotting Experiment Results and the API reference
    under API Reference → Experiment Analysis → Plotting.

Public API

from cluster_experiments import AnalysisPlan, plot_experiment_results

plan = AnalysisPlan.from_metrics_dict({...})
results = plan.analyze(df)

plot_experiment_results(results, metric_type="relative", title="Revenue lift")
plot_experiment_results(results, metric_type="absolute", ax=my_ax)

Signature:

plot_experiment_results(
    result,
    metric_type: str = "relative",   # "relative" | "absolute"
    title: str | None = None,
    ax: matplotlib.axes.Axes | None = None,
) -> matplotlib.axes.Axes

Design decisions

  • Scoreboard, not forest plot. Matches the existing internal aesthetic and
    reads well as a stakeholder readout. Multi-variant simply stacks cards.
  • Module-level function, no class. Per the brief: small, functional, no
    custom themes or abstractions.
  • Lazy matplotlib import. Keeps the core package dependency-light;
    raises "Plotting requires matplotlib. Install with: pip install matplotlib"
    if missing.
  • Reads AnalysisPlanResults attributes directly. No adapter, no DataFrame
    conversion required. Users filter the result themselves (a helper is shown
    in docs/plotting.md) when they want a single chart out of many rows.
  • Edge cases never crash. Zero control mean → ±inf label + x marker.
    NaN → nan label + x marker. Missing p-value → gray. Missing CI → no bar.

Backwards compatibility

Purely additive:

  • New module cluster_experiments.plotting.
  • New export plot_experiment_results in cluster_experiments.__init__.
  • New optional usage of matplotlib (already in dev/docs extras; not
    promoted to a core dependency).

No existing module, class, or function is modified.

Verification

uv run --extra dev pytest tests/test_plotting.py -q
# 16 passed

uv run --extra dev pytest tests/test_plotting.py tests/inference/test_analysis_results.py -q
# 22 passed

uv run --extra dev black --check cluster_experiments/plotting.py tests/test_plotting.py examples/plotting_outputs/generate_plots.py
# All done

uv run --extra dev ruff check cluster_experiments/plotting.py tests/test_plotting.py examples/plotting_outputs/generate_plots.py
# All checks passed

uv run --extra docs mkdocs build
# Builds; site/plotting.html and site/api/plotting.html generated

Files

File Status
cluster_experiments/plotting.py new
cluster_experiments/__init__.py modified (add export)
tests/test_plotting.py new
examples/plotting_outputs/generate_plots.py new
examples/plotting_outputs/*.png (16 files) new
docs/plotting.md new
docs/api/plotting.md new
mkdocs.yml modified (nav entries)

How to review

  1. Skim cluster_experiments/plotting.py (~340 lines, one public function +
    small helpers).
  2. Open examples/plotting_outputs/ and flip through the 16 PNGs to verify
    the visual output across edge cases.
  3. Read docs/plotting.md end-to-end — it doubles as the design walkthrough.
  4. Run the test suite locally.

Out of scope (deliberate)

  • Other plot types (heatmaps, time-series, calendars) — separate concern.
  • Adapter for non-AnalysisPlanResults objects — duck-typed via getattr
    with sensible defaults; explicit converter can come later if needed.
  • Promoting matplotlib to a core dependency — left optional per the brief.

@gae-ric
gae-ric requested a review from david26694 May 21, 2026 13:55
Comment thread docs/api/plotting.md
@@ -0,0 +1,3 @@
# `from cluster_experiments.plotting import *`

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

try a make docs-serve ti check if it works

Comment thread docs/plotting.md
`cluster-experiments`. Install it on demand:

```bash
pip install matplotlib

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's try to do something like

pip install cluster-experiments[matplotlib]

or some changes in the pyproject to make this work directly from a simple command in the installation of the library

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants