Skip to content

Multivariate its#724

Open
JeanVanDyk wants to merge 4 commits intopymc-labs:mainfrom
JeanVanDyk:multivariate_its
Open

Multivariate its#724
JeanVanDyk wants to merge 4 commits intopymc-labs:mainfrom
JeanVanDyk:multivariate_its

Conversation

@JeanVanDyk
Copy link
Copy Markdown
Contributor

Acknowledgments & Disclaimer

This PR involved significant architectural changes and required taking a fair amount of initiative. I had to make several design choices that may still need refinement. Please feel free to challenge my approach and suggest improvements; I am very open to feedback to ensure this aligns with the project's standards.

To showcase these new features, I have included a demo notebook in this PR which covers the full scope of the implementation.

1. New Model: MultivarLinearReg

I have introduced a new model capable of handling multiple outcomes simultaneously.

Cholesky Decomposition: The model uses Cholesky decomposition for the covariance matrix. This is a robust approach because it ensures the matrix remains symmetric and positive-definite, while significantly improving computational efficiency and numerical stability compared to a direct matrix inversion.

2. Structural Refactor: New "Outcome" Dimension

I updated the data structure by introducing a dedicated outcome dimension.

Scalability: This choice makes the library more scalable for high-dimensional targets.

Design Choice: I deliberately avoided using the treated units dimension for this purpose. While treated units are typically assumed to be independent, outcomes can be highly correlated. Using a distinct dimension allows us to model these correlations properly.

3. Visualization & Reporting

New Plotting Modes: Added two ways to visualize results: overlay (for direct comparison) and per_outcome (for individual analysis).

Selective Plotting: It is possible to filter which outcomes to display by passing a list to the outcomes_to_plot argument.

Enhanced Summary: The summary() output has been updated to provide a clear distinction between outcomes, making the statistical results easier to read.

@review-notebook-app
Copy link
Copy Markdown

Check out this pull request on  ReviewNB

See visual diffs & provide feedback on Jupyter Notebooks.


Powered by ReviewNB

@cursor
Copy link
Copy Markdown

cursor Bot commented Feb 20, 2026

PR Summary

Medium Risk
Touches core ITS data shapes and plotting/reporting paths and adds a new PyMC model with custom predict/score behavior; regressions are most likely around dimension handling and backward compatibility.

Overview
Enables multivariate Interrupted Time Series by allowing formula to be a list[str], introducing an explicit outcomes dimension in ITS y data, and gating multivariate fitting to PyMC models that declare supports_multivariate.

Adds MultivarLinearReg (LKJ covariance across outcomes) and updates ITS fit/predict/impact, effect_summary, get_plot_data_bayesian (long-format output for multivariate), and Bayesian plotting (overlay vs per-outcome layouts + outcome filtering). Also updates BaseExperiment.plot type hints and expands the ITS test suite with multivariate integration coverage.

Written by Cursor Bugbot for commit c244263. This will update automatically on new commits. Configure here.

@read-the-docs-community
Copy link
Copy Markdown

Documentation build overview

📚 causalpy | 🛠️ Build #31493777 | 📁 Comparing c244263 against latest (7c5e685)


🔍 Preview build

