Skip to content

Add Bayesian Synthetic Difference-in-Differences#823

Open
thomaspinder wants to merge 6 commits intomainfrom
feat/bayesian-sdid
Open

Add Bayesian Synthetic Difference-in-Differences#823
thomaspinder wants to merge 6 commits intomainfrom
feat/bayesian-sdid

Conversation

@thomaspinder
Copy link
Copy Markdown
Contributor

Implements the cut-posterior SDiD formulation of SDiD as a new experiment class. Unit and time weights are estimated jointly in a single SyntheticDifferenceInDifferencesWeightFitter model, and the ATT is computed analytically via the double-difference formula. Includes the California Proposition 99 dataset, a Jupyter notebook demo, and full test coverage.

Depends on feat/softmax-weighted-sum-fitter (PR #822) for the _softmax_simplex_weights helper and SoftmaxWeightedSumFitter base.

Relevant Issue: #47

@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

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 3, 2026

👋 Welcome to CausalPy, @thomaspinder!

Thank you for opening your first pull request! We're excited to have you contribute to the project. 🎉

Here are a few tips to help your PR get merged smoothly:

  • ✅ Make sure all CI checks pass (tests, linting, type checking)
  • 📝 Run prek run --all-files locally before pushing
  • 📖 Check our Contributing Guide for more details

A maintainer will review your changes soon. Thanks for helping make CausalPy better! 🚀


💼 LinkedIn Shoutout: Once your PR is merged, we'd love to give you a shoutout on LinkedIn to thank you for your contribution! If you're interested, just drop your LinkedIn profile URL in a comment below.

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

read-the-docs-community bot commented Apr 3, 2026

Documentation build overview

📚 causalpy | 🛠️ Build #32315835 | 📁 Comparing 5f8f5fb against latest (78ec91d)

  🔍 Preview build  

73 files changed · + 56 added · ± 17 modified

+ Added

± Modified

@NathanielF
Copy link
Copy Markdown
Collaborator

Very cool @thomaspinder!

@juanitorduz
Copy link
Copy Markdown
Collaborator

So cool! 💪 . Is this one ready for review?

@juanitorduz juanitorduz self-requested a review April 13, 2026 21:07
@thomaspinder
Copy link
Copy Markdown
Contributor Author

So cool! 💪 . Is this one ready for review?

It is!

thomaspinder and others added 2 commits April 13, 2026 23:25
Implements the cut-posterior SDiD formulation (Pinder, 2026) as a new
experiment class. Unit and time weights are estimated jointly in a single
SyntheticDifferenceInDifferencesWeightFitter model, and the ATT is
computed analytically via the double-difference formula. Includes the
California Proposition 99 dataset, a Jupyter notebook demo, and full
test coverage.

Depends on feat/softmax-weighted-sum-fitter (PR #1) for the
_softmax_simplex_weights helper and SoftmaxWeightedSumFitter base.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@thomaspinder thomaspinder changed the base branch from feat/softmax-weighted-sum-fitter to main April 13, 2026 21:29
@thomaspinder
Copy link
Copy Markdown
Contributor Author

pre-commit.ci run

Copy link
Copy Markdown
Collaborator

@juanitorduz juanitorduz left a comment

Choose a reason for hiding this comment

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

This looks great! just left some initial comments :)

Comment thread causalpy/experiments/synthetic_difference_in_differences.py Outdated
"obs_ind_raw": list(range(1, T_pre)),
}

self.model.fit(X=X, y=y, coords=COORDS)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Shall we allow the user to pass kwargs for the fit method? Say I wanna sample with Nutpie and add a random seed?

Copy link
Copy Markdown
Collaborator

@juanitorduz juanitorduz Apr 16, 2026

Choose a reason for hiding this comment

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

ahh ok! I now understand the API from the notebook

result = cp.SyntheticDifferenceInDifferences(
    df,
    treatment_time,
    control_units=control_units,
    treated_units=treated_units,
    model=cp.pymc_models.SyntheticDifferenceInDifferencesWeightFitter(
        sample_kwargs={
            "target_accept": 0.95,
            "random_seed": seed,
            "tune": 3000,
            "draws": 2000,
        },
        priors={
            "sigma_omega": Prior("HalfNormal", sigma=y_sd),
            "sigma_lambda": Prior("HalfNormal", sigma=y_sd),
            "omega0": Prior("Normal", mu=0, sigma=y_sd * 2),
            "lambda0": Prior("Normal", mu=0, sigma=y_sd * 2),
        },
    ),
)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Precisely :) Are you ok with this API, or does your original comment still stand and you're in favour of a **kwargs style argument in the SDiD model? Generally, my principle was minimising the number of places to inject this type of information, and keeping the number of **kwargs to a minimum.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This makes sense (and it is documented in the example as well :) ), maybe @drbenvincent has an opinion here.

Comment thread causalpy/experiments/synthetic_difference_in_differences.py Outdated
Comment thread causalpy/experiments/synthetic_difference_in_differences.py Outdated
Comment thread causalpy/experiments/synthetic_difference_in_differences.py Outdated
Comment thread causalpy/experiments/synthetic_difference_in_differences.py
Comment thread causalpy/experiments/synthetic_difference_in_differences.py Outdated
Comment thread docs/source/notebooks/sdid_pymc.ipynb
Comment thread docs/source/notebooks/sdid_pymc.ipynb
Comment thread docs/source/notebooks/sdid_pymc.ipynb
@juanitorduz
Copy link
Copy Markdown
Collaborator

@thomaspinder this looks great! left some minor suggestions, let me know what you think about it :)

The arkhangelsky2021synthetic entry was missing its closing brace
following the merge of main into feat/bayesian-sdid, which broke
pybtex parsing and the Sphinx/readthedocs docs build.
Copy link
Copy Markdown
Collaborator

@juanitorduz juanitorduz left a comment

Choose a reason for hiding this comment

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

great! thank you @thomaspinder ! It seems tests failures are because of other reasons, right? cc @drbenvincent

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.

4 participants