Skip to content

Add response_type parameter for expectation vs prediction intervals#643

Open
drbenvincent wants to merge 10 commits intomainfrom
issue-295-hdi-type-option
Open

Add response_type parameter for expectation vs prediction intervals#643
drbenvincent wants to merge 10 commits intomainfrom
issue-295-hdi-type-option

Conversation

@drbenvincent
Copy link
Collaborator

@drbenvincent drbenvincent commented Jan 10, 2026

Summary

Allow causal impact plots and effect summaries to show posterior predictive outcomes, not just model expectation.

This PR adds a response_type parameter across experiment classes, allowing users to choose whether intervals represent:

  • \"expectation\" (default): model expectation (μ), excludes observation noise. Narrower intervals for parameter uncertainty.
  • \"prediction\": posterior predictive (ŷ), includes observation noise (σ). Wider intervals for full predictive uncertainty.

Fixes #295

Changes

Core infrastructure (causalpy/plot_utils.py)

  • Add ResponseType type alias for Literal[\"expectation\", \"prediction\"]
  • Add add_hdi_annotation() helper to annotate plots
  • Add _log_response_type_info_once() and _log_response_type_effect_summary_once() for one-time guidance

Model changes (causalpy/pymc_models.py)

  • Add response_type parameter to calculate_impact()
  • Map response_type to internal variable names (mu / y_hat)
  • Keep TODO note about future refactor to avoid duplicated logic

Experiment classes updated

All _bayesian_plot() methods:

  • InterruptedTimeSeries, SyntheticControl, DifferenceInDifferences, PrePostNEGD, RegressionDiscontinuity, RegressionKink

Changes to each:

  • Add response_type parameter with default \"expectation\"
  • Add show_hdi_annotation parameter with default False
  • Use mu or y_hat from posterior_predictive based on response_type
  • Log one-time info message about response type options
  • Update docstrings with comprehensive parameter documentation

Additional methods with response_type support (ITS & SC only):

  • effect_summary()
  • get_plot_data_bayesian()
  • analyze_persistence() (ITS only)

Reporting changes (causalpy/reporting.py)

  • Add response_type parameter to _extract_window() and _extract_counterfactual()
  • On-demand calculation of y_hat-based impacts when response_type=\"prediction\"

Base class (causalpy/experiments/base.py)

  • Add response_type parameter to abstract effect_summary() signature
  • Document that parameter only affects ITS/SC (ignored for coefficient-based experiments)

Tests

  • Add coverage for new setup helpers and properties in causalpy/tests/test_experiment_refactor.py
  • Add coverage for analyze_persistence(response_type=\"prediction\") in ITS

Notebook

  • Re-ran docs/source/notebooks/its_covid.ipynb to update outputs

Response type applicability

Experiment Plotting Effect Summary Notes
ITS ✅ Full support ✅ Full support Effect = y - ŷ
Synthetic Control ✅ Full support ✅ Full support Effect = y - ŷ
DiD ✅ Plotting only ❌ Ignored Effect = coefficient
PrePostNEGD ✅ Plotting only ❌ Ignored Effect = coefficient
RD ✅ Plotting only ❌ Ignored Effect = coefficient
RKink ✅ Plotting only ❌ Ignored Effect = coefficient

User communication strategy

Instead of cluttering plots with annotations by default:

  1. Clean output by default - show_hdi_annotation=False
  2. One-time logging - INFO message on first plot/effect_summary call
  3. Opt-in annotation - Users can enable show_hdi_annotation=True

Usage

# Default behavior (model expectation, narrower intervals)
result.plot()

# Include observation noise (wider intervals)
result.plot(response_type=\"prediction\")

# Add annotation to explain what the interval represents
result.plot(show_hdi_annotation=True)

# Effect summary with full predictive uncertainty
result.effect_summary(response_type=\"prediction\")

# Get plot data with prediction-based values
result.get_plot_data_bayesian(response_type=\"prediction\")

Design decisions

  1. Semantic naming: User-facing APIs use \"expectation\" / \"prediction\" rather than internal variable names \"mu\" / \"y_hat\"
  2. On-demand calculation: Stored impacts use mu by default; y_hat-based impacts calculated on request
  3. Logging over warnings: INFO logging for better production control
  4. Annotation opt-in: Clean plots for publication, opt-in annotation for exploration

Testing

  • pre-commit run --all-files

Checklist

  • Pre-commit checks pass
  • All tests pass
  • Documentation updated (docstrings)
  • Follows project coding conventions
  • Type hints consistent (ResponseType alias used throughout)
  • Notebook outputs refreshed (its_covid.ipynb)
  • Example notebooks updated (to be done separately)
  • Integration tests for new response_type parameter

📚 Documentation preview 📚: https://causalpy--643.org.readthedocs.build/en/643/

Fixes #295

This adds an hdi_type parameter to _bayesian_plot methods across all
experiment classes, allowing users to choose between:

- 'expectation': HDI of model expectation (μ), excluding observation noise
  (default, current behavior)
- 'prediction': HDI of posterior predictive (ŷ), including observation noise

Changes:
- Add HdiType type alias and add_hdi_annotation() helper to plot_utils.py
- Update _bayesian_plot() methods in:
  - InterruptedTimeSeries
  - SyntheticControl
  - DifferenceInDifferences
  - PrePostNEGD
  - RegressionDiscontinuity
  - RegressionKink
- Add text annotation to figures explaining what the HDI represents
- Update docstrings with comprehensive parameter documentation
@codecov
Copy link

codecov bot commented Jan 10, 2026

Codecov Report

❌ Patch coverage is 98.35616% with 6 lines in your changes missing coverage. Please review.
✅ Project coverage is 94.52%. Comparing base (278e947) to head (bfa2780).

Files with missing lines Patch % Lines
causalpy/reporting.py 68.42% 3 Missing and 3 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #643      +/-   ##
==========================================
+ Coverage   94.35%   94.52%   +0.16%     
==========================================
  Files          44       46       +2     
  Lines        7517     7853     +336     
  Branches      456      478      +22     
==========================================
+ Hits         7093     7423     +330     
- Misses        262      265       +3     
- Partials      162      165       +3     

☔ 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.

- Move annotation text lower to avoid overlap with axis labels
- Add subplots_adjust to make room for annotation below axes
- Add show_hdi_annotation parameter (default True) to all _bayesian_plot methods
- Users can set show_hdi_annotation=False to hide the text annotation
@review-notebook-app
Copy link

Check out this pull request on  ReviewNB

See visual diffs & provide feedback on Jupyter Notebooks.


Powered by ReviewNB

@drbenvincent drbenvincent changed the title Add hdi_type parameter to Bayesian plot methods [WIP] Add hdi_type parameter to Bayesian plot methods Jan 10, 2026
Introduces the hdi_type parameter to allow users to choose between 'expectation' (model mean, excluding observation noise) and 'prediction' (posterior predictive, including observation noise) for HDI intervals in plots, effect summaries, and data extraction methods. Updates plotting and reporting utilities to support this option, adds logging and annotation for HDI type, and refactors experiment classes to calculate impacts and counterfactuals on demand based on hdi_type. Documentation and notebook examples are updated to reflect these changes.
@drbenvincent drbenvincent changed the title [WIP] Add hdi_type parameter to Bayesian plot methods Add hdi_type parameter for expectation vs prediction HDI intervals Jan 11, 2026
- Add comprehensive tests in test_hdi_type.py for hdi_type='prediction'
  and show_hdi_annotation=True across all experiment classes (ITS, SC,
  DiD, RD, RKink, PrePostNEGD)
- Add tests for effect_summary and get_plot_data_bayesian with prediction
- Add unit tests for add_hdi_annotation and logging functions in
  test_plot_utils.py
- Fix bug in _extract_window where it assumed result.post_y exists
  (ITS only); now correctly handles result.datapost_treated for
  SyntheticControl as well
- These tests specifically cover the partial coverage branches reported
  by Codecov
@drbenvincent drbenvincent added the enhancement New feature or request label Jan 11, 2026
Copy link
Contributor

@OriolAbril OriolAbril left a comment

Choose a reason for hiding this comment

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

I would use a different name for the argument so it doesn't include hdi in it. Some options could be response, response_type, effect_type or target (bambi uses a mix of string arg target and boolean pps argument for a very similar purpose; I am not a very big fan of this split though).

The main reason is avoiding otherwise unnecessary future changes. I see quite probable at some point the kind credible interval will be a user choice between HDI or ETI, so having hdi in the name would be a bit confusing when using quantile based intervals instead. It might also be interesting to add different ways to visualize the uncertainty in addition to the shaded band generated from the credible interval such as "spaghetti"/ensemble plots or projected histogram view

Align plotting and summary APIs with response_type naming, update logging helpers,
and adjust tests for the new parameter name.
@drbenvincent drbenvincent changed the title Add hdi_type parameter for expectation vs prediction HDI intervals Add response_type parameter for expectation vs prediction intervals Jan 24, 2026
@github-actions github-actions bot added the plotting Improve or fix plotting label Jan 24, 2026
Cover datapre/datapost properties for ITS and synthetic control, and
assert RegressionKink gradient change uses the instance epsilon.
Cover prediction response_type in three-period ITS analysis and refresh the
its_covid notebook outputs.
@read-the-docs-community
Copy link

Documentation build overview

📚 causalpy | 🛠️ Build #31338460 | 📁 Comparing bfa2780 against latest (278e947)


🔍 Preview build

Show files changed (25 files in total): 📝 25 modified | ➕ 0 added | ➖ 0 deleted
File Status
404.html 📝 modified
notebooks/its_covid.html 📝 modified
_modules/causalpy/pymc_models.html 📝 modified
api/generated/causalpy.experiments.base.BaseExperiment.effect_summary.html 📝 modified
api/generated/causalpy.experiments.interrupted_time_series.InterruptedTimeSeries.analyze_persistence.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.synthetic_control.SyntheticControl.effect_summary.html 📝 modified
api/generated/causalpy.experiments.synthetic_control.SyntheticControl.get_plot_data_bayesian.html 📝 modified
api/generated/causalpy.pymc_models.BayesianBasisExpansionTimeSeries.calculate_impact.html 📝 modified
api/generated/causalpy.pymc_models.InstrumentalVariableRegression.calculate_impact.html 📝 modified
api/generated/causalpy.pymc_models.LinearRegression.calculate_impact.html 📝 modified
api/generated/causalpy.pymc_models.PropensityScore.calculate_impact.html 📝 modified
api/generated/causalpy.pymc_models.PyMCModel.calculate_impact.html 📝 modified
api/generated/causalpy.pymc_models.PyMCModel.html 📝 modified
api/generated/causalpy.pymc_models.StateSpaceTimeSeries.calculate_impact.html 📝 modified
api/generated/causalpy.pymc_models.StateSpaceTimeSeries.html 📝 modified
api/generated/causalpy.pymc_models.WeightedSumFitter.calculate_impact.html 📝 modified
_modules/causalpy/experiments/base.html 📝 modified
_modules/causalpy/experiments/diff_in_diff.html 📝 modified
_modules/causalpy/experiments/interrupted_time_series.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/synthetic_control.html 📝 modified

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

Labels

enhancement New feature or request plotting Improve or fix plotting

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Allow causal impact plots to show posterior predicted outcome, not just model expectation

2 participants