Show files changed (83 files in total): 📝 41 modified | ➕ 42 added | ➖ 0 deleted
File Status
404.html 📝 modified
genindex.html 📝 modified
notebooks/its_plots_examples.html ➕ added
_modules/causalpy/pymc_models.html 📝 modified
api/generated/causalpy.experiments.base.BaseExperiment.html 📝 modified
api/generated/causalpy.experiments.base.BaseExperiment.plot.html 📝 modified
api/generated/causalpy.experiments.diff_in_diff.DifferenceInDifferences.html 📝 modified
api/generated/causalpy.experiments.diff_in_diff.DifferenceInDifferences.plot.html 📝 modified
api/generated/causalpy.experiments.instrumental_variable.InstrumentalVariable.init.html 📝 modified
api/generated/causalpy.experiments.instrumental_variable.InstrumentalVariable.html 📝 modified
api/generated/causalpy.experiments.interrupted_time_series.InterruptedTimeSeries.init.html 📝 modified
api/generated/causalpy.experiments.interrupted_time_series.InterruptedTimeSeries.algorithm.html 📝 modified
api/generated/causalpy.experiments.interrupted_time_series.InterruptedTimeSeries.effect_summary.html 📝 modified
api/generated/causalpy.experiments.interrupted_time_series.InterruptedTimeSeries.get_plot_data_bayesian.html 📝 modified
api/generated/causalpy.experiments.interrupted_time_series.InterruptedTimeSeries.html 📝 modified
api/generated/causalpy.experiments.interrupted_time_series.InterruptedTimeSeries.plot.html 📝 modified
api/generated/causalpy.experiments.inverse_propensity_weighting.InversePropensityWeighting.init.html 📝 modified
api/generated/causalpy.experiments.inverse_propensity_weighting.InversePropensityWeighting.html 📝 modified
api/generated/causalpy.experiments.inverse_propensity_weighting.InversePropensityWeighting.plot.html 📝 modified
api/generated/causalpy.experiments.prepostnegd.PrePostNEGD.html 📝 modified
api/generated/causalpy.experiments.prepostnegd.PrePostNEGD.plot.html 📝 modified
api/generated/causalpy.experiments.regression_discontinuity.RegressionDiscontinuity.html 📝 modified
api/generated/causalpy.experiments.regression_discontinuity.RegressionDiscontinuity.plot.html 📝 modified
api/generated/causalpy.experiments.regression_kink.RegressionKink.init.html 📝 modified
api/generated/causalpy.experiments.regression_kink.RegressionKink.html 📝 modified
api/generated/causalpy.experiments.regression_kink.RegressionKink.plot.html 📝 modified
api/generated/causalpy.experiments.regression_kink.html 📝 modified
api/generated/causalpy.experiments.staggered_did.StaggeredDifferenceInDifferences.html 📝 modified
api/generated/causalpy.experiments.staggered_did.StaggeredDifferenceInDifferences.plot.html 📝 modified
api/generated/causalpy.experiments.synthetic_control.SyntheticControl.html 📝 modified
api/generated/causalpy.experiments.synthetic_control.SyntheticControl.plot.html 📝 modified
api/generated/causalpy.pymc_models.MultivarLinearReg.init.html ➕ added
api/generated/causalpy.pymc_models.MultivarLinearReg.add_coord.html ➕ added
api/generated/causalpy.pymc_models.MultivarLinearReg.add_coords.html ➕ added
api/generated/causalpy.pymc_models.MultivarLinearReg.add_named_variable.html ➕ added
api/generated/causalpy.pymc_models.MultivarLinearReg.build_model.html ➕ added
api/generated/causalpy.pymc_models.MultivarLinearReg.calculate_cumulative_impact.html ➕ added
api/generated/causalpy.pymc_models.MultivarLinearReg.calculate_impact.html ➕ added
api/generated/causalpy.pymc_models.MultivarLinearReg.check_start_vals.html ➕ added
api/generated/causalpy.pymc_models.MultivarLinearReg.compile_d2logp.html ➕ added
api/generated/causalpy.pymc_models.MultivarLinearReg.compile_dlogp.html ➕ added
api/generated/causalpy.pymc_models.MultivarLinearReg.compile_fn.html ➕ added
api/generated/causalpy.pymc_models.MultivarLinearReg.compile_logp.html ➕ added
api/generated/causalpy.pymc_models.MultivarLinearReg.copy.html ➕ added
api/generated/causalpy.pymc_models.MultivarLinearReg.create_value_var.html ➕ added
api/generated/causalpy.pymc_models.MultivarLinearReg.d2logp.html ➕ added
api/generated/causalpy.pymc_models.MultivarLinearReg.debug.html ➕ added
api/generated/causalpy.pymc_models.MultivarLinearReg.dlogp.html ➕ added
api/generated/causalpy.pymc_models.MultivarLinearReg.eval_rv_shapes.html ➕ added
api/generated/causalpy.pymc_models.MultivarLinearReg.fit.html ➕ added
api/generated/causalpy.pymc_models.MultivarLinearReg.get_context.html ➕ added
api/generated/causalpy.pymc_models.MultivarLinearReg.html ➕ added
api/generated/causalpy.pymc_models.MultivarLinearReg.initial_point.html ➕ added
api/generated/causalpy.pymc_models.MultivarLinearReg.logp.html ➕ added
api/generated/causalpy.pymc_models.MultivarLinearReg.logp_dlogp_function.html ➕ added
api/generated/causalpy.pymc_models.MultivarLinearReg.make_obs_var.html ➕ added
api/generated/causalpy.pymc_models.MultivarLinearReg.name_for.html ➕ added
api/generated/causalpy.pymc_models.MultivarLinearReg.name_of.html ➕ added
api/generated/causalpy.pymc_models.MultivarLinearReg.point_logps.html ➕ added
api/generated/causalpy.pymc_models.MultivarLinearReg.predict.html ➕ added
api/generated/causalpy.pymc_models.MultivarLinearReg.print_coefficients.html ➕ added
api/generated/causalpy.pymc_models.MultivarLinearReg.priors_from_data.html ➕ added
api/generated/causalpy.pymc_models.MultivarLinearReg.profile.html ➕ added
api/generated/causalpy.pymc_models.MultivarLinearReg.register_data_var.html ➕ added
api/generated/causalpy.pymc_models.MultivarLinearReg.register_rv.html ➕ added
api/generated/causalpy.pymc_models.MultivarLinearReg.replace_rvs_by_values.html ➕ added
api/generated/causalpy.pymc_models.MultivarLinearReg.score.html ➕ added
api/generated/causalpy.pymc_models.MultivarLinearReg.set_data.html ➕ added
api/generated/causalpy.pymc_models.MultivarLinearReg.set_dim.html ➕ added
api/generated/causalpy.pymc_models.MultivarLinearReg.set_initval.html ➕ added
api/generated/causalpy.pymc_models.MultivarLinearReg.shape_from_dims.html ➕ added
api/generated/causalpy.pymc_models.MultivarLinearReg.to_graphviz.html ➕ added
api/generated/causalpy.pymc_models.html 📝 modified
_modules/causalpy/experiments/base.html 📝 modified
_modules/causalpy/experiments/diff_in_diff.html 📝 modified
_modules/causalpy/experiments/instrumental_variable.html 📝 modified
_modules/causalpy/experiments/interrupted_time_series.html 📝 modified
_modules/causalpy/experiments/inverse_propensity_weighting.html 📝 modified
_modules/causalpy/experiments/prepostnegd.html 📝 modified
_modules/causalpy/experiments/regression_discontinuity.html 📝 modified
_modules/causalpy/experiments/regression_kink.html 📝 modified
_modules/causalpy/experiments/staggered_did.html 📝 modified
_modules/causalpy/experiments/synthetic_control.html 📝 modified

Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 3 potential issues.

