Visualize decisions

pip install compileml[viz]

One design rule governs compileml.viz: plots draw decision payloads, they never recompute them. Every function consumes decide() output — the same integers production emits — so a chart cannot disagree with the deployed decision. The waterfall's bars sum to the score because the reconciliation identity says they must; the test suite asserts it on the exact half-micro integers behind the plot.

The waterfall — one decision, audited#

from compileml.runtime import decide
from compileml.viz import waterfall

decision = decide(artifact, applicant_row, include_contributions=True)
fig, ax = waterfall(decision, labels=DISPLAY_NAMES)

Baseline → per-feature impacts (largest first) → final score, with the band and latent_int in the title. Features beyond max_features collapse into a single balancing remainder bar; artifacts compiled deeper than depth 2 show their interaction residual as an explicit bar rather than hiding it.

A validator can re-add the figure by hand. That is the point.

Population views#

from compileml.viz import decision_drivers, band_conditioned_decision_drivers, band_ladder

sample = [decide(artifact, row, explain=True) for row in X_sample]
decision_drivers(sample, y=y_sample)                       # SHAP-style beeswarm by reason code
band_conditioned_decision_drivers(sample, y=y_sample)      # faceted beeswarm per band
band_ladder(score_only_decisions, y_sample)                # observed bad rate per band

The driver plots are the original beeswarm design: one point per (decision, top-k reason), labeled by reason code, biggest drivers on top, per-pile x-jitter to break discrete-value stacks, risk-increasing points drawn slightly larger and on top. color_by selects the encoding — "auto" colors by observed outcome when y is given and by impact direction otherwise — and value_color=True / value_alpha=True (with values= one {feature: value} dict per decision) add SHAP-style feature-value gradients toward dark low-end counterpart colors.

If your decisioning already explains everything (the recommended default), the driver plots simply consume stored payloads — sampling becomes a readability choice for the beeswarm, not a compute constraint. When generating explanations ad hoc instead, note each costs O(features²) traversals. band_ladder needs only bands either way, so cheap decide(…, explain=False) payloads suffice for it.

Dependency-free SVG#

from compileml.viz import waterfall_svg

svg_text = waterfall_svg(decision)            # standard library only
open("decision.svg", "w").write(svg_text)

The payload is plain integers, so the waterfall renders without any plotting stack — suitable for audit records, emails, and docs. Output is deterministic: same payload, same bytes. The image on the project README is this function's output, generated from the repo's committed reference artifact.

Styling#

Every renderer accepts labels={feature: display_name} and a colors={…} override (keys: up, down, base, remainder, residual, good, bad, neutral), and the matplotlib functions take an ax= to compose into your own figures.

See it rendered#

examples/04_visualization.ipynb is the visual reference for everything on this page: arrow geometry, exact remainder truncation, the depth>2 residual bar, inline SVG, every color_by and value_color encoding, sort_metric variants, per-band facets, and restyling with an institutional palette.