[plotting] Migrate plotting from matplotlib/xarray to tidydraws + plotnine (#988)#990
[plotting] Migrate plotting from matplotlib/xarray to tidydraws + plotnine (#988)#990drbenvincent wants to merge 60 commits into
Conversation
First step of the plotting migration from imperative matplotlib/xarray to a declarative tidydraws + plotnine stack. environment.yml is regenerated from pyproject.toml by the pyproject2conda prek hook. Co-authored-by: Cursor <cursoragent@cursor.com>
Replace the imperative matplotlib/xarray _bayesian_plot with a declarative plotnine build for the ribbon and spaghetti kinds, returning a ggplot. The xarray dimension guards (isel(treated_units=0), stack) are replaced by tidydraws prediction_draws + point_interval, grouping on the running variable. The histogram kind (a per-column-normalised 2D density heatmap with no plotnine geom) keeps the legacy matplotlib code path via _bayesian_plot_mpl and returns a (fig, ax) tuple. base._render_plot now passes a ggplot straight through while retaining the legacy tuple path for not-yet-migrated experiments. Tests updated for the new return type and to spy on td.point_interval for the ci_prob wiring check. Co-authored-by: Cursor <cursoragent@cursor.com>
Temporary visual-review assets for the plotnine migration PR. Removed in the final cleanup commit before merge; referenced from PR comments via raw URLs. Co-authored-by: Cursor <cursoragent@cursor.com>
Build a two-facet ggplot for kind="ribbon": top facet shows the pre/post scatter plus control and treatment posterior predictive bands, bottom facet shows the treatment effect posterior as a geom_density with a zero reference line and credible-interval bounds, replacing az.plot_posterior. Posterior extraction uses tidydraws prediction_draws + point_interval. spaghetti and histogram kinds route to the retained matplotlib method and return a (fig, ax) tuple. ci_prob wiring test now spies on td.point_interval. Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Documentation build overview
145 files changed ·
|
Relabel scatter points to the same Control/Treatment series names as the posterior bands so points, lines and bands share a single legend (the fill guide is suppressed). Drop the oversized panel_spacing that left a large vertical gap between the two facets. Co-authored-by: Cursor <cursoragent@cursor.com>
…988) The plotnine paths already covered the default ribbon (and RD spaghetti) kinds; keeping full `_bayesian_plot_mpl` copies only grew the files. Delete those fallbacks and raise for kinds that do not yet map cleanly. Co-authored-by: Cursor <cursoragent@cursor.com>
|
Cleanup after RD / PrePostNEGD migration The earlier RD and PrePostNEGD commits added plotnine paths but left the full legacy matplotlib methods as This commit deletes both duplicate plotters (~218 net lines removed). Default Next up: Guardrail #0 inventory for |
Build scatter plus control/treatment (and multi-period counterfactual) ribbons as a shared plotnine ggplot, then .draw() and reuse the existing continuous-x violin and causal-impact arrow snippets. Returns (fig, ax). Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Replace the imperative matplotlib/xarray _bayesian_plot with a declarative plotnine build for ribbon and spaghetti, returning a ggplot. The kink threshold is a geom_vline. kind='histogram' raises until a plotnine path exists. Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Commit — RegressionKink (ribbon, default)Full Before (matplotlib) After (plotnine) Review checklist
Known cosmetic deltas (deferred to the final theming pass): legend moved to the right (plotnine default) and includes the threshold series; multi-line title left-aligned rather than centred. Visual review approved. |
Build the three-panel ITS layout as a faceted plotnine ggplot, then .draw() and reuse treatment axvlines, singleton HDI markers, and date formatting. Returns (fig, ax); kind other than ribbon raises. Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Build the three-panel SC layout as a faceted plotnine ggplot, then .draw() and reuse the treatment axvline, optional predictor lines, and date formatting. Returns (fig, ax); kind other than ribbon raises. Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
…988) Build the three-panel SDiD layout as a faceted plotnine ggplot, then .draw() and reuse the treatment axvline and date formatting. Returns (fig, ax); kind other than ribbon raises. Co-authored-by: Cursor <cursoragent@cursor.com>
…mages (#988) Co-authored-by: Cursor <cursoragent@cursor.com>
Build the three-panel PiecewiseITS layout as a faceted plotnine ggplot over the full time series, then .draw() and reuse interruption axvlines plus titles/legend. Returns (fig, ax); kind other than ribbon raises. OLS path unchanged. Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
…#988) Build the event-study view with geom_pointrange over cached ATT/HDI bounds, then .draw() and set titles/legend. Returns (fig, [ax]). plot_group_time and OLS paths unchanged. Co-authored-by: Cursor <cursoragent@cursor.com>
Commit — StaggeredDifferenceInDifferences event study (Bayesian
|
…988) Build cohort ATT trajectories as a faceted (or overlay) plotnine ggplot with ribbons/lines/points, then .draw() for titles, shared-y, vlines, and legends. OLS helpers unchanged; unused Bayesian segment helper removed. Co-authored-by: Cursor <cursoragent@cursor.com>
#988) Co-authored-by: Cursor <cursoragent@cursor.com>
Commit — StaggeredDifferenceInDifferences
|
Replace az.plot_forest and OLS barh with geom_errorbarh/geom_col; thread hdi_prob via az.hdi for wiring tests. Co-authored-by: Cursor <cursoragent@cursor.com>
SC + SDiD — spaghetti & histogram (Step 1–2)Commit: Migrated
Review images (
Still on matplotlib fallback: DiD, PiecewiseITS, PrePostNEGD (spaghetti/histogram); RD/RKink (histogram only). Next: DiD / PiecewiseITS / PrePostNEGD spaghetti + histogram. |
…PostNEGD (#988) Follow the ITS/SC pattern: plotnine layout with tidydraws draw samples for spaghetti and matplotlib pcolormesh overlays for histogram density. PrePostNEGD keeps ggplot return for ribbon only. Co-authored-by: Cursor <cursoragent@cursor.com>
DiD + PiecewiseITS + PrePostNEGD — spaghetti & histogramCommit: All three experiments now support
Review images (
Remaining matplotlib fallback: RD/RKink histogram only. Next: RD/RK histogram plotnine path, then Step 3 cleanup ( |
#988) geom_line(group="_draw_id") merged control and treatment posterior samples that share chain/draw ids at the same pretest x, producing vertical comb artifacts. Use per-series _line_id and sort by draw then pre. Co-authored-by: Cursor <cursoragent@cursor.com>
PrePostNEGD spaghetti fix — visual gate passedCommit: Bug: Fix: per-series Review image: Remaining: RD/RKink histogram plotnine path, then Step 3 cleanup. |
Use the plotnine scatter/line/vline base with matplotlib pcolormesh density overlay after .draw(), matching ITS and DiD. Completes kind parity for all Bayesian time-series-style experiment plots. Co-authored-by: Cursor <cursoragent@cursor.com>
RD + RKink — histogram (final kind slice)Commit: (see latest on branch) Last matplotlib fallback removed: Review images (
Compare to existing baselines: All experiments now have plotnine paths for ribbon/spaghetti/histogram (where applicable). Next: Step 3 cleanup — remove |
RD + RKink histogram — visual gate passedCommit: Plotnine scatter/line/vline base + matplotlib Review images: Migration slice complete: all Bayesian experiments with |
Drop unused matplotlib Bayesian plot implementations now that all kind variants use plotnine (histogram keeps a matplotlib pcolormesh overlay). Co-authored-by: Cursor <cursoragent@cursor.com>
Step 3 complete — dead matplotlib Bayesian paths removedRemoved Still intentional:
Checks: Migration slice for Bayesian |
…988) Extract sample_draw_lines, prediction/da summaries, posterior_kind_layers, and plot_posterior_histogram into plot_utils; refactor experiments to use them instead of copy-pasted inner functions. Remove plot_xY, ribbon/spaghetti matplotlib paths, and histogram_tile_df. Mark tidydraws pip-only for CI. Co-authored-by: Cursor <cursoragent@cursor.com>
Visual gate is complete; review PNGs remain in PR comments and git history. Co-authored-by: Cursor <cursoragent@cursor.com>
Unify experiment plots behind BaseExperiment rendering with a stable (Figure, Axes) contract, shared tidydraws/plotnine primitives, and declarative geom_tile posterior histograms using proportion normalization instead of matplotlib pcolormesh overlays. Co-authored-by: Cursor <cursoragent@cursor.com>
Derive summaries, paths, and histograms from one tidy posterior table and remove duplicate legacy plotting paths. Co-authored-by: Cursor <cursoragent@cursor.com>
Separate tidy plot-data preparation from plotnine composition and keep only the compatibility legend bridge. Co-authored-by: Cursor <cursoragent@cursor.com>
Separate tidy posterior preparation from declarative plot composition and remove the unused histogram-layer abstraction. Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Merge origin/main and preserve Patsy validation while completing the tidydraws/plotnine migration: causal-panel semantic contract for ITS/SC/SDiD/PiecewiseITS, shared posterior validation, discrete-x DiD scales, PlotSpec-only Bayesian internals, and stable public (Figure, Axes) returns. Upstream blocker tracked at drbenvincent/tidydraws#35 Co-authored-by: Cursor <cursoragent@cursor.com>
Consolidate posterior transformations and use explicit causal-panel data to reduce duplicated plotting code while preserving public plot contracts. Co-authored-by: Cursor <cursoragent@cursor.com>
Remove branch-specific agent guidance so plotting changes do not alter repository-wide Cursor behavior. Co-authored-by: Cursor <cursoragent@cursor.com>
Use the 0.4.3 release with bare ArviZ metadata so CausalPy retains its existing ArviZ constraint across supported Python versions. Co-authored-by: Cursor <cursoragent@cursor.com>
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #990 +/- ##
==========================================
- Coverage 95.51% 95.42% -0.10%
==========================================
Files 98 99 +1
Lines 15868 16080 +212
Branches 931 979 +48
==========================================
+ Hits 15157 15345 +188
- Misses 504 524 +20
- Partials 207 211 +4 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Restore plot semantics and compatibility behavior across panel, pre/post, and staggered DiD views, with focused regression coverage. Co-authored-by: Cursor <cursoragent@cursor.com>
Use a consistent Plotnine namespace to reduce import noise, while removing unused plot metadata and avoiding mutation through a frozen result bundle. Co-authored-by: Cursor <cursoragent@cursor.com>
Keep Plotnine as the single legend owner while preserving Matplotlib compatibility for figure display, legend options, and layout. Co-authored-by: Cursor <cursoragent@cursor.com>
Render the first causal panel at the top and keep the PrePost legend inside its data panel for clearer notebook plots. Co-authored-by: Cursor <cursoragent@cursor.com>
Assert the Plotnine figure-level legend used by the declarative singleton-period plot instead of expecting an Axes legend. Co-authored-by: Cursor <cursoragent@cursor.com>
Commit only notebooks with complete, error-free Papermill executions so the refreshed plots are ready for review. Co-authored-by: Cursor <cursoragent@cursor.com>
|
Notebook rendering checkpoint Commit Not rerendered or not complete yet:
These notebooks should be rerendered on a faster machine when their dependencies and runtime allow it. |






Summary
Migrates CausalPy experiment plotting from imperative matplotlib/ArviZ composition to a declarative tidydraws + plotnine stack (#988).
The public contract remains stable: experiment
plot()methods return(matplotlib.figure.Figure, matplotlib.axes.Axes | numpy.ndarray). Internally, Bayesian plot builders returnPlotSpec, which contains a declarativeggplotand an optional narrow post-draw matplotlib overlay.Architecture
ITS, Synthetic Control, Synthetic DiD, and Piecewise ITS additionally share a semantic
CausalPanelData→build_causal_panel_plot()path for their common three-panel outcome/effect/cumulative-effect view.Plotnine is imported consistently through the
p9namespace. Matplotlib overlays remain only for compatibility behavior that is awkward to express declaratively, including date-axis formatting, custom legends required bylegend_kwargs, and selected post-draw axis adjustments.What changed
plotnine>=0.15.7,tidydraws==0.4.3, and directpolarsdependencies while retaining Python 3.11 support.az.plot_*.kind,ci_kind, andnum_samples.mainacross the affected experiments.Scope
kind ∈ {"ribbon", "histogram", "spaghetti"}views and PanelRegression/Staggered DiD auxiliary plots.plot()stub (NotImplementedError).geom_bin_2draw bin counts mapped to alpha for this pass.Size
Against the current PR base:
base.py: net −3 lines.plot_utils.py: net +400 lines, containing the shared extraction, transformation, composition, and rendering helpers.The remaining production increase is concentrated in the shared plotting layer rather than duplicated experiment implementations.
Test evidence
prek run --all-files.make test-patch-cov: 97% total coverage, 96% patch coverage.(Figure, Axes)contract coverage for every posterior kind.CausalPanelDatacoverage for ITS, Synthetic Control, and Synthetic DiD.