Comment thread causalpy/pymc_models.py
# Reduce to (chain, draw, n_o, n_o); dim names may vary
cov_arr = np.asarray(cov_post).squeeze()
if cov_arr.ndim != 4:
cov_arr = cov_arr.reshape((-1, -1, n_o, n_o)) # (chain, draw, n_o, n_o)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Two -1 dimensions in numpy reshape causes crash

High Severity

cov_arr.reshape((-1, -1, n_o, n_o)) uses two -1 dimensions, which numpy does not allow — only one unknown dimension can be specified. This will always raise a ValueError at runtime when cov_arr.ndim != 4, making print_coefficients crash whenever it tries to print the residual covariance for MultivarLinearReg.

Fix in Cursor Fix in Web

self.pre_y_raw = np.column_stack(pre_y_list) # (n_pre, n_outcomes)
self.post_y_raw = np.column_stack(post_y_list) # (n_post, n_outcomes)
self.pre_X = pre_X_list[0]
self.post_X = post_X_list[0]
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Only first formula's X used for all outcomes

Medium Severity

When multiple formulas with different right-hand sides are provided (e.g., ["y1 ~ 1 + t + x1", "y2 ~ 1 + t + x2"]), only the first formula's design matrix X is used for fitting all outcomes via MultivarLinearReg. The per-outcome pre_X_list and post_X_list are stored but never used for model fitting, silently producing incorrect estimates for non-first outcomes with different predictors. The docstring's example encourages exactly this usage.

Additional Locations (1)

Fix in Cursor Fix in Web

Object with .table (DataFrame) and .text (str) attributes.
In the multivariate case (multiple outcomes), the table has an
``outcome`` column and a ``statistic`` column (e.g. "average",
"cumulative"), with one row per (outcome, statistic).
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Multivariate comparison summary crashes on float conversion

Medium Severity

Calling effect_summary(period="comparison") on a multivariate ITS crashes. The period="comparison" early return bypasses the new multivariate handling added at lines 1732–1773. The delegated _comparison_period_summary then calls float(intervention_avg.mean(dim=["chain", "draw"]).values) on data that still carries the outcomes dimension, producing a multi-element array where a scalar is expected. This is caused by the new algorithm() producing impact data with an outcomes dimension that the comparison path never squeezes.

Additional Locations (1)

Fix in Cursor Fix in Web

@codecov
Copy link
Copy Markdown

codecov Bot commented Feb 20, 2026

Codecov Report

❌ Patch coverage is 84.31002% with 83 lines in your changes missing coverage. Please review.
✅ Project coverage is 93.82%. Comparing base (e16802d) to head (c244263).
⚠️ Report is 7 commits behind head on main.

Files with missing lines Patch % Lines
causalpy/pymc_models.py 52.57% 42 Missing and 4 partials ⚠️
causalpy/experiments/interrupted_time_series.py 87.24% 17 Missing and 20 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #724      +/-   ##
==========================================
- Coverage   94.36%   93.82%   -0.54%     
==========================================
  Files          44       45       +1     
  Lines        7591     8101     +510     
  Branches      461      515      +54     
==========================================
+ Hits         7163     7601     +438     
- Misses        264      321      +57     
- Partials      164      179      +15     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

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

Labels

enhancement New feature or request major

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants