Automatically scale synth control sigma prior to the data#976
Conversation
|
Check out this pull request on See visual diffs & provide feedback on Jupyter Notebooks. Powered by ReviewNB |
|
👋 Welcome to CausalPy, @szego! 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:
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. |
Documentation build overview
151 files changed ·
|
|
Amazing, thanks for this! Very initial thought - sometimes you can get numerical issues with the exponential prior on a sigma because of so much mass on zero. So that might be a reason for the divergences. We also recently added the But I'll look forward to reviewing this properly soon. |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #976 +/- ##
==========================================
+ Coverage 95.32% 95.35% +0.02%
==========================================
Files 96 97 +1
Lines 15221 15302 +81
Branches 878 881 +3
==========================================
+ Hits 14510 14591 +81
Misses 502 502
Partials 209 209 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
|
I don't think the exponential prior leads to significantly more divergences than the current HalfNormal(1) prior. I had claude write a messy script that sweeps across seeds 1:100 for the four models in the three notebooks, comparing the results on main to the results on this PR's branch: Here are the resulting seed x model x branch divergence counts: And here are the totals across seeds:
It did produce about 2% more divergences in the sparse-Dirichlet brexit model, but when you have that many divergences you have bigger problems already. |
ReviewOverall: the change fixes a real, well-diagnosed correctness problem — the default A few things I'd like to see addressed before merge: 1. No tests (blocking)The PR adds a new public parameter and a new branch in
2. Single scalar scale across multiple treated unitsy_std = float(self.pre_design["treated"].std(dim="obs_ind", ddof=1).mean())This collapses to one scalar, then applies it to every treated unit via the 3. Edge cases on
|
|
Thank you @szego ! I just triggered our internal Bayesian bot to check the PR :) In addition, I will look at it this week as well :) |
|
I'm working on implementing the cell-specific priors as recommended by the bot but I'm getting totally different data when I run I reran the notebook on main and it produces similar results to the cell-specific exponential prior, but it'll be worth comparing the results of rerunning that notebook on the current main with the results in this branch when reviewing this. (Neither version actually fits very well.) I'll leave that notebook as-is for now but I'm happy to commit the newly-run version if you'd prefer. |
|
Ok, changed it so that it scales the prior separately to each treated unit, erroring if any have zero or undefined standard deviation. This was a good recommendation from the bot! Also added support for SoftmaxWeightedSumFitter which was actually really straightforward (kudos on your design there). |
This PR adds a feature to SyntheticControl that scales the prior on sigma to match the scale of the data.
Before, it always applied the prior
sigma ~ HalfNormal(1), which works well for unit-scale data but will generally be too tight for data on world-scale (in units of counts, dollars, etc.). The user could make the model work for their data by doing the scaling/unscaling manually - say, by standardizing the data within cells, though that does change the model - or they would need to change the prior on that sigma.We go the latter route and set a prior
sigma ~ Exponential(2/s), wheresis the pre-treatment standard deviation of the treated cells. The prior mean ofsigmais thens/2, which seems reasonable as a weak prior on the size of the residuals. We want the prior onsigmato be wide enough not to get in the way but not, like, orders of magnitude too wide.Motivation
We ran into this when developing this simulation study: https://research.getrecast.com/geolift-sim-study
We were using data in world-scale units and noticed that the confidence intervals from CausalPy were way too narrow. Here's a comparison between some CausalPy CIs to some Google Matched Markets CIs:
We realized we were probably just using CausalPy wrong, and a close read of its code lead us to discovering the
sigma ~ HalfNormal(1)prior as the probable root cause. Putting a wider prior onsigma(roughly like we do in this PR) produced CIs more like we expected:In the final version of the sim study we ended up going the standardize-the-data route and didn't change the
HalfNormal(1)prior onsigmajust to keep things simple. That also produced reasonable CIs since theHalfNormal(1)prior is fine in that regime.Changes to the notebooks
Generally the changes to the results in the notebooks are minor since the data isn't too far from unit scale in them.
One thing we did change was deleting this line from
docs/source/notebooks/geolift1.ipynb:It's true that there were no divergences during sampling in the original example, but that was only by accident. When I tried different seeds in that notebook (on main, NOT on this PR's branch), most of them produced divergences there.
On this PR's branch we do get some divergences for that seed and other seeds, but it didn't seem much better or worse than the behavior on main. Similar can be said for the other notebooks.
Alternative proposal
If you'd rather not add this feature, we've also prepared a branch that only includes changes to the docs:
main...getrecast:CausalPy:docs/synth-control-scaling-reco
It recommends either applying this prior manually or standardizing the data. Happy to open a PR for that if you'd prefer.