Bayesian synthetic control workflow#
The synthetic control method is one of the most influential innovations in causal inference for policy evaluation [Athey and Imbens, 2017]. This notebook demonstrates the full workflow — not just model fitting, but the recommended pipeline of feasibility assessment, estimation, and validation recommended by Abadie [2021], augmented with Bayesian design tools from the geo-experiment literature [Meta Incubator, 2022, Brodersen et al., 2015].
We use the California Proposition 99 dataset — the canonical example from the SC literature. In 1988, California passed Proposition 99, which imposed a 25-cent tax on cigarette packs. Abadie et al. [2010] estimated the causal effect of this policy on per-capita cigarette sales using the remaining 38 US states as a donor pool. This example is ideal for demonstrating the workflow because the treatment effect is large, sustained, and well-documented.
Before the experiment
Donor pool selection. Inspect pairwise correlations between the treated unit and candidate controls in the pre-period. Donors with weak or negative correlations should be excluded, because they force the model to extrapolate rather than interpolate, undermining the counterfactual [Abadie et al., 2010, Abadie, 2021].
Convex hull condition. Verify that the treated unit’s pre-intervention trajectory falls within the range spanned by the control units. If it does not, the weighted combination cannot reconstruct the treated series accurately and the method’s core assumption is violated [Abadie, 2021].
Donor pool quality scoring. Quantify correlation strength, convex hull coverage, and weight concentration in a single diagnostic report. This translates the visual checks above into a scored summary that flags potential problems before any experiment is run.
Dress rehearsal. Split the pre-period, inject a known synthetic effect into the held-out window, and check whether the model recovers it. A passing dress rehearsal confirms the design can detect and correctly estimate an effect of the anticipated magnitude — extending the placebo-in-time validation concept from Abadie et al. [2010].
Power analysis. Repeat the dress rehearsal across a range of effect sizes to build a Bayesian power curve. This identifies the minimum detectable effect — the smallest treatment effect the design can reliably distinguish from noise — and determines whether the experiment is worth running [Meta Incubator, 2022].
After the experiment
Model fitting. Fit the synthetic control on the full dataset, including both pre- and post-intervention observations. The model learns donor weights from the pre-period and projects the counterfactual into the post-period [Abadie et al., 2010, Abadie and Gardeazabal, 2003].
Causal impact estimation. Compare observed outcomes to the synthetic counterfactual to estimate the average and cumulative causal effect of the intervention. Posterior intervals quantify uncertainty around these estimates [Brodersen et al., 2015].
Effect summary reporting. Produce a decision-ready summary with point estimates, HDI intervals, tail probabilities, and relative effects — the information a stakeholder needs to act on the result.
After the experiment: robustness and sensitivity
Estimation alone is not enough — the SC literature strongly recommends a battery of post-estimation checks to validate the causal claims [Abadie et al., 2010, Athey and Imbens, 2017, Abadie et al., 2015].
Placebo-in-space. Reassign treatment to each control unit in turn and compare placebo effects to the actual estimate. If placebos produce effects as large as the real one, the causal claim is weakened.
Placebo-in-time. Reassign the intervention to a date in the pre-period. A well-specified model should show no effect where none occurred.
Leave-one-out robustness. Remove each donor unit in turn and re-estimate. Stable results across these perturbations indicate the estimate is not driven by a single unit.
Prior sensitivity. For Bayesian SC, vary the prior specification and check that posterior effect estimates are not dominated by the prior.
import arviz as az
import matplotlib.pyplot as plt
import numpy as np
import causalpy as cp
%load_ext autoreload
%autoreload 2
%config InlineBackend.figure_format = 'retina'
seed = 42
The autoreload extension is already loaded. To reload it, use:
%reload_ext autoreload
Load data#
We load the California Proposition 99 dataset — per-capita cigarette pack sales across 39 US states from 1970 to 2000. California (the treated unit) enacted Proposition 99 in 1988, with the tax taking effect in 1989. The 38 remaining states serve as the donor pool.
df = cp.load_data("prop99").set_index("Year")
treatment_time = 1989
treated_unit = "California"
control_units = [c for c in df.columns if c != treated_unit]
df.head()
| Alabama | Arkansas | California | Colorado | Connecticut | Delaware | Georgia | Idaho | Illinois | Indiana | ... | South Carolina | South Dakota | Tennessee | Texas | Utah | Vermont | Virginia | West Virginia | Wisconsin | Wyoming | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Year | |||||||||||||||||||||
| 1970 | 89.8 | 100.3 | 123.0 | 124.8 | 120.0 | 155.0 | 109.9 | 102.4 | 124.8 | 134.6 | ... | 103.6 | 92.7 | 99.8 | 106.4 | 65.5 | 122.6 | 124.3 | 114.5 | 106.4 | 132.2 |
| 1971 | 95.4 | 104.1 | 121.0 | 125.5 | 117.6 | 161.1 | 115.7 | 108.5 | 125.6 | 139.3 | ... | 115.0 | 96.7 | 106.3 | 108.9 | 67.7 | 124.4 | 128.4 | 111.5 | 105.4 | 131.7 |
| 1972 | 101.1 | 103.9 | 123.5 | 134.3 | 110.8 | 156.3 | 117.0 | 126.1 | 126.6 | 149.2 | ... | 118.7 | 103.0 | 111.5 | 108.6 | 71.3 | 138.0 | 137.0 | 117.5 | 108.8 | 140.0 |
| 1973 | 102.9 | 108.0 | 124.4 | 137.9 | 109.3 | 154.7 | 119.8 | 121.8 | 124.4 | 156.0 | ... | 125.5 | 103.5 | 109.7 | 110.4 | 72.7 | 146.8 | 143.1 | 116.6 | 109.5 | 141.2 |
| 1974 | 108.2 | 109.7 | 126.7 | 132.8 | 112.4 | 151.3 | 123.7 | 125.6 | 131.9 | 159.6 | ... | 129.7 | 108.4 | 114.8 | 114.7 | 75.6 | 151.8 | 149.6 | 119.9 | 111.8 | 145.8 |
5 rows × 39 columns
fig, ax = plt.subplots(figsize=(10, 5))
for state in control_units:
ax.plot(df.index, df[state], color="0.8", linewidth=0.8)
ax.plot(df.index, df[treated_unit], color="C0", linewidth=2.5, label=treated_unit)
ax.axvline(
treatment_time, color="k", linestyle="--", linewidth=1, label="Prop 99 (1989)"
)
ax.set(
xlabel="Year",
ylabel="Per-capita cigarette sales (packs)",
title="Cigarette sales across US states",
)
ax.legend(frameon=False)
fig.tight_layout()
All states share a broad downward trend in cigarette consumption from the 1970s onward, reflecting national public-health campaigns and changing social norms. California (blue) tracks the middle of the donor pack in the pre-period, confirming that a synthetic control constructed from these states is plausible. After Proposition 99 takes effect in 1989, California’s trajectory drops noticeably below the donor band — the gap between the blue line and the grey bundle is the visual signature of the treatment effect we aim to estimate.
Before the experiment: assessing the design#
Will this experiment detect the effect we care about? Before committing budget, we should assess whether the synthetic control design can plausibly recover an effect from the available donor pool. Everything in this section uses only pre-period data — no post-period observations are needed.
We begin with visual checks — inspecting donor correlations and the convex hull condition — then move to quantitative tools: donor pool quality scoring, a dress rehearsal, and a power analysis. For the quantitative tools we fit the SC model on historical data alone using SyntheticControl.from_pre_period().
Donor pool selection#
Before fitting, it is good practice to inspect pairwise correlations among units in the pre-treatment period. The donor pool should consist of units not affected by the intervention and driven by the same structural process as the treated unit [Abadie, 2021]. A poorly chosen pool forces the synthetic control to extrapolate rather than interpolate, introducing bias [Abadie et al., 2010]. Donor units that experienced similar shocks or policy changes during the study period should also be excluded [Abadie et al., 2015].
For the Proposition 99 analysis, Abadie et al. [2010] excluded states that enacted large tobacco tax increases or control programs during the study period. The dataset we use here already reflects their curated 38-state donor pool.
Tip
Beyond correlations, practitioners should verify that (a) no donor units were affected by similar interventions during the study window [Abadie et al., 2015] and (b) relevant pre-treatment covariates are also balanced, when available — careless donor pool composition is one of the most consequential choices in SC analysis, and pre-treatment outcome matching alone may be insufficient [Pickett et al., 2025].
pre = df.loc[:treatment_time]
corr, ax = cp.plot_correlations(
pre, columns=control_units + [treated_unit], figsize=(16, 14)
)
ax.set(title="Pre-treatment pairwise correlations (1970–1988)");
Most control states are positively correlated with California in the pre-period, reflecting shared national trends in declining cigarette consumption. However, a few states show negative or near-zero correlations — these units’ pre-treatment trajectories moved in the opposite direction to California’s and would force the model to assign them negative weight (which the Dirichlet prior prohibits) or effectively ignore them.
Including such units is not harmful in a strict sense — they will receive near-zero weight — but they add noise to the optimisation and can slow convergence. More importantly, Abadie [2021] recommends that the donor pool consist of units “driven by the same structural process” as the treated unit, and a negative pre-treatment correlation is evidence against that assumption.
We apply a correlation threshold of 0.0 to exclude states whose pre-treatment cigarette sales moved in the opposite direction to California’s. This is a conservative choice — it only removes clearly unsuitable donors rather than aggressively pruning the pool. A stricter threshold (e.g., 0.5) would retain only the best-matching states but risks excluding units that contribute useful information. The right threshold depends on domain knowledge and the number of donors available; with 38 candidates, we can afford to be selective without starving the model of donors.
from matplotlib.colors import Normalize
STATE_ABBREV = {
"Alabama": "AL",
"Arkansas": "AR",
"California": "CA",
"Colorado": "CO",
"Connecticut": "CT",
"Delaware": "DE",
"Georgia": "GA",
"Idaho": "ID",
"Illinois": "IL",
"Indiana": "IN",
"Iowa": "IA",
"Kansas": "KS",
"Kentucky": "KY",
"Louisiana": "LA",
"Maine": "ME",
"Minnesota": "MN",
"Mississippi": "MS",
"Missouri": "MO",
"Montana": "MT",
"Nebraska": "NE",
"Nevada": "NV",
"New Hampshire": "NH",
"New Mexico": "NM",
"North Carolina": "NC",
"North Dakota": "ND",
"Ohio": "OH",
"Oklahoma": "OK",
"Pennsylvania": "PA",
"Rhode Island": "RI",
"South Carolina": "SC_state",
"South Dakota": "SD",
"Tennessee": "TN",
"Texas": "TX",
"Utah": "UT",
"Vermont": "VT",
"Virginia": "VA",
"West Virginia": "WV",
"Wisconsin": "WI",
"Wyoming": "WY",
}
# Tile grid positions (col, row) approximating US geography.
GRID = {
"AK": (0, 0),
"ME": (10, 0),
"VT": (8, 1),
"NH": (9, 1),
"WA": (0, 2),
"ID": (1, 2),
"MT": (2, 2),
"ND": (3, 2),
"MN": (4, 2),
"WI": (5, 2),
"MI": (7, 2),
"NY": (8, 2),
"MA": (9, 2),
"RI": (10, 2),
"CT": (11, 2),
"OR": (0, 3),
"NV": (1, 3),
"WY": (2, 3),
"SD": (3, 3),
"IA": (4, 3),
"IL": (5, 3),
"IN": (6, 3),
"OH": (7, 3),
"PA": (8, 3),
"NJ": (9, 3),
"CA": (0, 4),
"UT": (1, 4),
"CO": (2, 4),
"NE": (3, 4),
"MO": (4, 4),
"KY": (5, 4),
"WV": (6, 4),
"VA": (7, 4),
"MD": (8, 4),
"DE": (9, 4),
"AZ": (1, 5),
"NM": (2, 5),
"KS": (3, 5),
"AR": (4, 5),
"TN": (5, 5),
"NC": (6, 5),
"SC": (7, 5),
"OK": (3, 6),
"LA": (4, 6),
"MS": (5, 6),
"AL": (6, 6),
"GA": (7, 6),
"HI": (0, 7),
"TX": (3, 7),
"FL": (8, 7),
}
calif_corr = corr[treated_unit].drop(treated_unit)
abbrev_corr = {
STATE_ABBREV[name]: val for name, val in calif_corr.items() if name in STATE_ABBREV
}
abbrev_corr = {("SC" if k == "SC_state" else k): v for k, v in abbrev_corr.items()}
r = 0.46
spacing = 1.0
cmap = plt.cm.RdYlGn
norm = Normalize(vmin=-0.2, vmax=1.0)
fig, ax = plt.subplots(figsize=(12, 7))
for abbr, (col, row) in GRID.items():
x = col * spacing
y = -row * spacing
if abbr == "CA":
color, ec = "C0", "C0"
label = "CA\n(treated)"
elif abbr in abbrev_corr:
color = cmap(norm(abbrev_corr[abbr]))
ec = "0.6"
label = f"{abbr}\n{abbrev_corr[abbr]:.2f}"
else:
color, ec = "0.93", "0.8"
label = abbr
circle = plt.Circle((x, y), r, facecolor=color, edgecolor=ec, linewidth=1.2)
ax.add_patch(circle)
ax.text(
x,
y,
label,
ha="center",
va="center",
fontsize=6,
fontweight="bold" if abbr == "CA" else "normal",
color="white" if abbr == "CA" else "0.15",
)
sm = plt.cm.ScalarMappable(cmap=cmap, norm=norm)
sm.set_array([])
cbar = fig.colorbar(sm, ax=ax, shrink=0.6, aspect=25, pad=0.02)
cbar.set_label("Correlation with California (pre-treatment)")
ax.set_aspect("equal")
ax.autoscale_view()
ax.axis("off")
ax.set_title(
"Pre-treatment correlation with California by state",
fontsize=14,
fontweight="bold",
pad=15,
)
fig.tight_layout()
The hex map reveals a clear spatial pattern in donor quality. States in the West and Upper Midwest tend to show the highest correlations with California — they share similar secular trends in cigarette consumption. States in the South and Deep South are weaker donors, and a few show near-zero or negative correlations. Grey hexagons are states excluded from the original Abadie et al. [2010] donor pool (they enacted tobacco legislation during the study period). This spatial view complements the heatmap above by making it easy to spot regional clusters of strong and weak donors.
correlation_threshold = 0.0
calif_corr = corr[treated_unit].drop(treated_unit)
excluded = calif_corr[calif_corr < correlation_threshold].index.tolist()
control_units = [s for s in control_units if s not in excluded]
print(f"Correlation threshold: {correlation_threshold}")
print(f"Excluded {len(excluded)} states: {excluded}")
print(f"Remaining donor pool: {len(control_units)} states")
Correlation threshold: 0.0
Excluded 4 states: ['Alabama', 'Arkansas', 'Georgia', 'Tennessee']
Remaining donor pool: 34 states
The convex hull condition#
The synthetic control method uses non-negative weights that sum to one. This means the synthetic control is a convex combination of the control units — it can only produce values within the range spanned by the controls at each time point [Abadie, 2021]. The convex hull requirement is a direct consequence of the non-negativity and sum-to-one constraints on the weights [Doudchenko and Imbens, 2016].
Note
For the method to work well, the treated unit’s pre-intervention trajectory should lie within the “envelope” of the control units. If all controls are consistently above or below the treated unit, the method cannot construct an accurate counterfactual. Abadie [2021] discusses this condition at length in §4.3.
CausalPy automatically checks this assumption when you create a SyntheticControl object and will issue a warning if violated. The donor_pool_quality() tool in the next section quantifies this condition automatically alongside other diagnostics. See the Convex hull condition glossary entry and Abadie et al. [2010] for more details. The Augmented Synthetic Control Method [Ben-Michael et al., 2021] can handle cases where this assumption is violated.
Create a SyntheticControl design object from the pre-period#
We create a design object using only the pre-intervention data (1970–1988). This object provides the design assessment methods — donor pool quality scoring, dress rehearsal, and power analysis — without requiring post-intervention data.
pre_data = df[df.index < treatment_time]
design = cp.SyntheticControl.from_pre_period(
pre_data,
control_units=control_units,
treated_units=[treated_unit],
model=cp.pymc_models.WeightedSumFitter(
sample_kwargs={
"target_accept": 0.95,
"random_seed": seed,
}
),
)
Show code cell output
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 1_000 tune and 1_000 draw iterations (4_000 + 4_000 draws total) took 6 seconds.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Is the donor pool adequate?#
Abadie [2021] recommends a structured feasibility assessment before applying the synthetic control method. donor_pool_quality() implements this assessment by aggregating three diagnostics into a scored summary: mean donor correlation, convex hull condition coverage (what fraction of pre-period time points fall within the control envelope), and weight concentration (effective number of donors via the Dirichlet weights). The weight concentration metric is motivated by the observation that SC solutions tend to be sparse — few donors receive non-zero weights — and while some sparsity is natural, extreme concentration in a single donor makes the estimate fragile to that unit’s idiosyncratic shocks [Abadie, 2021]. A pool rated “good” on all three dimensions gives confidence the SC can produce a reliable counterfactual. GeoLift [Meta Incubator, 2022] implements a parallel composite diagnostic in the frequentist setting.
quality = design.donor_pool_quality()
quality.summary()
| metric | value | assessment | |
|---|---|---|---|
| 0 | Mean donor correlation | 0.635 | acceptable |
| 1 | Convex hull coverage | 100.0% | good |
| 2 | Effective number of donors | 10.11 | good |
| 3 | Overall quality | acceptable | acceptable |
Can the design recover a known effect?#
A dress rehearsal answers: if there were a real effect of a given size, would this design detect it? This approach is conceptually a placebo-in-time test — Abadie et al. [2010] introduced the idea of reassigning the intervention to a pre-period date and checking model behaviour (§4.2), and Abadie [2021] recommends it as a standard validation exercise (§7.2). The dress rehearsal extends this concept by injecting a known effect into the held-out window, rather than testing for the absence of an effect. This simulation-based validation is the standard approach in the geo-experiment design literature — GeoLift [Meta Incubator, 2022] uses the same pattern, while Cattaneo et al. [2021] develop a related simulation-based approach for constructing prediction intervals in the SC framework.
How the effect injection works. The procedure uses only the pre-period data (1970–1988), splitting it into two parts:
The first ~75% becomes the pseudo-pre-period — the data the SC model will train on.
The remaining ~25% becomes the pseudo-post-period — the window where we simulate an intervention.
In the pseudo-post window, a known effect is injected into the treated unit’s values. The effect_type parameter controls how:
"relative"(default): each treated value is multiplied by(1 + injected_effect). Soinjected_effect=0.15turns every treated observation \(y_t\) into \(1.15 \cdot y_t\) — a 15% uplift."absolute": the value is added directly, soinjected_effect=5.0adds 5 units to each observation.
The injected truth is the total cumulative signal that was added — the sum across all pseudo-post time points of the difference between the modified and original values. For relative effects, this equals sum(original_values) × injected_effect.
A fresh SC model is then fitted on the pseudo-pre data, with the modified pseudo-post data as the “post-intervention” period. The model has no knowledge of the injected effect — it must discover it from the gap between the treated unit and its synthetic control in the pseudo-post window.
The dress rehearsal passes when the 94% HDI of the posterior cumulative impact covers the injected truth — the model can both detect and correctly estimate the effect magnitude.
Important
The dress rehearsal and the power analysis use different success criteria. The dress rehearsal asks: does the posterior recover the true effect? (HDI covers the injected truth). The power analysis below asks: does the posterior detect that any effect exists? (HDI excludes zero). A design can detect an effect exists without precisely estimating its magnitude, and vice versa.
rehearsal = design.validate_design(
injected_effect=0.15,
holdout_periods=5,
)
fig, ax = rehearsal.plot()
rehearsal.summary()
/Users/benjamv/git/CausalPy/causalpy/experiments/synthetic_control.py:129: UserWarning: Control units ['Connecticut' (r=-0.196), 'Kansas' (r=-0.211), 'Louisiana' (r=-0.126), 'Minnesota' (r=-0.201), 'Mississippi' (r=-0.195), 'North Dakota' (r=-0.155), 'Ohio' (r=-0.575), 'Oklahoma' (r=-0.230), 'Pennsylvania' (r=-0.416), 'South Carolina' (r=-0.014), 'Texas' (r=-0.405)] have pre-treatment correlation below 0.0 or undefined with treated unit 'California'. Consider excluding them from the donor pool. Use cp.plot_correlations() to inspect. See Abadie (2021) for guidance on donor pool selection.
self._check_donor_correlations()
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 1_000 tune and 1_000 draw iterations (4_000 + 4_000 draws total) took 5 seconds.
There were 2 divergences after tuning. Increase `target_accept` or reparameterize.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
| injected_effect | effect_type | injected_truth | recovered_mean | recovered_hdi_lower | recovered_hdi_upper | hdi_covers_truth | bias | relative_bias | |
|---|---|---|---|---|---|---|---|---|---|
| 0 | 0.15 | relative | 74.235 | 33.024002 | 17.945903 | 49.978769 | False | -41.210998 | -0.555142 |
How to read this plot. The dress rehearsal plot shows the posterior cumulative causal impact over the pseudo-post period. The shaded band is the 94% HDI — the range of cumulative effects the model considers plausible. The dashed line marks the injected truth, the known cumulative effect we added to the data.
Pass (HDI covers injected truth): The model can both detect and correctly estimate an effect of this magnitude. The design has sufficient sensitivity for this effect size.
Fail (HDI misses injected truth): The model’s posterior is too narrow (overconfident) or systematically biased. This may indicate that the pre-period is too short, the donor pool is inadequate, or the effect size is too small relative to noise.
The printed summary reports the recovered posterior mean, the HDI bounds, and whether the interval covers the truth. If the dress rehearsal passes, proceed to the power analysis below to map out detection rates across a range of effect sizes.
What is the minimum detectable effect size?#
The dress rehearsal tests a single effect size. A power analysis extends this across a range of candidates to produce a Bayesian power curve — for each effect size, what fraction of simulations successfully detect the effect? Simulation-based power analysis is the industry standard for SC experiment design, pioneered by GeoLift [Meta Incubator, 2022] and supported by Athey and Imbens [2017], who note the importance of supplementary simulation exercises to strengthen the credibility of identification strategies.
How it works. For each candidate effect size, multiple simulations are run. Each simulation:
Adds random noise (drawn from the fitted model’s residual distribution) to the treated unit in the pre-period, creating a realistic “what if we ran this experiment again?” scenario.
Fits a fresh SC on the noisy pre-period data.
Runs a dress rehearsal with the candidate effect size — injecting the effect into the pseudo-post window as described above.
Evaluates whether the effect was detected: does the 94% HDI of the cumulative impact exclude zero?
Note the difference from the dress rehearsal criterion: the power analysis asks whether the model can tell that any effect exists (HDI excludes zero), not whether it recovers the exact magnitude (HDI covers truth). The detection rate across simulations gives the power at that effect size. The minimum detectable effect (MDE) is the smallest effect size where the power curve crosses the conventional 80% threshold.
Unlike frequentist power (which asks “does the p-value fall below 0.05?”), Bayesian power here asks: does the 94% HDI of the cumulative impact exclude zero? This posterior-based approach to detection is a natural advantage of the Bayesian framework: Brodersen et al. [2015] demonstrate how posterior predictive distributions quantify uncertainty in the counterfactual, and Kim et al. [2020] extend this specifically to synthetic control, arguing that the Bayesian framework provides “straightforward statistical inference” while avoiding the restrictive assumptions of frequentist SC inference.
Note
We use n_simulations=20 and lighter MCMC settings to keep runtime reasonable for documentation builds. In practice, more simulations (50–100) produce smoother power curves.
fast_kwargs = {
"chains": 4,
"draws": 500,
"tune": 500,
"progressbar": False,
"target_accept": 0.95,
"random_seed": 42,
}
power_curve = design.power_analysis(
effect_sizes=np.linspace(0.05, 0.25, 5),
n_simulations=10,
sample_kwargs=fast_kwargs,
random_seed=42,
)
Show code cell output
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 500 tune and 500 draw iterations (2_000 + 2_000 draws total) took 3 seconds.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
/Users/benjamv/git/CausalPy/causalpy/experiments/synthetic_control.py:129: UserWarning: Control units ['Connecticut' (r=-0.004), 'Kansas' (r=-0.020), 'Minnesota' (r=-0.180), 'Mississippi' (r=-0.041), 'Ohio' (r=-0.249), 'Oklahoma' (r=-0.034), 'Pennsylvania' (r=-0.108), 'Texas' (r=-0.131)] have pre-treatment correlation below 0.0 or undefined with treated unit 'California'. Consider excluding them from the donor pool. Use cp.plot_correlations() to inspect. See Abadie (2021) for guidance on donor pool selection.
self._check_donor_correlations()
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 500 tune and 500 draw iterations (2_000 + 2_000 draws total) took 3 seconds.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 500 tune and 500 draw iterations (2_000 + 2_000 draws total) took 3 seconds.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
/Users/benjamv/git/CausalPy/causalpy/experiments/synthetic_control.py:129: UserWarning: Control units ['Kansas' (r=-0.001), 'Ohio' (r=-0.240), 'Pennsylvania' (r=-0.075), 'Texas' (r=-0.073)] have pre-treatment correlation below 0.0 or undefined with treated unit 'California'. Consider excluding them from the donor pool. Use cp.plot_correlations() to inspect. See Abadie (2021) for guidance on donor pool selection.
self._check_donor_correlations()
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 500 tune and 500 draw iterations (2_000 + 2_000 draws total) took 3 seconds.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 500 tune and 500 draw iterations (2_000 + 2_000 draws total) took 3 seconds.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
/Users/benjamv/git/CausalPy/causalpy/experiments/synthetic_control.py:129: UserWarning: Control units ['Kansas' (r=-0.104), 'Ohio' (r=-0.238), 'Pennsylvania' (r=-0.120), 'Texas' (r=-0.101)] have pre-treatment correlation below 0.0 or undefined with treated unit 'California'. Consider excluding them from the donor pool. Use cp.plot_correlations() to inspect. See Abadie (2021) for guidance on donor pool selection.
self._check_donor_correlations()
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 500 tune and 500 draw iterations (2_000 + 2_000 draws total) took 3 seconds.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 500 tune and 500 draw iterations (2_000 + 2_000 draws total) took 3 seconds.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
/Users/benjamv/git/CausalPy/causalpy/experiments/synthetic_control.py:129: UserWarning: Control units ['Kansas' (r=-0.041), 'Minnesota' (r=-0.086), 'Mississippi' (r=-0.157), 'Ohio' (r=-0.288), 'Oklahoma' (r=-0.067), 'Pennsylvania' (r=-0.184), 'South Carolina' (r=-0.015), 'Texas' (r=-0.173)] have pre-treatment correlation below 0.0 or undefined with treated unit 'California'. Consider excluding them from the donor pool. Use cp.plot_correlations() to inspect. See Abadie (2021) for guidance on donor pool selection.
self._check_donor_correlations()
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 500 tune and 500 draw iterations (2_000 + 2_000 draws total) took 3 seconds.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 500 tune and 500 draw iterations (2_000 + 2_000 draws total) took 3 seconds.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
/Users/benjamv/git/CausalPy/causalpy/experiments/synthetic_control.py:129: UserWarning: Control units ['Connecticut' (r=-0.115), 'Kansas' (r=-0.238), 'Louisiana' (r=-0.070), 'Minnesota' (r=-0.183), 'Mississippi' (r=-0.138), 'North Dakota' (r=-0.078), 'Ohio' (r=-0.460), 'Oklahoma' (r=-0.135), 'Pennsylvania' (r=-0.314), 'South Carolina' (r=-0.037), 'Texas' (r=-0.307)] have pre-treatment correlation below 0.0 or undefined with treated unit 'California'. Consider excluding them from the donor pool. Use cp.plot_correlations() to inspect. See Abadie (2021) for guidance on donor pool selection.
self._check_donor_correlations()
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 500 tune and 500 draw iterations (2_000 + 2_000 draws total) took 3 seconds.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 500 tune and 500 draw iterations (2_000 + 2_000 draws total) took 3 seconds.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
/Users/benjamv/git/CausalPy/causalpy/experiments/synthetic_control.py:129: UserWarning: Control units ['Connecticut' (r=-0.088), 'Ohio' (r=-0.261), 'Texas' (r=-0.003)] have pre-treatment correlation below 0.0 or undefined with treated unit 'California'. Consider excluding them from the donor pool. Use cp.plot_correlations() to inspect. See Abadie (2021) for guidance on donor pool selection.
self._check_donor_correlations()
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 500 tune and 500 draw iterations (2_000 + 2_000 draws total) took 3 seconds.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 500 tune and 500 draw iterations (2_000 + 2_000 draws total) took 3 seconds.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
/Users/benjamv/git/CausalPy/causalpy/experiments/synthetic_control.py:129: UserWarning: Control units ['Kansas' (r=-0.035), 'Ohio' (r=-0.204), 'Pennsylvania' (r=-0.111), 'Texas' (r=-0.072)] have pre-treatment correlation below 0.0 or undefined with treated unit 'California'. Consider excluding them from the donor pool. Use cp.plot_correlations() to inspect. See Abadie (2021) for guidance on donor pool selection.
self._check_donor_correlations()
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 500 tune and 500 draw iterations (2_000 + 2_000 draws total) took 3 seconds.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
/Users/benjamv/git/CausalPy/causalpy/experiments/synthetic_control.py:129: UserWarning: Control units ['Ohio' (r=-0.109)] have pre-treatment correlation below 0.0 or undefined with treated unit 'California'. Consider excluding them from the donor pool. Use cp.plot_correlations() to inspect. See Abadie (2021) for guidance on donor pool selection.
self._check_donor_correlations()
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 500 tune and 500 draw iterations (2_000 + 2_000 draws total) took 3 seconds.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
/Users/benjamv/git/CausalPy/causalpy/experiments/synthetic_control.py:129: UserWarning: Control units ['Connecticut' (r=-0.238), 'Minnesota' (r=-0.072), 'Mississippi' (r=-0.046), 'Ohio' (r=-0.499), 'Oklahoma' (r=-0.019), 'Pennsylvania' (r=-0.185), 'Texas' (r=-0.195)] have pre-treatment correlation below 0.0 or undefined with treated unit 'California'. Consider excluding them from the donor pool. Use cp.plot_correlations() to inspect. See Abadie (2021) for guidance on donor pool selection.
self._check_donor_correlations()
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 500 tune and 500 draw iterations (2_000 + 2_000 draws total) took 3 seconds.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 500 tune and 500 draw iterations (2_000 + 2_000 draws total) took 3 seconds.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
/Users/benjamv/git/CausalPy/causalpy/experiments/synthetic_control.py:129: UserWarning: Control units ['Connecticut' (r=-0.345), 'Kansas' (r=-0.127), 'Minnesota' (r=-0.115), 'Ohio' (r=-0.360), 'Oklahoma' (r=-0.012), 'Pennsylvania' (r=-0.220), 'Texas' (r=-0.181)] have pre-treatment correlation below 0.0 or undefined with treated unit 'California'. Consider excluding them from the donor pool. Use cp.plot_correlations() to inspect. See Abadie (2021) for guidance on donor pool selection.
self._check_donor_correlations()
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 500 tune and 500 draw iterations (2_000 + 2_000 draws total) took 3 seconds.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 500 tune and 500 draw iterations (2_000 + 2_000 draws total) took 3 seconds.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
/Users/benjamv/git/CausalPy/causalpy/experiments/synthetic_control.py:129: UserWarning: Control units ['Mississippi' (r=-0.001), 'Ohio' (r=-0.263), 'Pennsylvania' (r=-0.050), 'Texas' (r=-0.027)] have pre-treatment correlation below 0.0 or undefined with treated unit 'California'. Consider excluding them from the donor pool. Use cp.plot_correlations() to inspect. See Abadie (2021) for guidance on donor pool selection.
self._check_donor_correlations()
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 500 tune and 500 draw iterations (2_000 + 2_000 draws total) took 3 seconds.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 500 tune and 500 draw iterations (2_000 + 2_000 draws total) took 3 seconds.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
/Users/benjamv/git/CausalPy/causalpy/experiments/synthetic_control.py:129: UserWarning: Control units ['Kansas' (r=-0.074), 'Minnesota' (r=-0.040), 'Mississippi' (r=-0.164), 'North Dakota' (r=-0.003), 'Ohio' (r=-0.296), 'Oklahoma' (r=-0.099), 'Pennsylvania' (r=-0.151), 'South Carolina' (r=-0.060), 'Texas' (r=-0.162)] have pre-treatment correlation below 0.0 or undefined with treated unit 'California'. Consider excluding them from the donor pool. Use cp.plot_correlations() to inspect. See Abadie (2021) for guidance on donor pool selection.
self._check_donor_correlations()
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 500 tune and 500 draw iterations (2_000 + 2_000 draws total) took 3 seconds.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
/Users/benjamv/git/CausalPy/causalpy/experiments/synthetic_control.py:129: UserWarning: Control units ['Ohio' (r=-0.020)] have pre-treatment correlation below 0.0 or undefined with treated unit 'California'. Consider excluding them from the donor pool. Use cp.plot_correlations() to inspect. See Abadie (2021) for guidance on donor pool selection.
self._check_donor_correlations()
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 500 tune and 500 draw iterations (2_000 + 2_000 draws total) took 3 seconds.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
/Users/benjamv/git/CausalPy/causalpy/experiments/synthetic_control.py:129: UserWarning: Control units ['Kansas' (r=-0.052), 'Mississippi' (r=-0.024), 'Ohio' (r=-0.281), 'Pennsylvania' (r=-0.116), 'Texas' (r=-0.087)] have pre-treatment correlation below 0.0 or undefined with treated unit 'California'. Consider excluding them from the donor pool. Use cp.plot_correlations() to inspect. See Abadie (2021) for guidance on donor pool selection.
self._check_donor_correlations()
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 500 tune and 500 draw iterations (2_000 + 2_000 draws total) took 3 seconds.
There was 1 divergence after tuning. Increase `target_accept` or reparameterize.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
/Users/benjamv/git/CausalPy/causalpy/experiments/synthetic_control.py:129: UserWarning: Control units ['Ohio' (r=-0.051)] have pre-treatment correlation below 0.0 or undefined with treated unit 'California'. Consider excluding them from the donor pool. Use cp.plot_correlations() to inspect. See Abadie (2021) for guidance on donor pool selection.
self._check_donor_correlations()
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 500 tune and 500 draw iterations (2_000 + 2_000 draws total) took 3 seconds.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
/Users/benjamv/git/CausalPy/causalpy/experiments/synthetic_control.py:129: UserWarning: Control units ['Connecticut' (r=-0.113), 'Kansas' (r=-0.125), 'Louisiana' (r=-0.028), 'Minnesota' (r=-0.034), 'Mississippi' (r=-0.112), 'Ohio' (r=-0.468), 'Oklahoma' (r=-0.076), 'Pennsylvania' (r=-0.222), 'Texas' (r=-0.232)] have pre-treatment correlation below 0.0 or undefined with treated unit 'California'. Consider excluding them from the donor pool. Use cp.plot_correlations() to inspect. See Abadie (2021) for guidance on donor pool selection.
self._check_donor_correlations()
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 500 tune and 500 draw iterations (2_000 + 2_000 draws total) took 3 seconds.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
/Users/benjamv/git/CausalPy/causalpy/experiments/synthetic_control.py:129: UserWarning: Control units ['Ohio' (r=-0.121), 'South Carolina' (r=-0.025)] have pre-treatment correlation below 0.0 or undefined with treated unit 'California'. Consider excluding them from the donor pool. Use cp.plot_correlations() to inspect. See Abadie (2021) for guidance on donor pool selection.
self._check_donor_correlations()
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 500 tune and 500 draw iterations (2_000 + 2_000 draws total) took 3 seconds.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
/Users/benjamv/git/CausalPy/causalpy/experiments/synthetic_control.py:129: UserWarning: Control units ['Kansas' (r=-0.394), 'Louisiana' (r=-0.276), 'Minnesota' (r=-0.042), 'Mississippi' (r=-0.404), 'Missouri' (r=-0.122), 'North Dakota' (r=-0.300), 'Ohio' (r=-0.497), 'Oklahoma' (r=-0.352), 'Pennsylvania' (r=-0.441), 'Rhode Island' (r=-0.024), 'South Carolina' (r=-0.251), 'South Dakota' (r=-0.153), 'Texas' (r=-0.418), 'Vermont' (r=-0.206)] have pre-treatment correlation below 0.0 or undefined with treated unit 'California'. Consider excluding them from the donor pool. Use cp.plot_correlations() to inspect. See Abadie (2021) for guidance on donor pool selection.
self._check_donor_correlations()
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 500 tune and 500 draw iterations (2_000 + 2_000 draws total) took 3 seconds.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 500 tune and 500 draw iterations (2_000 + 2_000 draws total) took 3 seconds.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
/Users/benjamv/git/CausalPy/causalpy/experiments/synthetic_control.py:129: UserWarning: Control units ['Kansas' (r=-0.064), 'Mississippi' (r=-0.057), 'Ohio' (r=-0.299), 'Pennsylvania' (r=-0.175), 'Texas' (r=-0.132)] have pre-treatment correlation below 0.0 or undefined with treated unit 'California'. Consider excluding them from the donor pool. Use cp.plot_correlations() to inspect. See Abadie (2021) for guidance on donor pool selection.
self._check_donor_correlations()
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 500 tune and 500 draw iterations (2_000 + 2_000 draws total) took 3 seconds.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
/Users/benjamv/git/CausalPy/causalpy/experiments/synthetic_control.py:129: UserWarning: Control units ['Ohio' (r=-0.039)] have pre-treatment correlation below 0.0 or undefined with treated unit 'California'. Consider excluding them from the donor pool. Use cp.plot_correlations() to inspect. See Abadie (2021) for guidance on donor pool selection.
self._check_donor_correlations()
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 500 tune and 500 draw iterations (2_000 + 2_000 draws total) took 3 seconds.
The rhat statistic is larger than 1.01 for some parameters. This indicates problems during sampling. See https://arxiv.org/abs/1903.08008 for details
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
/Users/benjamv/git/CausalPy/causalpy/experiments/synthetic_control.py:129: UserWarning: Control units ['Connecticut' (r=-0.022), 'Kansas' (r=-0.177), 'Louisiana' (r=-0.021), 'Minnesota' (r=-0.074), 'Mississippi' (r=-0.137), 'North Dakota' (r=-0.036), 'Ohio' (r=-0.434), 'Oklahoma' (r=-0.141), 'Pennsylvania' (r=-0.311), 'South Carolina' (r=-0.012), 'Texas' (r=-0.271)] have pre-treatment correlation below 0.0 or undefined with treated unit 'California'. Consider excluding them from the donor pool. Use cp.plot_correlations() to inspect. See Abadie (2021) for guidance on donor pool selection.
self._check_donor_correlations()
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 500 tune and 500 draw iterations (2_000 + 2_000 draws total) took 3 seconds.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
/Users/benjamv/git/CausalPy/causalpy/experiments/synthetic_control.py:129: UserWarning: Control units ['Ohio' (r=-0.025)] have pre-treatment correlation below 0.0 or undefined with treated unit 'California'. Consider excluding them from the donor pool. Use cp.plot_correlations() to inspect. See Abadie (2021) for guidance on donor pool selection.
self._check_donor_correlations()
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 500 tune and 500 draw iterations (2_000 + 2_000 draws total) took 3 seconds.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
/Users/benjamv/git/CausalPy/causalpy/experiments/synthetic_control.py:129: UserWarning: Control units ['Connecticut' (r=-0.142), 'Minnesota' (r=-0.019), 'Ohio' (r=-0.377), 'Pennsylvania' (r=-0.132), 'Texas' (r=-0.129)] have pre-treatment correlation below 0.0 or undefined with treated unit 'California'. Consider excluding them from the donor pool. Use cp.plot_correlations() to inspect. See Abadie (2021) for guidance on donor pool selection.
self._check_donor_correlations()
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 500 tune and 500 draw iterations (2_000 + 2_000 draws total) took 3 seconds.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
/Users/benjamv/git/CausalPy/causalpy/experiments/synthetic_control.py:129: UserWarning: Control units ['Ohio' (r=-0.013)] have pre-treatment correlation below 0.0 or undefined with treated unit 'California'. Consider excluding them from the donor pool. Use cp.plot_correlations() to inspect. See Abadie (2021) for guidance on donor pool selection.
self._check_donor_correlations()
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 500 tune and 500 draw iterations (2_000 + 2_000 draws total) took 3 seconds.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
/Users/benjamv/git/CausalPy/causalpy/experiments/synthetic_control.py:129: UserWarning: Control units ['Kansas' (r=-0.351), 'Louisiana' (r=-0.190), 'Minnesota' (r=-0.108), 'Mississippi' (r=-0.322), 'Missouri' (r=-0.035), 'North Dakota' (r=-0.249), 'Ohio' (r=-0.409), 'Oklahoma' (r=-0.282), 'Pennsylvania' (r=-0.401), 'Rhode Island' (r=-0.040), 'South Carolina' (r=-0.179), 'South Dakota' (r=-0.093), 'Texas' (r=-0.369), 'Vermont' (r=-0.094)] have pre-treatment correlation below 0.0 or undefined with treated unit 'California'. Consider excluding them from the donor pool. Use cp.plot_correlations() to inspect. See Abadie (2021) for guidance on donor pool selection.
self._check_donor_correlations()
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 500 tune and 500 draw iterations (2_000 + 2_000 draws total) took 3 seconds.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 500 tune and 500 draw iterations (2_000 + 2_000 draws total) took 3 seconds.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
/Users/benjamv/git/CausalPy/causalpy/experiments/synthetic_control.py:129: UserWarning: Control units ['Kansas' (r=-0.071), 'Minnesota' (r=-0.040), 'Mississippi' (r=-0.059), 'Ohio' (r=-0.139), 'Pennsylvania' (r=-0.133), 'Texas' (r=-0.095)] have pre-treatment correlation below 0.0 or undefined with treated unit 'California'. Consider excluding them from the donor pool. Use cp.plot_correlations() to inspect. See Abadie (2021) for guidance on donor pool selection.
self._check_donor_correlations()
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 500 tune and 500 draw iterations (2_000 + 2_000 draws total) took 3 seconds.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 500 tune and 500 draw iterations (2_000 + 2_000 draws total) took 3 seconds.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
/Users/benjamv/git/CausalPy/causalpy/experiments/synthetic_control.py:129: UserWarning: Control units ['Kansas' (r=-0.115), 'Mississippi' (r=-0.054), 'North Dakota' (r=-0.025), 'Ohio' (r=-0.250), 'Oklahoma' (r=-0.040), 'Pennsylvania' (r=-0.160), 'Texas' (r=-0.115)] have pre-treatment correlation below 0.0 or undefined with treated unit 'California'. Consider excluding them from the donor pool. Use cp.plot_correlations() to inspect. See Abadie (2021) for guidance on donor pool selection.
self._check_donor_correlations()
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 500 tune and 500 draw iterations (2_000 + 2_000 draws total) took 3 seconds.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
/Users/benjamv/git/CausalPy/causalpy/experiments/synthetic_control.py:129: UserWarning: Control units ['Ohio' (r=-0.014)] have pre-treatment correlation below 0.0 or undefined with treated unit 'California'. Consider excluding them from the donor pool. Use cp.plot_correlations() to inspect. See Abadie (2021) for guidance on donor pool selection.
self._check_donor_correlations()
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 500 tune and 500 draw iterations (2_000 + 2_000 draws total) took 3 seconds.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
/Users/benjamv/git/CausalPy/causalpy/experiments/synthetic_control.py:129: UserWarning: Control units ['Kansas' (r=-0.169), 'Louisiana' (r=-0.095), 'Mississippi' (r=-0.253), 'North Dakota' (r=-0.075), 'Ohio' (r=-0.403), 'Oklahoma' (r=-0.155), 'Pennsylvania' (r=-0.275), 'South Carolina' (r=-0.068), 'Texas' (r=-0.251)] have pre-treatment correlation below 0.0 or undefined with treated unit 'California'. Consider excluding them from the donor pool. Use cp.plot_correlations() to inspect. See Abadie (2021) for guidance on donor pool selection.
self._check_donor_correlations()
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 500 tune and 500 draw iterations (2_000 + 2_000 draws total) took 3 seconds.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
/Users/benjamv/git/CausalPy/causalpy/experiments/synthetic_control.py:129: UserWarning: Control units ['Ohio' (r=-0.236), 'Pennsylvania' (r=-0.001)] have pre-treatment correlation below 0.0 or undefined with treated unit 'California'. Consider excluding them from the donor pool. Use cp.plot_correlations() to inspect. See Abadie (2021) for guidance on donor pool selection.
self._check_donor_correlations()
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 500 tune and 500 draw iterations (2_000 + 2_000 draws total) took 3 seconds.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
/Users/benjamv/git/CausalPy/causalpy/experiments/synthetic_control.py:129: UserWarning: Control units ['Connecticut' (r=-0.242), 'Kansas' (r=-0.318), 'Louisiana' (r=-0.199), 'Minnesota' (r=-0.286), 'Mississippi' (r=-0.233), 'Missouri' (r=-0.023), 'Nebraska' (r=-0.044), 'North Dakota' (r=-0.198), 'Ohio' (r=-0.632), 'Oklahoma' (r=-0.269), 'Pennsylvania' (r=-0.442), 'South Carolina' (r=-0.078), 'South Dakota' (r=-0.029), 'Texas' (r=-0.460), 'Vermont' (r=-0.010)] have pre-treatment correlation below 0.0 or undefined with treated unit 'California'. Consider excluding them from the donor pool. Use cp.plot_correlations() to inspect. See Abadie (2021) for guidance on donor pool selection.
self._check_donor_correlations()
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 500 tune and 500 draw iterations (2_000 + 2_000 draws total) took 3 seconds.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 500 tune and 500 draw iterations (2_000 + 2_000 draws total) took 3 seconds.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
/Users/benjamv/git/CausalPy/causalpy/experiments/synthetic_control.py:129: UserWarning: Control units ['Kansas' (r=-0.260), 'Louisiana' (r=-0.075), 'Mississippi' (r=-0.171), 'North Dakota' (r=-0.126), 'Ohio' (r=-0.270), 'Oklahoma' (r=-0.134), 'Pennsylvania' (r=-0.230), 'South Carolina' (r=-0.060), 'Texas' (r=-0.228), 'Vermont' (r=-0.012)] have pre-treatment correlation below 0.0 or undefined with treated unit 'California'. Consider excluding them from the donor pool. Use cp.plot_correlations() to inspect. See Abadie (2021) for guidance on donor pool selection.
self._check_donor_correlations()
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 500 tune and 500 draw iterations (2_000 + 2_000 draws total) took 3 seconds.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 500 tune and 500 draw iterations (2_000 + 2_000 draws total) took 4 seconds.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
/Users/benjamv/git/CausalPy/causalpy/experiments/synthetic_control.py:129: UserWarning: Control units ['Connecticut' (r=-0.194), 'Minnesota' (r=-0.009), 'Ohio' (r=-0.331), 'Pennsylvania' (r=-0.095), 'Texas' (r=-0.101)] have pre-treatment correlation below 0.0 or undefined with treated unit 'California'. Consider excluding them from the donor pool. Use cp.plot_correlations() to inspect. See Abadie (2021) for guidance on donor pool selection.
self._check_donor_correlations()
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 500 tune and 500 draw iterations (2_000 + 2_000 draws total) took 4 seconds.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 500 tune and 500 draw iterations (2_000 + 2_000 draws total) took 3 seconds.
There was 1 divergence after tuning. Increase `target_accept` or reparameterize.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
/Users/benjamv/git/CausalPy/causalpy/experiments/synthetic_control.py:129: UserWarning: Control units ['Connecticut' (r=-0.008), 'Kansas' (r=-0.163), 'Louisiana' (r=-0.084), 'Minnesota' (r=-0.210), 'Mississippi' (r=-0.239), 'Missouri' (r=-0.012), 'North Dakota' (r=-0.147), 'Ohio' (r=-0.321), 'Oklahoma' (r=-0.201), 'Pennsylvania' (r=-0.293), 'South Carolina' (r=-0.180), 'South Dakota' (r=-0.081), 'Texas' (r=-0.281), 'Vermont' (r=-0.063)] have pre-treatment correlation below 0.0 or undefined with treated unit 'California'. Consider excluding them from the donor pool. Use cp.plot_correlations() to inspect. See Abadie (2021) for guidance on donor pool selection.
self._check_donor_correlations()
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 500 tune and 500 draw iterations (2_000 + 2_000 draws total) took 3 seconds.
The rhat statistic is larger than 1.01 for some parameters. This indicates problems during sampling. See https://arxiv.org/abs/1903.08008 for details
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
/Users/benjamv/git/CausalPy/causalpy/experiments/synthetic_control.py:129: UserWarning: Control units ['Ohio' (r=-0.063)] have pre-treatment correlation below 0.0 or undefined with treated unit 'California'. Consider excluding them from the donor pool. Use cp.plot_correlations() to inspect. See Abadie (2021) for guidance on donor pool selection.
self._check_donor_correlations()
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 500 tune and 500 draw iterations (2_000 + 2_000 draws total) took 3 seconds.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
/Users/benjamv/git/CausalPy/causalpy/experiments/synthetic_control.py:129: UserWarning: Control units ['Connecticut' (r=-0.054), 'Kansas' (r=-0.163), 'Louisiana' (r=-0.006), 'Mississippi' (r=-0.087), 'North Dakota' (r=-0.012), 'Ohio' (r=-0.456), 'Oklahoma' (r=-0.072), 'Pennsylvania' (r=-0.266), 'Texas' (r=-0.234)] have pre-treatment correlation below 0.0 or undefined with treated unit 'California'. Consider excluding them from the donor pool. Use cp.plot_correlations() to inspect. See Abadie (2021) for guidance on donor pool selection.
self._check_donor_correlations()
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 500 tune and 500 draw iterations (2_000 + 2_000 draws total) took 3 seconds.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 500 tune and 500 draw iterations (2_000 + 2_000 draws total) took 3 seconds.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
/Users/benjamv/git/CausalPy/causalpy/experiments/synthetic_control.py:129: UserWarning: Control units ['Kansas' (r=-0.025), 'Mississippi' (r=-0.106), 'Ohio' (r=-0.269), 'Pennsylvania' (r=-0.148), 'Texas' (r=-0.089)] have pre-treatment correlation below 0.0 or undefined with treated unit 'California'. Consider excluding them from the donor pool. Use cp.plot_correlations() to inspect. See Abadie (2021) for guidance on donor pool selection.
self._check_donor_correlations()
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 500 tune and 500 draw iterations (2_000 + 2_000 draws total) took 3 seconds.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 500 tune and 500 draw iterations (2_000 + 2_000 draws total) took 3 seconds.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
/Users/benjamv/git/CausalPy/causalpy/experiments/synthetic_control.py:129: UserWarning: Control units ['Connecticut' (r=-0.122), 'Kansas' (r=-0.121), 'Louisiana' (r=-0.006), 'Minnesota' (r=-0.044), 'Mississippi' (r=-0.092), 'Ohio' (r=-0.407), 'Oklahoma' (r=-0.075), 'Pennsylvania' (r=-0.187), 'Texas' (r=-0.221)] have pre-treatment correlation below 0.0 or undefined with treated unit 'California'. Consider excluding them from the donor pool. Use cp.plot_correlations() to inspect. See Abadie (2021) for guidance on donor pool selection.
self._check_donor_correlations()
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 500 tune and 500 draw iterations (2_000 + 2_000 draws total) took 3 seconds.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
/Users/benjamv/git/CausalPy/causalpy/experiments/synthetic_control.py:129: UserWarning: Control units ['Ohio' (r=-0.048)] have pre-treatment correlation below 0.0 or undefined with treated unit 'California'. Consider excluding them from the donor pool. Use cp.plot_correlations() to inspect. See Abadie (2021) for guidance on donor pool selection.
self._check_donor_correlations()
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 500 tune and 500 draw iterations (2_000 + 2_000 draws total) took 3 seconds.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
/Users/benjamv/git/CausalPy/causalpy/experiments/synthetic_control.py:129: UserWarning: Control units ['Connecticut' (r=-0.115), 'Kansas' (r=-0.264), 'Louisiana' (r=-0.135), 'Minnesota' (r=-0.077), 'Mississippi' (r=-0.202), 'North Dakota' (r=-0.133), 'Ohio' (r=-0.487), 'Oklahoma' (r=-0.213), 'Pennsylvania' (r=-0.350), 'South Carolina' (r=-0.094), 'South Dakota' (r=-0.039), 'Texas' (r=-0.327), 'Vermont' (r=-0.040)] have pre-treatment correlation below 0.0 or undefined with treated unit 'California'. Consider excluding them from the donor pool. Use cp.plot_correlations() to inspect. See Abadie (2021) for guidance on donor pool selection.
self._check_donor_correlations()
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 500 tune and 500 draw iterations (2_000 + 2_000 draws total) took 3 seconds.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
/Users/benjamv/git/CausalPy/causalpy/experiments/synthetic_control.py:129: UserWarning: Control units ['Ohio' (r=-0.018)] have pre-treatment correlation below 0.0 or undefined with treated unit 'California'. Consider excluding them from the donor pool. Use cp.plot_correlations() to inspect. See Abadie (2021) for guidance on donor pool selection.
self._check_donor_correlations()
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 500 tune and 500 draw iterations (2_000 + 2_000 draws total) took 3 seconds.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
/Users/benjamv/git/CausalPy/causalpy/experiments/synthetic_control.py:129: UserWarning: Control units ['Connecticut' (r=-0.087), 'Iowa' (r=-0.076), 'Kansas' (r=-0.466), 'Kentucky' (r=-0.062), 'Louisiana' (r=-0.318), 'Minnesota' (r=-0.496), 'Mississippi' (r=-0.365), 'Missouri' (r=-0.168), 'Nebraska' (r=-0.213), 'North Dakota' (r=-0.356), 'Ohio' (r=-0.521), 'Oklahoma' (r=-0.403), 'Pennsylvania' (r=-0.488), 'Rhode Island' (r=-0.064), 'South Carolina' (r=-0.317), 'South Dakota' (r=-0.209), 'Texas' (r=-0.517), 'Vermont' (r=-0.227), 'Virginia' (r=-0.039)] have pre-treatment correlation below 0.0 or undefined with treated unit 'California'. Consider excluding them from the donor pool. Use cp.plot_correlations() to inspect. See Abadie (2021) for guidance on donor pool selection.
self._check_donor_correlations()
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 500 tune and 500 draw iterations (2_000 + 2_000 draws total) took 3 seconds.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
/Users/benjamv/git/CausalPy/causalpy/experiments/synthetic_control.py:129: UserWarning: Control units ['Ohio' (r=-0.035)] have pre-treatment correlation below 0.0 or undefined with treated unit 'California'. Consider excluding them from the donor pool. Use cp.plot_correlations() to inspect. See Abadie (2021) for guidance on donor pool selection.
self._check_donor_correlations()
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 500 tune and 500 draw iterations (2_000 + 2_000 draws total) took 3 seconds.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
/Users/benjamv/git/CausalPy/causalpy/experiments/synthetic_control.py:129: UserWarning: Control units ['Connecticut' (r=-0.079), 'Kansas' (r=-0.123), 'Minnesota' (r=-0.038), 'Mississippi' (r=-0.045), 'Ohio' (r=-0.409), 'Oklahoma' (r=-0.027), 'Pennsylvania' (r=-0.239), 'Texas' (r=-0.219)] have pre-treatment correlation below 0.0 or undefined with treated unit 'California'. Consider excluding them from the donor pool. Use cp.plot_correlations() to inspect. See Abadie (2021) for guidance on donor pool selection.
self._check_donor_correlations()
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 500 tune and 500 draw iterations (2_000 + 2_000 draws total) took 3 seconds.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
/Users/benjamv/git/CausalPy/causalpy/experiments/synthetic_control.py:129: UserWarning: Control units ['Ohio' (r=-0.040)] have pre-treatment correlation below 0.0 or undefined with treated unit 'California'. Consider excluding them from the donor pool. Use cp.plot_correlations() to inspect. See Abadie (2021) for guidance on donor pool selection.
self._check_donor_correlations()
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 500 tune and 500 draw iterations (2_000 + 2_000 draws total) took 3 seconds.
There were 2 divergences after tuning. Increase `target_accept` or reparameterize.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
/Users/benjamv/git/CausalPy/causalpy/experiments/synthetic_control.py:129: UserWarning: Control units ['Kansas' (r=-0.127), 'Louisiana' (r=-0.124), 'Minnesota' (r=-0.241), 'Mississippi' (r=-0.240), 'Missouri' (r=-0.032), 'North Dakota' (r=-0.122), 'Ohio' (r=-0.406), 'Oklahoma' (r=-0.181), 'Pennsylvania' (r=-0.229), 'South Carolina' (r=-0.139), 'South Dakota' (r=-0.041), 'Texas' (r=-0.283)] have pre-treatment correlation below 0.0 or undefined with treated unit 'California'. Consider excluding them from the donor pool. Use cp.plot_correlations() to inspect. See Abadie (2021) for guidance on donor pool selection.
self._check_donor_correlations()
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 500 tune and 500 draw iterations (2_000 + 2_000 draws total) took 3 seconds.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 500 tune and 500 draw iterations (2_000 + 2_000 draws total) took 3 seconds.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
/Users/benjamv/git/CausalPy/causalpy/experiments/synthetic_control.py:129: UserWarning: Control units ['Connecticut' (r=-0.047), 'Kansas' (r=-0.032), 'Ohio' (r=-0.197), 'Pennsylvania' (r=-0.065), 'Texas' (r=-0.053)] have pre-treatment correlation below 0.0 or undefined with treated unit 'California'. Consider excluding them from the donor pool. Use cp.plot_correlations() to inspect. See Abadie (2021) for guidance on donor pool selection.
self._check_donor_correlations()
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 500 tune and 500 draw iterations (2_000 + 2_000 draws total) took 3 seconds.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
/Users/benjamv/git/CausalPy/causalpy/experiments/synthetic_control.py:129: UserWarning: Control units ['Ohio' (r=-0.078)] have pre-treatment correlation below 0.0 or undefined with treated unit 'California'. Consider excluding them from the donor pool. Use cp.plot_correlations() to inspect. See Abadie (2021) for guidance on donor pool selection.
self._check_donor_correlations()
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 500 tune and 500 draw iterations (2_000 + 2_000 draws total) took 3 seconds.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
/Users/benjamv/git/CausalPy/causalpy/experiments/synthetic_control.py:129: UserWarning: Control units ['Connecticut' (r=-0.025), 'Kansas' (r=-0.118), 'Louisiana' (r=-0.106), 'Minnesota' (r=-0.063), 'Mississippi' (r=-0.207), 'North Dakota' (r=-0.137), 'Ohio' (r=-0.472), 'Oklahoma' (r=-0.170), 'Pennsylvania' (r=-0.281), 'South Carolina' (r=-0.069), 'South Dakota' (r=-0.038), 'Texas' (r=-0.279)] have pre-treatment correlation below 0.0 or undefined with treated unit 'California'. Consider excluding them from the donor pool. Use cp.plot_correlations() to inspect. See Abadie (2021) for guidance on donor pool selection.
self._check_donor_correlations()
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 500 tune and 500 draw iterations (2_000 + 2_000 draws total) took 3 seconds.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 500 tune and 500 draw iterations (2_000 + 2_000 draws total) took 3 seconds.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
/Users/benjamv/git/CausalPy/causalpy/experiments/synthetic_control.py:129: UserWarning: Control units ['Connecticut' (r=-0.052), 'Minnesota' (r=-0.051), 'Mississippi' (r=-0.018), 'Ohio' (r=-0.306), 'Pennsylvania' (r=-0.088), 'Texas' (r=-0.097)] have pre-treatment correlation below 0.0 or undefined with treated unit 'California'. Consider excluding them from the donor pool. Use cp.plot_correlations() to inspect. See Abadie (2021) for guidance on donor pool selection.
self._check_donor_correlations()
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 500 tune and 500 draw iterations (2_000 + 2_000 draws total) took 3 seconds.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 500 tune and 500 draw iterations (2_000 + 2_000 draws total) took 4 seconds.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
/Users/benjamv/git/CausalPy/causalpy/experiments/synthetic_control.py:129: UserWarning: Control units ['Connecticut' (r=-0.115)] have pre-treatment correlation below 0.0 or undefined with treated unit 'California'. Consider excluding them from the donor pool. Use cp.plot_correlations() to inspect. See Abadie (2021) for guidance on donor pool selection.
self._check_donor_correlations()
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 500 tune and 500 draw iterations (2_000 + 2_000 draws total) took 3 seconds.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
/Users/benjamv/git/CausalPy/causalpy/experiments/synthetic_control.py:129: UserWarning: Control units ['Ohio' (r=-0.139)] have pre-treatment correlation below 0.0 or undefined with treated unit 'California'. Consider excluding them from the donor pool. Use cp.plot_correlations() to inspect. See Abadie (2021) for guidance on donor pool selection.
self._check_donor_correlations()
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 500 tune and 500 draw iterations (2_000 + 2_000 draws total) took 3 seconds.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
/Users/benjamv/git/CausalPy/causalpy/experiments/synthetic_control.py:129: UserWarning: Control units ['Connecticut' (r=-0.174), 'Kansas' (r=-0.275), 'Louisiana' (r=-0.134), 'Minnesota' (r=-0.268), 'Mississippi' (r=-0.191), 'North Dakota' (r=-0.145), 'Ohio' (r=-0.544), 'Oklahoma' (r=-0.223), 'Pennsylvania' (r=-0.374), 'South Carolina' (r=-0.060), 'South Dakota' (r=-0.013), 'Texas' (r=-0.393)] have pre-treatment correlation below 0.0 or undefined with treated unit 'California'. Consider excluding them from the donor pool. Use cp.plot_correlations() to inspect. See Abadie (2021) for guidance on donor pool selection.
self._check_donor_correlations()
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 500 tune and 500 draw iterations (2_000 + 2_000 draws total) took 3 seconds.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
/Users/benjamv/git/CausalPy/causalpy/experiments/synthetic_control.py:129: UserWarning: Control units ['Ohio' (r=-0.063)] have pre-treatment correlation below 0.0 or undefined with treated unit 'California'. Consider excluding them from the donor pool. Use cp.plot_correlations() to inspect. See Abadie (2021) for guidance on donor pool selection.
self._check_donor_correlations()
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 500 tune and 500 draw iterations (2_000 + 2_000 draws total) took 3 seconds.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
/Users/benjamv/git/CausalPy/causalpy/experiments/synthetic_control.py:129: UserWarning: Control units ['Kansas' (r=-0.223), 'Louisiana' (r=-0.052), 'Mississippi' (r=-0.182), 'North Dakota' (r=-0.091), 'Ohio' (r=-0.435), 'Oklahoma' (r=-0.166), 'Pennsylvania' (r=-0.323), 'Texas' (r=-0.288)] have pre-treatment correlation below 0.0 or undefined with treated unit 'California'. Consider excluding them from the donor pool. Use cp.plot_correlations() to inspect. See Abadie (2021) for guidance on donor pool selection.
self._check_donor_correlations()
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 500 tune and 500 draw iterations (2_000 + 2_000 draws total) took 3 seconds.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 500 tune and 500 draw iterations (2_000 + 2_000 draws total) took 3 seconds.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
/Users/benjamv/git/CausalPy/causalpy/experiments/synthetic_control.py:129: UserWarning: Control units ['Connecticut' (r=-0.342), 'Ohio' (r=-0.259), 'Texas' (r=-0.006)] have pre-treatment correlation below 0.0 or undefined with treated unit 'California'. Consider excluding them from the donor pool. Use cp.plot_correlations() to inspect. See Abadie (2021) for guidance on donor pool selection.
self._check_donor_correlations()
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 500 tune and 500 draw iterations (2_000 + 2_000 draws total) took 3 seconds.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
/Users/benjamv/git/CausalPy/causalpy/experiments/synthetic_control.py:129: UserWarning: Control units ['Ohio' (r=-0.020)] have pre-treatment correlation below 0.0 or undefined with treated unit 'California'. Consider excluding them from the donor pool. Use cp.plot_correlations() to inspect. See Abadie (2021) for guidance on donor pool selection.
self._check_donor_correlations()
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 500 tune and 500 draw iterations (2_000 + 2_000 draws total) took 3 seconds.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
/Users/benjamv/git/CausalPy/causalpy/experiments/synthetic_control.py:129: UserWarning: Control units ['Connecticut' (r=-0.152), 'Minnesota' (r=-0.026), 'Mississippi' (r=-0.116), 'Ohio' (r=-0.451), 'Oklahoma' (r=-0.005), 'Pennsylvania' (r=-0.124), 'Texas' (r=-0.135)] have pre-treatment correlation below 0.0 or undefined with treated unit 'California'. Consider excluding them from the donor pool. Use cp.plot_correlations() to inspect. See Abadie (2021) for guidance on donor pool selection.
self._check_donor_correlations()
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 500 tune and 500 draw iterations (2_000 + 2_000 draws total) took 3 seconds.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 500 tune and 500 draw iterations (2_000 + 2_000 draws total) took 3 seconds.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
/Users/benjamv/git/CausalPy/causalpy/experiments/synthetic_control.py:129: UserWarning: Control units ['Kansas' (r=-0.192), 'Louisiana' (r=-0.055), 'Minnesota' (r=-0.105), 'Mississippi' (r=-0.161), 'North Dakota' (r=-0.093), 'Ohio' (r=-0.244), 'Oklahoma' (r=-0.153), 'Pennsylvania' (r=-0.173), 'South Carolina' (r=-0.112), 'South Dakota' (r=-0.021), 'Texas' (r=-0.185)] have pre-treatment correlation below 0.0 or undefined with treated unit 'California'. Consider excluding them from the donor pool. Use cp.plot_correlations() to inspect. See Abadie (2021) for guidance on donor pool selection.
self._check_donor_correlations()
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 500 tune and 500 draw iterations (2_000 + 2_000 draws total) took 3 seconds.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
/Users/benjamv/git/CausalPy/causalpy/experiments/synthetic_control.py:129: UserWarning: Control units ['Ohio' (r=-0.140)] have pre-treatment correlation below 0.0 or undefined with treated unit 'California'. Consider excluding them from the donor pool. Use cp.plot_correlations() to inspect. See Abadie (2021) for guidance on donor pool selection.
self._check_donor_correlations()
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 500 tune and 500 draw iterations (2_000 + 2_000 draws total) took 3 seconds.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
/Users/benjamv/git/CausalPy/causalpy/experiments/synthetic_control.py:129: UserWarning: Control units ['Connecticut' (r=-0.310), 'Kansas' (r=-0.232), 'Louisiana' (r=-0.084), 'Minnesota' (r=-0.191), 'Mississippi' (r=-0.071), 'North Dakota' (r=-0.059), 'Ohio' (r=-0.581), 'Oklahoma' (r=-0.135), 'Pennsylvania' (r=-0.313), 'Texas' (r=-0.341)] have pre-treatment correlation below 0.0 or undefined with treated unit 'California'. Consider excluding them from the donor pool. Use cp.plot_correlations() to inspect. See Abadie (2021) for guidance on donor pool selection.
self._check_donor_correlations()
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 500 tune and 500 draw iterations (2_000 + 2_000 draws total) took 3 seconds.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
/Users/benjamv/git/CausalPy/causalpy/experiments/synthetic_control.py:129: UserWarning: Control units ['Ohio' (r=-0.029)] have pre-treatment correlation below 0.0 or undefined with treated unit 'California'. Consider excluding them from the donor pool. Use cp.plot_correlations() to inspect. See Abadie (2021) for guidance on donor pool selection.
self._check_donor_correlations()
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 500 tune and 500 draw iterations (2_000 + 2_000 draws total) took 3 seconds.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
/Users/benjamv/git/CausalPy/causalpy/experiments/synthetic_control.py:129: UserWarning: Control units ['Kansas' (r=-0.069), 'Louisiana' (r=-0.007), 'Mississippi' (r=-0.191), 'North Dakota' (r=-0.006), 'Ohio' (r=-0.438), 'Oklahoma' (r=-0.109), 'Pennsylvania' (r=-0.243), 'South Carolina' (r=-0.003), 'Texas' (r=-0.226)] have pre-treatment correlation below 0.0 or undefined with treated unit 'California'. Consider excluding them from the donor pool. Use cp.plot_correlations() to inspect. See Abadie (2021) for guidance on donor pool selection.
self._check_donor_correlations()
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 500 tune and 500 draw iterations (2_000 + 2_000 draws total) took 3 seconds.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 500 tune and 500 draw iterations (2_000 + 2_000 draws total) took 3 seconds.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
/Users/benjamv/git/CausalPy/causalpy/experiments/synthetic_control.py:129: UserWarning: Control units ['Kansas' (r=-0.299), 'Louisiana' (r=-0.217), 'Minnesota' (r=-0.117), 'Mississippi' (r=-0.249), 'Missouri' (r=-0.055), 'Nebraska' (r=-0.071), 'North Dakota' (r=-0.277), 'Ohio' (r=-0.431), 'Oklahoma' (r=-0.280), 'Pennsylvania' (r=-0.298), 'South Carolina' (r=-0.142), 'South Dakota' (r=-0.088), 'Texas' (r=-0.355), 'Vermont' (r=-0.142)] have pre-treatment correlation below 0.0 or undefined with treated unit 'California'. Consider excluding them from the donor pool. Use cp.plot_correlations() to inspect. See Abadie (2021) for guidance on donor pool selection.
self._check_donor_correlations()
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 500 tune and 500 draw iterations (2_000 + 2_000 draws total) took 3 seconds.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 500 tune and 500 draw iterations (2_000 + 2_000 draws total) took 3 seconds.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
/Users/benjamv/git/CausalPy/causalpy/experiments/synthetic_control.py:129: UserWarning: Control units ['Kansas' (r=-0.170), 'Minnesota' (r=-0.058), 'Ohio' (r=-0.262), 'Oklahoma' (r=-0.038), 'Pennsylvania' (r=-0.149), 'Texas' (r=-0.150)] have pre-treatment correlation below 0.0 or undefined with treated unit 'California'. Consider excluding them from the donor pool. Use cp.plot_correlations() to inspect. See Abadie (2021) for guidance on donor pool selection.
self._check_donor_correlations()
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 500 tune and 500 draw iterations (2_000 + 2_000 draws total) took 3 seconds.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 500 tune and 500 draw iterations (2_000 + 2_000 draws total) took 3 seconds.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
/Users/benjamv/git/CausalPy/causalpy/experiments/synthetic_control.py:129: UserWarning: Control units ['Kansas' (r=-0.271), 'Louisiana' (r=-0.073), 'Minnesota' (r=-0.132), 'Mississippi' (r=-0.180), 'North Dakota' (r=-0.150), 'Ohio' (r=-0.316), 'Oklahoma' (r=-0.165), 'Pennsylvania' (r=-0.297), 'South Carolina' (r=-0.061), 'Texas' (r=-0.257), 'Vermont' (r=-0.011)] have pre-treatment correlation below 0.0 or undefined with treated unit 'California'. Consider excluding them from the donor pool. Use cp.plot_correlations() to inspect. See Abadie (2021) for guidance on donor pool selection.
self._check_donor_correlations()
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 500 tune and 500 draw iterations (2_000 + 2_000 draws total) took 3 seconds.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 500 tune and 500 draw iterations (2_000 + 2_000 draws total) took 3 seconds.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
/Users/benjamv/git/CausalPy/causalpy/experiments/synthetic_control.py:129: UserWarning: Control units ['Connecticut' (r=-0.156), 'Ohio' (r=-0.220)] have pre-treatment correlation below 0.0 or undefined with treated unit 'California'. Consider excluding them from the donor pool. Use cp.plot_correlations() to inspect. See Abadie (2021) for guidance on donor pool selection.
self._check_donor_correlations()
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 500 tune and 500 draw iterations (2_000 + 2_000 draws total) took 3 seconds.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 500 tune and 500 draw iterations (2_000 + 2_000 draws total) took 3 seconds.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
/Users/benjamv/git/CausalPy/causalpy/experiments/synthetic_control.py:129: UserWarning: Control units ['Kansas' (r=-0.046), 'Minnesota' (r=-0.051), 'Ohio' (r=-0.107), 'Pennsylvania' (r=-0.100), 'Texas' (r=-0.038)] have pre-treatment correlation below 0.0 or undefined with treated unit 'California'. Consider excluding them from the donor pool. Use cp.plot_correlations() to inspect. See Abadie (2021) for guidance on donor pool selection.
self._check_donor_correlations()
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 500 tune and 500 draw iterations (2_000 + 2_000 draws total) took 3 seconds.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
/Users/benjamv/git/CausalPy/causalpy/experiments/synthetic_control.py:129: UserWarning: Control units ['Ohio' (r=-0.101)] have pre-treatment correlation below 0.0 or undefined with treated unit 'California'. Consider excluding them from the donor pool. Use cp.plot_correlations() to inspect. See Abadie (2021) for guidance on donor pool selection.
self._check_donor_correlations()
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 500 tune and 500 draw iterations (2_000 + 2_000 draws total) took 3 seconds.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
/Users/benjamv/git/CausalPy/causalpy/experiments/synthetic_control.py:129: UserWarning: Control units ['Connecticut' (r=-0.313), 'Iowa' (r=-0.007), 'Kansas' (r=-0.213), 'Louisiana' (r=-0.163), 'Minnesota' (r=-0.343), 'Mississippi' (r=-0.192), 'Missouri' (r=-0.019), 'Nebraska' (r=-0.046), 'North Dakota' (r=-0.113), 'Ohio' (r=-0.575), 'Oklahoma' (r=-0.197), 'Pennsylvania' (r=-0.310), 'South Carolina' (r=-0.096), 'South Dakota' (r=-0.025), 'Texas' (r=-0.368)] have pre-treatment correlation below 0.0 or undefined with treated unit 'California'. Consider excluding them from the donor pool. Use cp.plot_correlations() to inspect. See Abadie (2021) for guidance on donor pool selection.
self._check_donor_correlations()
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 500 tune and 500 draw iterations (2_000 + 2_000 draws total) took 3 seconds.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 500 tune and 500 draw iterations (2_000 + 2_000 draws total) took 3 seconds.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
/Users/benjamv/git/CausalPy/causalpy/experiments/synthetic_control.py:129: UserWarning: Control units ['Kansas' (r=-0.221), 'Minnesota' (r=-0.089), 'Mississippi' (r=-0.102), 'North Dakota' (r=-0.056), 'Ohio' (r=-0.255), 'Oklahoma' (r=-0.088), 'Pennsylvania' (r=-0.237), 'South Carolina' (r=-0.011), 'Texas' (r=-0.196)] have pre-treatment correlation below 0.0 or undefined with treated unit 'California'. Consider excluding them from the donor pool. Use cp.plot_correlations() to inspect. See Abadie (2021) for guidance on donor pool selection.
self._check_donor_correlations()
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 500 tune and 500 draw iterations (2_000 + 2_000 draws total) took 3 seconds.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
fig, ax = power_curve.plot()
power_curve.summary()
| effect_size | detection_rate | mean_recovery | median_recovery | n_simulations | |
|---|---|---|---|---|---|
| 0 | 0.05 | 0.8 | -17.073743 | -16.870934 | 10 |
| 1 | 0.10 | 0.2 | 3.042276 | 3.700733 | 10 |
| 2 | 0.15 | 1.0 | 29.604917 | 27.537678 | 10 |
| 3 | 0.20 | 1.0 | 46.571889 | 46.765516 | 10 |
| 4 | 0.25 | 1.0 | 55.050144 | 56.175586 | 10 |
Abadie [2021] recommends an explicit go/no-go assessment before applying the synthetic control method. The power curve shows the probability of detecting an effect at each candidate size. If the curve crosses the 80% threshold at or below the effect size you expect (or the smallest effect that would justify the cost of the intervention), the design has adequate power and you can proceed with confidence.
If the curve stays below 80% at your target effect size, the experiment is underpowered. Common remedies include:
Extending the pre-period to give the model more data for learning donor weights. Ferman [2021] shows that the asymptotic properties of the SC estimator improve as the number of pre-treatment periods grows.
Adding donors that are more strongly correlated with the treated unit.
Lengthening the planned post-period, which accumulates a larger cumulative impact and makes detection easier.
For the Proposition 99 data, the effect of the tobacco tax on California’s cigarette sales was substantial — roughly a 25–30% decline relative to the synthetic control by 2000 [Abadie et al., 2010]. The power curve should confirm that this design can reliably detect effects well below that magnitude.
Tip
Before examining post-treatment data, document and commit to the SC specification — donor pool, matching variables, and pre-treatment period — as a form of pre-analysis plan. Ferman et al. [2020] show that the discretion inherent in choosing SC specifications creates “substantial room for specification searching,” and pre-commitment mitigates this risk.
TODO — Power curve calibration at effect size zero
The power analysis currently exhibits inflated Type I error rates when effect_size=0 is included. The hdi_excludes_zero criterion detects a “significant” effect in nearly all simulations even when no effect is injected, because the SC model’s out-of-sample prediction bias in the pseudo-post period produces a systematic non-zero cumulative impact.
Root cause: validate_design splits the pre-period into pseudo-pre (~75%) and pseudo-post (~25%). Even with zero injected effect, the SC trained on the truncated pseudo-pre systematically mis-predicts the pseudo-post — particularly on data with strong secular trends like Prop 99. The hdi_excludes_zero criterion flags this bias as a detection.
Planned fix (follow-up PR): Calibrate the detection criterion against a null distribution estimated from effect_size=0 simulations, so the test asks whether the effect is distinguishable from the null rather than from zero. Alternatively, use a permutation-based approach or bias-corrected threshold.
For now, effect_size=0 is excluded from the demo effect_sizes list above to avoid misleading output.
After the experiment: estimating the causal effect#
The design assessment above would be done before running the experiment. Having confirmed the design is adequate, we now analyze the results after the intervention period. We fit the model on the full dataset — including both pre- and post-intervention observations — following the core SC estimation approach introduced by Abadie and Gardeazabal [2003] and Abadie et al. [2010]. The Bayesian formulation used here draws on Kim et al. [2020] for the prior structure and Brodersen et al. [2015] for the posterior predictive approach to causal impact.
For the Proposition 99 analysis, we expect to see California’s cigarette consumption diverge downward from its synthetic counterpart after the 1989 tax increase.
result = cp.SyntheticControl(
df,
treatment_time,
control_units=control_units,
treated_units=[treated_unit],
model=cp.pymc_models.WeightedSumFitter(
sample_kwargs={"target_accept": 0.95, "random_seed": seed}
),
)
Show code cell output
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 1_000 tune and 1_000 draw iterations (4_000 + 4_000 draws total) took 7 seconds.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
fig, ax = result.plot(plot_predictors=True)
result.summary()
================================SyntheticControl================================
Control units: ['Colorado', 'Connecticut', 'Delaware', 'Idaho', 'Illinois', 'Indiana', 'Iowa', 'Kansas', 'Kentucky', 'Louisiana', 'Maine', 'Minnesota', 'Mississippi', 'Missouri', 'Montana', 'Nebraska', 'Nevada', 'New Hampshire', 'New Mexico', 'North Carolina', 'North Dakota', 'Ohio', 'Oklahoma', 'Pennsylvania', 'Rhode Island', 'South Carolina', 'South Dakota', 'Texas', 'Utah', 'Vermont', 'Virginia', 'West Virginia', 'Wisconsin', 'Wyoming']
Treated unit: California
Model coefficients:
Colorado 0.037, 94% HDI [0.0012, 0.12]
Connecticut 0.026, 94% HDI [0.00096, 0.086]
Delaware 0.016, 94% HDI [0.00054, 0.055]
Idaho 0.051, 94% HDI [0.0021, 0.17]
Illinois 0.021, 94% HDI [0.0008, 0.071]
Indiana 0.014, 94% HDI [0.00038, 0.05]
Iowa 0.019, 94% HDI [0.0006, 0.067]
Kansas 0.015, 94% HDI [0.00058, 0.051]
Kentucky 0.0061, 94% HDI [0.00017, 0.022]
Louisiana 0.012, 94% HDI [0.00032, 0.043]
Maine 0.016, 94% HDI [0.0005, 0.055]
Minnesota 0.019, 94% HDI [0.00056, 0.063]
Mississippi 0.012, 94% HDI [0.00034, 0.042]
Missouri 0.012, 94% HDI [0.00032, 0.042]
Montana 0.046, 94% HDI [0.002, 0.15]
Nebraska 0.025, 94% HDI [0.001, 0.087]
Nevada 0.086, 94% HDI [0.006, 0.18]
New Hampshire 0.028, 94% HDI [0.0011, 0.082]
New Mexico 0.056, 94% HDI [0.0019, 0.2]
North Carolina 0.023, 94% HDI [0.00071, 0.073]
North Dakota 0.017, 94% HDI [0.00047, 0.062]
Ohio 0.013, 94% HDI [0.00032, 0.049]
Oklahoma 0.011, 94% HDI [0.00045, 0.04]
Pennsylvania 0.014, 94% HDI [0.00049, 0.049]
Rhode Island 0.011, 94% HDI [0.00029, 0.037]
South Carolina 0.01, 94% HDI [0.0003, 0.035]
South Dakota 0.019, 94% HDI [0.00054, 0.063]
Texas 0.015, 94% HDI [0.00047, 0.053]
Utah 0.27, 94% HDI [0.13, 0.4]
Vermont 0.0089, 94% HDI [0.00026, 0.031]
Virginia 0.011, 94% HDI [0.00038, 0.039]
West Virginia 0.021, 94% HDI [0.00048, 0.072]
Wisconsin 0.02, 94% HDI [0.00052, 0.072]
Wyoming 0.014, 94% HDI [0.00042, 0.048]
y_hat_sigma 3.5, 94% HDI [2.6, 4.6]
Pre-treatment correlation (California): 0.9358
We can get nicely formatted tables from our integration with the maketables package.
from maketables import ETable
result.set_maketables_options(hdi_prob=0.95)
ETable(result, coef_fmt="b:.3f \n [ci95l:.3f, ci95u:.3f]")
| y | |
|---|---|
| (1) | |
| coef | |
| Colorado | 0.037 [0.000, 0.110] |
| Connecticut | 0.026 [0.000, 0.075] |
| Delaware | 0.016 [0.000, 0.048] |
| Idaho | 0.051 [0.000, 0.148] |
| Illinois | 0.021 [0.000, 0.061] |
| Indiana | 0.014 [0.000, 0.042] |
| Iowa | 0.019 [0.000, 0.058] |
| Kansas | 0.015 [0.000, 0.044] |
| Kentucky | 0.006 [0.000, 0.019] |
| Louisiana | 0.012 [0.000, 0.037] |
| Maine | 0.016 [0.000, 0.046] |
| Minnesota | 0.019 [0.000, 0.054] |
| Mississippi | 0.012 [0.000, 0.035] |
| Missouri | 0.012 [0.000, 0.036] |
| Montana | 0.046 [0.000, 0.136] |
| Nebraska | 0.025 [0.000, 0.073] |
| Nevada | 0.086 [0.000, 0.171] |
| New Hampshire | 0.028 [0.000, 0.074] |
| New Mexico | 0.056 [0.000, 0.167] |
| North Carolina | 0.023 [0.000, 0.064] |
| North Dakota | 0.017 [0.000, 0.052] |
| Ohio | 0.013 [0.000, 0.041] |
| Oklahoma | 0.011 [0.000, 0.033] |
| Pennsylvania | 0.014 [0.000, 0.042] |
| Rhode Island | 0.011 [0.000, 0.032] |
| South Carolina | 0.010 [0.000, 0.029] |
| South Dakota | 0.019 [0.000, 0.054] |
| Texas | 0.015 [0.000, 0.046] |
| Utah | 0.272 [0.131, 0.410] |
| Vermont | 0.009 [0.000, 0.026] |
| Virginia | 0.011 [0.000, 0.033] |
| West Virginia | 0.021 [0.000, 0.063] |
| Wisconsin | 0.020 [0.000, 0.063] |
| Wyoming | 0.014 [0.000, 0.042] |
| stats | |
| N | 31 |
| Bayesian R2 | 0.816 |
| Format of coefficient cell: Coefficient [95% CI Lower, 95% CI Upper] | |
As well as the model coefficients, we might be interested in the average causal impact and average cumulative causal impact. The posterior predictive approach to pointwise and cumulative effects follows Brodersen et al. [2015], while Cattaneo et al. [2021] provide the theoretical foundation for prediction intervals in the SC framework.
az.summary(result.post_impact.mean("obs_ind"))
| mean | sd | hdi_3% | hdi_97% | mcse_mean | mcse_sd | ess_bulk | ess_tail | r_hat | |
|---|---|---|---|---|---|---|---|---|---|
| x[California] | -25.046 | 1.669 | -28.195 | -21.987 | 0.033 | 0.021 | 2575.0 | 3420.0 | 1.0 |
Warning
Care must be taken with the mean impact statistic. It only makes sense to use this statistic if it looks like the intervention had a lasting (and roughly constant) effect on the outcome variable. If the effect is transient, then clearly there will be a lot of post-intervention period where the impact of the intervention has ‘worn off’. If so, then it will be hard to interpret the mean impacts real meaning.
We can also ask for the summary statistics of the cumulative causal impact.
# get index of the final time point
index = result.post_impact_cumulative.obs_ind.max()
# grab the posterior distribution of the cumulative impact at this final time point
last_cumulative_estimate = result.post_impact_cumulative.sel({"obs_ind": index})
# get summary stats
az.summary(last_cumulative_estimate)
| mean | sd | hdi_3% | hdi_97% | mcse_mean | mcse_sd | ess_bulk | ess_tail | r_hat | |
|---|---|---|---|---|---|---|---|---|---|
| x[California] | -300.554 | 20.032 | -338.343 | -263.839 | 0.397 | 0.257 | 2575.0 | 3420.0 | 1.0 |
Decision-ready effect summary#
For decision-making, you often need a concise summary of the causal effect with key statistics. Brodersen et al. [2015] produce exactly this type of summary — posterior probability of a causal effect, cumulative impact with credible intervals, and relative effect sizes — and Abadie [2021] recommends reporting effect estimates alongside measures of uncertainty and significance. The effect_summary() method provides this decision-ready report with average and cumulative effects, HDI intervals, tail probabilities, and relative effects.
stats = result.effect_summary(treated_unit=treated_unit)
stats.table
| mean | median | hdi_lower | hdi_upper | p_gt_0 | relative_mean | relative_hdi_lower | relative_hdi_upper | |
|---|---|---|---|---|---|---|---|---|
| average | -25.046197 | -24.947792 | -28.476571 | -21.966703 | 0.0 | -29.302557 | -32.058618 | -26.685596 |
| cumulative | -300.554362 | -299.373499 | -341.718848 | -263.600432 | 0.0 | -29.302557 | -32.058618 | -26.685596 |
print(stats.text)
During the Post-period (1989 to 2000), the response variable had an average value of approx. 60.35. By contrast, in the absence of an intervention, we would have expected an average response of 85.40. The 95% interval of this counterfactual prediction is [82.32, 88.83]. Subtracting this prediction from the observed response yields an estimate of the causal effect the intervention had on the response variable. This effect is -25.05 with a 95% interval of [-28.48, -21.97].
Summing up the individual data points during the Post-period, the response variable had an overall value of 724.20. By contrast, had the intervention not taken place, we would have expected a sum of 1024.75. The 95% interval of this prediction is [987.80, 1065.92].
The 95% HDI of the effect [-28.48, -21.97] does not include zero. The posterior probability of a decrease is 1.000. Relative to the counterfactual, the effect represents a -29.30% change (95% HDI [-32.06%, -26.69%]).
This analysis assumes that the control units used to construct the synthetic counterfactual were not themselves affected by the intervention, and that the pre-treatment relationship between control and treated units remains stable throughout the post-treatment period. We recommend inspecting model fit, examining pre-intervention trends, and conducting sensitivity analyses (e.g., placebo tests) to support any causal conclusions drawn from this analysis.
You can customize the summary in several ways:
Window: Analyze a specific time period instead of the full post-period
Direction: Specify whether you’re testing for an increase, decrease, or two-sided effect
Options: Include/exclude cumulative or relative effects
post_indices = result.datapost.index
window_start = post_indices[0]
window_end = post_indices[len(post_indices) // 2]
stats_windowed = result.effect_summary(
window=(window_start, window_end),
treated_unit=treated_unit,
direction="two-sided",
cumulative=True,
relative=True,
)
stats_windowed.table
| mean | median | hdi_lower | hdi_upper | p_two_sided | prob_of_effect | relative_mean | relative_hdi_lower | relative_hdi_upper | |
|---|---|---|---|---|---|---|---|---|---|
| average | -20.457235 | -20.369337 | -23.580421 | -17.706657 | 0.0 | 1.0 | -23.149689 | -25.810578 | -20.716502 |
| cumulative | -143.200645 | -142.585358 | -165.062949 | -123.946601 | 0.0 | 1.0 | -23.149689 | -25.810578 | -20.716502 |
print(stats_windowed.text)
During the Post-period (1989 to 1995), the response variable had an average value of approx. 67.83. By contrast, in the absence of an intervention, we would have expected an average response of 88.29. The 95% interval of this counterfactual prediction is [85.54, 91.41]. Subtracting this prediction from the observed response yields an estimate of the causal effect the intervention had on the response variable. This effect is -20.46 with a 95% interval of [-23.58, -17.71].
Summing up the individual data points during the Post-period, the response variable had an overall value of 474.80. By contrast, had the intervention not taken place, we would have expected a sum of 618.00. The 95% interval of this prediction is [598.75, 639.86].
The 95% HDI of the effect [-23.58, -17.71] does not include zero. The posterior probability of an effect is 1.000. Relative to the counterfactual, the effect represents a -23.15% change (95% HDI [-25.81%, -20.72%]).
This analysis assumes that the control units used to construct the synthetic counterfactual were not themselves affected by the intervention, and that the pre-treatment relationship between control and treated units remains stable throughout the post-treatment period. We recommend inspecting model fit, examining pre-intervention trends, and conducting sensitivity analyses (e.g., placebo tests) to support any causal conclusions drawn from this analysis.
After the experiment: robustness and sensitivity#
Estimation alone does not establish a causal claim. The SC literature strongly recommends a battery of post-estimation robustness checks to probe the reliability of the effect estimate from different angles [Abadie et al., 2010, Athey and Imbens, 2017, Abadie et al., 2015]. If the estimated effect survives these checks, we can be more confident it reflects a genuine causal effect rather than an artefact of the particular model specification, donor pool composition, or prior assumptions.
The four checks below cover the primary threats to SC validity:
Placebo-in-space — does the effect stand out relative to placebo effects estimated for control units?
Placebo-in-time — does the model show a spurious effect when the intervention is reassigned to a pre-period date?
Leave-one-out — is the effect driven by a single donor unit?
Prior sensitivity — are the posterior conclusions dominated by the Bayesian prior?
CausalPy implements all four as Check objects that can be run individually or together via SensitivityAnalysis.
from causalpy.pipeline import PipelineContext
context = PipelineContext(
data=df,
experiment=result,
experiment_config={
"method": cp.SyntheticControl,
"treatment_time": treatment_time,
"control_units": control_units,
"treated_units": [treated_unit],
"model": cp.pymc_models.WeightedSumFitter(
sample_kwargs={"target_accept": 0.95, "random_seed": seed}
),
},
)
Placebo-in-space#
The placebo-in-space test is the primary inference tool for synthetic control, introduced by Abadie et al. [2010] and further developed by Abadie et al. [2015]. The idea is simple: reassign treatment to each control state in turn, fit a separate SC for each using the remaining states as donors, and compare the resulting placebo effects to the actual estimated effect for California. If placebo effects are frequently as large as the real one, the estimated treatment effect is not distinguishable from noise, and the causal claim is weakened.
Ferman and Pinto [2017] analyse the statistical properties of this permutation-based inference approach and show that while it can have size distortions in small donor pools, it remains the most widely used inference tool for SC. With 38 donor states, this test is well-powered. Firpo and Possebom [2018] extend the framework by allowing non-uniform treatment assignment probabilities and constructing formal confidence sets.
Note
With 38 control states, this step fits 38 separate SC models — one per placebo assignment. This may take a few minutes.
from causalpy.checks import PlaceboInSpace
pis_check = PlaceboInSpace()
pis_result = pis_check.run(result, context)
Show code cell output
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 1_000 tune and 1_000 draw iterations (4_000 + 4_000 draws total) took 5 seconds.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 1_000 tune and 1_000 draw iterations (4_000 + 4_000 draws total) took 4 seconds.
There were 2 divergences after tuning. Increase `target_accept` or reparameterize.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
/Users/benjamv/git/CausalPy/causalpy/experiments/synthetic_control.py:129: UserWarning: Control units ['South Carolina' (r=-0.069)] have pre-treatment correlation below 0.0 or undefined with treated unit 'Delaware'. Consider excluding them from the donor pool. Use cp.plot_correlations() to inspect. See Abadie (2021) for guidance on donor pool selection.
self._check_donor_correlations()
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 1_000 tune and 1_000 draw iterations (4_000 + 4_000 draws total) took 4 seconds.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 1_000 tune and 1_000 draw iterations (4_000 + 4_000 draws total) took 6 seconds.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 1_000 tune and 1_000 draw iterations (4_000 + 4_000 draws total) took 9 seconds.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
/Users/benjamv/git/CausalPy/causalpy/experiments/synthetic_control.py:129: UserWarning: Control units ['Ohio' (r=-0.022)] have pre-treatment correlation below 0.0 or undefined with treated unit 'Indiana'. Consider excluding them from the donor pool. Use cp.plot_correlations() to inspect. See Abadie (2021) for guidance on donor pool selection.
self._check_donor_correlations()
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 1_000 tune and 1_000 draw iterations (4_000 + 4_000 draws total) took 5 seconds.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 1_000 tune and 1_000 draw iterations (4_000 + 4_000 draws total) took 10 seconds.
There was 1 divergence after tuning. Increase `target_accept` or reparameterize.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 1_000 tune and 1_000 draw iterations (4_000 + 4_000 draws total) took 7 seconds.
There was 1 divergence after tuning. Increase `target_accept` or reparameterize.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 1_000 tune and 1_000 draw iterations (4_000 + 4_000 draws total) took 5 seconds.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 1_000 tune and 1_000 draw iterations (4_000 + 4_000 draws total) took 25 seconds.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 1_000 tune and 1_000 draw iterations (4_000 + 4_000 draws total) took 7 seconds.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 1_000 tune and 1_000 draw iterations (4_000 + 4_000 draws total) took 7 seconds.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 1_000 tune and 1_000 draw iterations (4_000 + 4_000 draws total) took 7 seconds.
There were 2 divergences after tuning. Increase `target_accept` or reparameterize.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 1_000 tune and 1_000 draw iterations (4_000 + 4_000 draws total) took 7 seconds.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 1_000 tune and 1_000 draw iterations (4_000 + 4_000 draws total) took 11 seconds.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 1_000 tune and 1_000 draw iterations (4_000 + 4_000 draws total) took 11 seconds.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
/Users/benjamv/git/CausalPy/causalpy/experiments/synthetic_control.py:129: UserWarning: Control units ['Ohio' (r=-0.259), 'Pennsylvania' (r=-0.040)] have pre-treatment correlation below 0.0 or undefined with treated unit 'Nevada'. Consider excluding them from the donor pool. Use cp.plot_correlations() to inspect. See Abadie (2021) for guidance on donor pool selection.
self._check_donor_correlations()
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 1_000 tune and 1_000 draw iterations (4_000 + 4_000 draws total) took 7 seconds.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
/Users/benjamv/git/CausalPy/causalpy/experiments/synthetic_control.py:129: UserWarning: Control units ['Ohio' (r=-0.113)] have pre-treatment correlation below 0.0 or undefined with treated unit 'New Hampshire'. Consider excluding them from the donor pool. Use cp.plot_correlations() to inspect. See Abadie (2021) for guidance on donor pool selection.
self._check_donor_correlations()
/Users/benjamv/git/CausalPy/causalpy/experiments/synthetic_control.py:130: UserWarning: Convex hull assumption may be violated: 19 pre-intervention time points (100.0% above, 0.0% below control range). The synthetic control method requires the treated unit to lie within the convex hull of control units. Consider: (1) adding more diverse control units, (2) using a model with an intercept (e.g., ITS with control predictors), or (3) using the Augmented Synthetic Control Method. See glossary term 'Convex hull condition' for more details.
self._check_convex_hull()
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 1_000 tune and 1_000 draw iterations (4_000 + 4_000 draws total) took 5 seconds.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 1_000 tune and 1_000 draw iterations (4_000 + 4_000 draws total) took 12 seconds.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
/Users/benjamv/git/CausalPy/causalpy/experiments/synthetic_control.py:129: UserWarning: Control units ['Ohio' (r=-0.194)] have pre-treatment correlation below 0.0 or undefined with treated unit 'North Carolina'. Consider excluding them from the donor pool. Use cp.plot_correlations() to inspect. See Abadie (2021) for guidance on donor pool selection.
self._check_donor_correlations()
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 1_000 tune and 1_000 draw iterations (4_000 + 4_000 draws total) took 8 seconds.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 1_000 tune and 1_000 draw iterations (4_000 + 4_000 draws total) took 11 seconds.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
/Users/benjamv/git/CausalPy/causalpy/experiments/synthetic_control.py:129: UserWarning: Control units ['Indiana' (r=-0.022), 'Nevada' (r=-0.259), 'New Hampshire' (r=-0.113), 'North Carolina' (r=-0.194)] have pre-treatment correlation below 0.0 or undefined with treated unit 'Ohio'. Consider excluding them from the donor pool. Use cp.plot_correlations() to inspect. See Abadie (2021) for guidance on donor pool selection.
self._check_donor_correlations()
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 1_000 tune and 1_000 draw iterations (4_000 + 4_000 draws total) took 6 seconds.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 1_000 tune and 1_000 draw iterations (4_000 + 4_000 draws total) took 9 seconds.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
/Users/benjamv/git/CausalPy/causalpy/experiments/synthetic_control.py:129: UserWarning: Control units ['Nevada' (r=-0.040)] have pre-treatment correlation below 0.0 or undefined with treated unit 'Pennsylvania'. Consider excluding them from the donor pool. Use cp.plot_correlations() to inspect. See Abadie (2021) for guidance on donor pool selection.
self._check_donor_correlations()
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 1_000 tune and 1_000 draw iterations (4_000 + 4_000 draws total) took 7 seconds.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 1_000 tune and 1_000 draw iterations (4_000 + 4_000 draws total) took 7 seconds.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
/Users/benjamv/git/CausalPy/causalpy/experiments/synthetic_control.py:129: UserWarning: Control units ['Delaware' (r=-0.069)] have pre-treatment correlation below 0.0 or undefined with treated unit 'South Carolina'. Consider excluding them from the donor pool. Use cp.plot_correlations() to inspect. See Abadie (2021) for guidance on donor pool selection.
self._check_donor_correlations()
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 1_000 tune and 1_000 draw iterations (4_000 + 4_000 draws total) took 7 seconds.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 1_000 tune and 1_000 draw iterations (4_000 + 4_000 draws total) took 13 seconds.
There was 1 divergence after tuning. Increase `target_accept` or reparameterize.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 1_000 tune and 1_000 draw iterations (4_000 + 4_000 draws total) took 8 seconds.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
/Users/benjamv/git/CausalPy/causalpy/experiments/synthetic_control.py:130: UserWarning: Convex hull assumption may be violated: 19 pre-intervention time points (0.0% above, 100.0% below control range). The synthetic control method requires the treated unit to lie within the convex hull of control units. Consider: (1) adding more diverse control units, (2) using a model with an intercept (e.g., ITS with control predictors), or (3) using the Augmented Synthetic Control Method. See glossary term 'Convex hull condition' for more details.
self._check_convex_hull()
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 1_000 tune and 1_000 draw iterations (4_000 + 4_000 draws total) took 5 seconds.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 1_000 tune and 1_000 draw iterations (4_000 + 4_000 draws total) took 8 seconds.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 1_000 tune and 1_000 draw iterations (4_000 + 4_000 draws total) took 7 seconds.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 1_000 tune and 1_000 draw iterations (4_000 + 4_000 draws total) took 7 seconds.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 1_000 tune and 1_000 draw iterations (4_000 + 4_000 draws total) took 7 seconds.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 1_000 tune and 1_000 draw iterations (4_000 + 4_000 draws total) took 8 seconds.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
print(pis_result.text)
Placebo-in-space analysis: tested 34 control units as placebo treated units. If placebo effects are comparable to the actual effect, the causal claim may be weakened.
pis_result.table
| placebo_treated | mean | median | hdi_lower | hdi_upper | p_gt_0 | relative_mean | relative_hdi_lower | relative_hdi_upper | |
|---|---|---|---|---|---|---|---|---|---|
| 0 | Colorado | -8.761844 | -8.689679 | -11.879127 | -5.572271 | 0.00000 | -9.350384 | -12.304137 | -6.175018 |
| 1 | Connecticut | -8.286354 | -8.290813 | -11.547998 | -5.170888 | 0.00000 | -9.272240 | -12.505999 | -6.015279 |
| 2 | Delaware | 17.651012 | 17.654377 | 13.907836 | 21.381022 | 1.00000 | 15.979231 | 12.152720 | 19.988059 |
| 3 | Idaho | -2.780553 | -2.695781 | -5.501251 | 0.119385 | 0.01675 | -3.307795 | -6.394232 | 0.148463 |
| 4 | Illinois | -10.853481 | -10.830870 | -14.038510 | -7.682190 | 0.00000 | -11.121718 | -13.962159 | -8.156012 |
| 5 | Indiana | 20.125799 | 20.174513 | 15.865568 | 24.449870 | 1.00000 | 18.202384 | 13.459851 | 22.624310 |
| 6 | Iowa | 4.840890 | 4.878850 | 2.050720 | 7.593272 | 0.99925 | 5.479689 | 2.239406 | 8.826130 |
| 7 | Kansas | -3.452821 | -3.488697 | -6.759627 | -0.045779 | 0.02550 | -3.694433 | -7.044853 | -0.051300 |
| 8 | Kentucky | 41.087645 | 41.083607 | 38.550447 | 43.722041 | 1.00000 | 31.404839 | 28.893068 | 34.090473 |
| 9 | Louisiana | 3.448703 | 3.414249 | 0.335759 | 6.833821 | 0.98425 | 3.427555 | 0.081112 | 6.718730 |
| 10 | Maine | -1.987834 | -2.001598 | -6.126344 | 1.828529 | 0.16225 | -1.840753 | -5.577015 | 1.780854 |
| 11 | Minnesota | -2.737246 | -2.763369 | -5.709552 | 0.314105 | 0.04375 | -3.125338 | -6.365669 | 0.375412 |
| 12 | Mississippi | 13.254756 | 13.229911 | 9.741872 | 16.470388 | 1.00000 | 14.491424 | 10.228604 | 18.607886 |
| 13 | Missouri | 12.361738 | 12.175457 | 8.615787 | 16.772850 | 1.00000 | 11.477268 | 7.702349 | 16.174061 |
| 14 | Montana | 0.108398 | 0.162789 | -2.684968 | 2.807512 | 0.54575 | 0.153506 | -3.037525 | 3.386591 |
| 15 | Nebraska | 3.788183 | 3.839221 | 1.252018 | 6.047240 | 0.99800 | 4.542323 | 1.449968 | 7.415125 |
| 16 | Nevada | -13.209028 | -13.133760 | -16.054412 | -10.724916 | 0.00000 | -10.789982 | -12.829392 | -8.951734 |
| 17 | New Hampshire | 4.108155 | 3.319345 | -5.962667 | 15.524885 | 0.72075 | 2.817328 | -3.747333 | 10.734300 |
| 18 | New Mexico | -5.505141 | -5.483924 | -7.055660 | -3.743583 | 0.00000 | -7.665026 | -9.837297 | -5.574814 |
| 19 | North Carolina | -4.617267 | -4.519063 | -8.756934 | -0.672170 | 0.00775 | -3.525738 | -6.655077 | -0.669093 |
| 20 | North Dakota | -6.871578 | -6.841577 | -9.719792 | -4.102625 | 0.00000 | -7.961905 | -10.950193 | -4.942468 |
| 21 | Ohio | 10.117904 | 10.056235 | 6.233450 | 14.344047 | 1.00000 | 10.218166 | 6.029947 | 15.057109 |
| 22 | Oklahoma | 1.080442 | 1.052213 | -2.338920 | 4.882307 | 0.71800 | 1.113112 | -2.268156 | 5.050776 |
| 23 | Pennsylvania | 4.381832 | 4.374230 | 0.925832 | 7.889715 | 0.99425 | 4.832282 | 0.975596 | 8.972184 |
| 24 | Rhode Island | -17.649688 | -17.683636 | -21.556777 | -13.744362 | 0.00000 | -15.653410 | -18.509215 | -12.649812 |
| 25 | South Carolina | 13.172381 | 13.155467 | 9.154770 | 17.404066 | 1.00000 | 12.990486 | 8.652611 | 17.840394 |
| 26 | South Dakota | 7.272269 | 7.276138 | 5.024237 | 9.597664 | 1.00000 | 8.870048 | 5.950483 | 12.018011 |
| 27 | Texas | -14.759844 | -14.747590 | -17.711450 | -11.943025 | 0.00000 | -15.986833 | -18.613631 | -13.361376 |
| 28 | Utah | -22.037183 | -21.961318 | -24.895172 | -19.336956 | 0.00000 | -29.846004 | -32.488134 | -27.208214 |
| 29 | Vermont | -9.745612 | -9.793855 | -12.812335 | -6.895730 | 0.00000 | -8.048417 | -10.337753 | -5.842812 |
| 30 | Virginia | -9.271649 | -9.422393 | -12.639599 | -5.568476 | 0.00000 | -7.894657 | -10.491650 | -4.910394 |
| 31 | West Virginia | 16.952162 | 16.980495 | 13.886350 | 19.860122 | 1.00000 | 18.607760 | 14.720984 | 22.477251 |
| 32 | Wisconsin | 5.455508 | 5.417406 | 2.798174 | 8.357459 | 1.00000 | 6.359794 | 3.149554 | 10.034866 |
| 33 | Wyoming | -3.378491 | -3.335420 | -8.031928 | 1.334383 | 0.08550 | -3.022941 | -7.178264 | 1.069842 |
How to read this table. Each row represents a placebo experiment where one control state (placebo_treated) was assigned as the “treated” unit and a SC was fitted using the remaining states as donors. The mean column shows the estimated average causal impact for that placebo, and hdi_lower/hdi_upper give the credible interval.
Compare these placebo effects to the actual estimated effect for California. A strong causal claim requires the actual effect to stand out clearly — ideally larger in magnitude than all or most of the placebos. If several placebo states show effects comparable to the actual treatment, the estimated effect may not be distinguishable from the variation that arises naturally across states, and the causal interpretation is weakened.
A useful rule of thumb from Abadie et al. [2010]: compute the ratio of the post/pre root mean squared prediction error (RMSPE) for the actual treated unit and each placebo. The treated unit’s ratio should be an outlier relative to the placebo distribution.
Show code cell source
fig, ax = PlaceboInSpace.plot(
pis_result, baseline_stats=stats, treated_label="California (treated)"
)
Placebo-in-time#
The placebo-in-time test reassigns the intervention to a date in the pre-period and checks whether the model finds a spurious effect. If the SC model is well specified, it should show no effect where none occurred. Abadie et al. [2010] introduced this test (§4.2) and it has become a standard robustness check in applied SC work [Abadie, 2021, Abadie et al., 2015].
With 19 pre-treatment years (1970–1988), there is enough room to test multiple pseudo-treatment dates.
from causalpy.checks import PlaceboInTime
def sc_factory(data, pseudo_treatment_time):
return cp.SyntheticControl(
data,
pseudo_treatment_time,
control_units=control_units,
treated_units=[treated_unit],
model=cp.pymc_models.WeightedSumFitter(
sample_kwargs={"target_accept": 0.95, "random_seed": seed}
),
)
pit_check = PlaceboInTime(n_folds=3, experiment_factory=sc_factory)
pit_result = pit_check.run(result)
Fold 1 has only 0 observations (minimum 3), skipping.
/Users/benjamv/git/CausalPy/causalpy/experiments/synthetic_control.py:129: UserWarning: Control units ['Colorado' (r=undefined, likely constant), 'Connecticut' (r=undefined, likely constant), 'Delaware' (r=undefined, likely constant), 'Idaho' (r=undefined, likely constant), 'Illinois' (r=undefined, likely constant), 'Indiana' (r=undefined, likely constant), 'Iowa' (r=undefined, likely constant), 'Kansas' (r=undefined, likely constant), 'Kentucky' (r=undefined, likely constant), 'Louisiana' (r=undefined, likely constant), 'Maine' (r=undefined, likely constant), 'Minnesota' (r=undefined, likely constant), 'Mississippi' (r=undefined, likely constant), 'Missouri' (r=undefined, likely constant), 'Montana' (r=undefined, likely constant), 'Nebraska' (r=undefined, likely constant), 'Nevada' (r=undefined, likely constant), 'New Hampshire' (r=undefined, likely constant), 'New Mexico' (r=undefined, likely constant), 'North Carolina' (r=undefined, likely constant), 'North Dakota' (r=undefined, likely constant), 'Ohio' (r=undefined, likely constant), 'Oklahoma' (r=undefined, likely constant), 'Pennsylvania' (r=undefined, likely constant), 'Rhode Island' (r=undefined, likely constant), 'South Carolina' (r=undefined, likely constant), 'South Dakota' (r=undefined, likely constant), 'Texas' (r=undefined, likely constant), 'Utah' (r=undefined, likely constant), 'Vermont' (r=undefined, likely constant), 'Virginia' (r=undefined, likely constant), 'West Virginia' (r=undefined, likely constant), 'Wisconsin' (r=undefined, likely constant), 'Wyoming' (r=undefined, likely constant)] have pre-treatment correlation below 0.0 or undefined with treated unit 'California'. Consider excluding them from the donor pool. Use cp.plot_correlations() to inspect. See Abadie (2021) for guidance on donor pool selection.
self._check_donor_correlations()
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 1_000 tune and 1_000 draw iterations (4_000 + 4_000 draws total) took 3 seconds.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Fold 2 failed to fit (pseudo_treatment_time=1967), skipping.
Traceback (most recent call last):
File "/Users/benjamv/git/CausalPy/causalpy/checks/placebo_in_time.py", line 647, in run
fold_experiment = factory(fold_data, pseudo_tt)
File "/var/folders/r0/nf1kgxsx6zx3rw16xc3wnnzr0000gn/T/ipykernel_18692/1364124629.py", line 5, in sc_factory
return cp.SyntheticControl(
~~~~~~~~~~~~~~~~~~~^
data,
^^^^^
...<5 lines>...
),
^^
)
^
File "/Users/benjamv/git/CausalPy/causalpy/experiments/synthetic_control.py", line 131, in __init__
self.algorithm()
~~~~~~~~~~~~~~^^
File "/Users/benjamv/git/CausalPy/causalpy/experiments/synthetic_control.py", line 368, in algorithm
self.score = self.model.score(
~~~~~~~~~~~~~~~~^
X=self.datapre_control,
^^^^^^^^^^^^^^^^^^^^^^^
y=self.datapre_treated,
^^^^^^^^^^^^^^^^^^^^^^^
)
^
File "/Users/benjamv/git/CausalPy/causalpy/pymc_models.py", line 380, in score
mu_data = az.extract(mu, group="posterior_predictive", var_names="mu")
File "/Users/benjamv/miniforge3/envs/CausalPy/lib/python3.13/site-packages/arviz/data/utils.py", line 123, in extract
data = data.stack(sample=("chain", "draw"))
File "/Users/benjamv/miniforge3/envs/CausalPy/lib/python3.13/site-packages/xarray/util/deprecation_helpers.py", line 144, in wrapper
return func(*args, **kwargs)
File "/Users/benjamv/miniforge3/envs/CausalPy/lib/python3.13/site-packages/xarray/core/dataarray.py", line 2986, in stack
ds = self._to_temp_dataset().stack(
dim,
...<2 lines>...
**dim_kwargs,
)
File "/Users/benjamv/miniforge3/envs/CausalPy/lib/python3.13/site-packages/xarray/util/deprecation_helpers.py", line 144, in wrapper
return func(*args, **kwargs)
File "/Users/benjamv/miniforge3/envs/CausalPy/lib/python3.13/site-packages/xarray/core/dataset.py", line 5293, in stack
result = result._stack_once(dims, new_dim, index_cls, create_index)
File "/Users/benjamv/miniforge3/envs/CausalPy/lib/python3.13/site-packages/xarray/core/dataset.py", line 5208, in _stack_once
stacked_var = exp_var.stack({new_dim: dims})
File "/Users/benjamv/miniforge3/envs/CausalPy/lib/python3.13/site-packages/xarray/util/deprecation_helpers.py", line 144, in wrapper
return func(*args, **kwargs)
File "/Users/benjamv/miniforge3/envs/CausalPy/lib/python3.13/site-packages/xarray/core/variable.py", line 1548, in stack
result = result._stack_once(dims, new_dim)
File "/Users/benjamv/miniforge3/envs/CausalPy/lib/python3.13/site-packages/xarray/core/variable.py", line 1511, in _stack_once
new_data = duck_array_ops.reshape(reordered.data, new_shape)
File "/Users/benjamv/miniforge3/envs/CausalPy/lib/python3.13/site-packages/xarray/core/duck_array_ops.py", line 460, in reshape
return xp.reshape(array, shape)
~~~~~~~~~~^^^^^^^^^^^^^^
File "/Users/benjamv/miniforge3/envs/CausalPy/lib/python3.13/site-packages/numpy/_core/fromnumeric.py", line 324, in reshape
return _wrapfunc(a, 'reshape', shape, order=order)
File "/Users/benjamv/miniforge3/envs/CausalPy/lib/python3.13/site-packages/numpy/_core/fromnumeric.py", line 57, in _wrapfunc
return bound(*args, **kwds)
ValueError: cannot reshape array of size 0 into shape (0,1,newaxis)
/Users/benjamv/git/CausalPy/causalpy/experiments/synthetic_control.py:129: UserWarning: Control units ['Connecticut' (r=-0.398), 'Delaware' (r=-0.851)] have pre-treatment correlation below 0.0 or undefined with treated unit 'California'. Consider excluding them from the donor pool. Use cp.plot_correlations() to inspect. See Abadie (2021) for guidance on donor pool selection.
self._check_donor_correlations()
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 1_000 tune and 1_000 draw iterations (4_000 + 4_000 draws total) took 6 seconds.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [mu_status_quo, tau_status_quo, fold_z]
Sampling 4 chains for 1_000 tune and 1_000 draw iterations (4_000 + 4_000 draws total) took 1 seconds.
Sampling: [theta_new]
How to read this output. The placebo-in-time check reports several key pieces of information:
Fold summaries. Each fold shifts the treatment time backward into the pre-period. The
meanandsdfor each fold represent the posterior cumulative impact when no real intervention occurred — these are the “null” effects. They should be close to zero if the SC model fits the pre-period well.Hierarchical status-quo model. The
muandtauparameters characterise the learned null distribution:muis the average placebo effect (ideally near zero) andtauis the between-fold variability.P(actual outside null). The key statistic — what fraction of null-distribution draws are smaller in magnitude than the actual estimated effect. A high value (above the threshold, typically 0.95) means the actual effect is clearly distinguishable from the null, supporting the causal claim. A low value means the actual effect looks similar to what arises from noise alone.
SUPPORTED / NOT SUPPORTED. The overall verdict based on whether the probability exceeds the threshold.
Show code cell source
fig, ax = PlaceboInTime.plot(pit_result)
Leave-one-out robustness#
The leave-one-out test removes each donor state in turn and re-estimates the SC, checking whether the effect estimate remains stable. Abadie et al. [2010] and Abadie et al. [2015] recommend this as a standard robustness check. If the estimated effect changes dramatically when a single state is removed, the result is fragile — it depends on one state’s idiosyncratic trajectory rather than the weighted combination. This test is the empirical complement to the weight concentration metric from the design phase: a high concentration score warns that LOO sensitivity is likely.
Note
With 38 donors, CausalPy only drops donors that received non-negligible weight in the original fit — states with near-zero weight have no effect on the synthetic control and don’t need testing.
from causalpy.checks import LeaveOneOut
loo_check = LeaveOneOut()
loo_result = loo_check.run(result, context)
Show code cell output
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 1_000 tune and 1_000 draw iterations (4_000 + 4_000 draws total) took 5 seconds.
There was 1 divergence after tuning. Increase `target_accept` or reparameterize.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 1_000 tune and 1_000 draw iterations (4_000 + 4_000 draws total) took 5 seconds.
There were 3 divergences after tuning. Increase `target_accept` or reparameterize.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 1_000 tune and 1_000 draw iterations (4_000 + 4_000 draws total) took 6 seconds.
There were 3 divergences after tuning. Increase `target_accept` or reparameterize.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 1_000 tune and 1_000 draw iterations (4_000 + 4_000 draws total) took 6 seconds.
There was 1 divergence after tuning. Increase `target_accept` or reparameterize.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 1_000 tune and 1_000 draw iterations (4_000 + 4_000 draws total) took 6 seconds.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 1_000 tune and 1_000 draw iterations (4_000 + 4_000 draws total) took 6 seconds.
There was 1 divergence after tuning. Increase `target_accept` or reparameterize.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 1_000 tune and 1_000 draw iterations (4_000 + 4_000 draws total) took 6 seconds.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 1_000 tune and 1_000 draw iterations (4_000 + 4_000 draws total) took 7 seconds.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 1_000 tune and 1_000 draw iterations (4_000 + 4_000 draws total) took 7 seconds.
There were 2 divergences after tuning. Increase `target_accept` or reparameterize.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 1_000 tune and 1_000 draw iterations (4_000 + 4_000 draws total) took 6 seconds.
There were 4 divergences after tuning. Increase `target_accept` or reparameterize.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 1_000 tune and 1_000 draw iterations (4_000 + 4_000 draws total) took 7 seconds.
There were 4 divergences after tuning. Increase `target_accept` or reparameterize.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 1_000 tune and 1_000 draw iterations (4_000 + 4_000 draws total) took 6 seconds.
There was 1 divergence after tuning. Increase `target_accept` or reparameterize.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 1_000 tune and 1_000 draw iterations (4_000 + 4_000 draws total) took 7 seconds.
There were 3 divergences after tuning. Increase `target_accept` or reparameterize.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 1_000 tune and 1_000 draw iterations (4_000 + 4_000 draws total) took 7 seconds.
There were 2 divergences after tuning. Increase `target_accept` or reparameterize.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 1_000 tune and 1_000 draw iterations (4_000 + 4_000 draws total) took 7 seconds.
There were 2 divergences after tuning. Increase `target_accept` or reparameterize.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 1_000 tune and 1_000 draw iterations (4_000 + 4_000 draws total) took 6 seconds.
There was 1 divergence after tuning. Increase `target_accept` or reparameterize.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 1_000 tune and 1_000 draw iterations (4_000 + 4_000 draws total) took 7 seconds.
There were 2 divergences after tuning. Increase `target_accept` or reparameterize.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 1_000 tune and 1_000 draw iterations (4_000 + 4_000 draws total) took 6 seconds.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 1_000 tune and 1_000 draw iterations (4_000 + 4_000 draws total) took 6 seconds.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 1_000 tune and 1_000 draw iterations (4_000 + 4_000 draws total) took 6 seconds.
There were 4 divergences after tuning. Increase `target_accept` or reparameterize.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 1_000 tune and 1_000 draw iterations (4_000 + 4_000 draws total) took 7 seconds.
There were 3 divergences after tuning. Increase `target_accept` or reparameterize.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 1_000 tune and 1_000 draw iterations (4_000 + 4_000 draws total) took 7 seconds.
There was 1 divergence after tuning. Increase `target_accept` or reparameterize.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 1_000 tune and 1_000 draw iterations (4_000 + 4_000 draws total) took 7 seconds.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 1_000 tune and 1_000 draw iterations (4_000 + 4_000 draws total) took 7 seconds.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 1_000 tune and 1_000 draw iterations (4_000 + 4_000 draws total) took 7 seconds.
There were 2 divergences after tuning. Increase `target_accept` or reparameterize.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 1_000 tune and 1_000 draw iterations (4_000 + 4_000 draws total) took 7 seconds.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 1_000 tune and 1_000 draw iterations (4_000 + 4_000 draws total) took 7 seconds.
There was 1 divergence after tuning. Increase `target_accept` or reparameterize.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 1_000 tune and 1_000 draw iterations (4_000 + 4_000 draws total) took 7 seconds.
There were 3 divergences after tuning. Increase `target_accept` or reparameterize.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 1_000 tune and 1_000 draw iterations (4_000 + 4_000 draws total) took 5 seconds.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 1_000 tune and 1_000 draw iterations (4_000 + 4_000 draws total) took 6 seconds.
There was 1 divergence after tuning. Increase `target_accept` or reparameterize.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 1_000 tune and 1_000 draw iterations (4_000 + 4_000 draws total) took 7 seconds.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 1_000 tune and 1_000 draw iterations (4_000 + 4_000 draws total) took 7 seconds.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 1_000 tune and 1_000 draw iterations (4_000 + 4_000 draws total) took 7 seconds.
There were 3 divergences after tuning. Increase `target_accept` or reparameterize.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 1_000 tune and 1_000 draw iterations (4_000 + 4_000 draws total) took 7 seconds.
There were 22 divergences after tuning. Increase `target_accept` or reparameterize.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
print(loo_result.text)
Leave-one-out analysis: dropped each of 34 control units. Examine the table for consistency of effect estimates.
loo_result.table
| dropped_unit | mean | median | hdi_lower | hdi_upper | p_gt_0 | relative_mean | relative_hdi_lower | relative_hdi_upper | |
|---|---|---|---|---|---|---|---|---|---|
| 0 | Colorado | -25.018891 | -24.909925 | -28.460470 | -22.057018 | 0.0 | -29.279775 | -32.046300 | -26.765946 |
| 1 | Connecticut | -24.996941 | -24.903499 | -28.302298 | -21.872308 | 0.0 | -29.262008 | -31.990229 | -26.669627 |
| 2 | Delaware | -24.521341 | -24.442129 | -27.570488 | -21.341097 | 0.0 | -28.866982 | -31.358434 | -26.124141 |
| 3 | Idaho | -24.753012 | -24.703932 | -27.925901 | -21.408945 | 0.0 | -29.059062 | -31.874733 | -26.460429 |
| 4 | Illinois | -25.009931 | -24.902270 | -28.316730 | -22.071859 | 0.0 | -29.273937 | -31.958674 | -26.803417 |
| 5 | Indiana | -24.576643 | -24.512754 | -27.677669 | -21.604112 | 0.0 | -28.913955 | -31.442011 | -26.361230 |
| 6 | Iowa | -24.692408 | -24.604468 | -27.745882 | -21.550289 | 0.0 | -29.009970 | -31.561590 | -26.384117 |
| 7 | Kansas | -24.913787 | -24.819691 | -28.138510 | -21.741221 | 0.0 | -29.193319 | -31.799055 | -26.484222 |
| 8 | Kentucky | -24.699889 | -24.626906 | -27.692408 | -21.539564 | 0.0 | -29.016571 | -31.453488 | -26.303185 |
| 9 | Louisiana | -24.806771 | -24.714739 | -28.156323 | -21.836803 | 0.0 | -29.104559 | -31.895690 | -26.656046 |
| 10 | Maine | -24.831274 | -24.750236 | -27.943428 | -21.695508 | 0.0 | -29.124882 | -31.654434 | -26.450007 |
| 11 | Minnesota | -24.852474 | -24.760346 | -28.121380 | -21.939072 | 0.0 | -29.142747 | -31.785850 | -26.660979 |
| 12 | Mississippi | -24.674063 | -24.586880 | -27.872268 | -21.647532 | 0.0 | -28.994131 | -31.735640 | -26.552626 |
| 13 | Missouri | -24.544555 | -24.410601 | -27.849198 | -21.630178 | 0.0 | -28.886087 | -31.575342 | -26.384644 |
| 14 | Montana | -24.737365 | -24.636958 | -28.183308 | -21.585035 | 0.0 | -29.044648 | -31.884140 | -26.395366 |
| 15 | Nebraska | -24.830630 | -24.738231 | -28.096360 | -21.508527 | 0.0 | -29.123156 | -32.076271 | -26.593136 |
| 16 | Nevada | -26.497329 | -26.394366 | -29.875225 | -23.574010 | 0.0 | -30.485249 | -33.111832 | -28.089709 |
| 17 | New Hampshire | -25.229211 | -25.131544 | -28.897207 | -21.991507 | 0.0 | -29.450280 | -32.464453 | -26.800233 |
| 18 | New Mexico | -25.070639 | -24.948138 | -28.622120 | -21.977832 | 0.0 | -29.320936 | -32.169763 | -26.695507 |
| 19 | North Carolina | -24.820099 | -24.686903 | -28.141959 | -21.603896 | 0.0 | -29.114018 | -31.801713 | -26.361036 |
| 20 | North Dakota | -24.946255 | -24.846892 | -28.174837 | -21.860763 | 0.0 | -29.220918 | -31.973148 | -26.755795 |
| 21 | Ohio | -24.644863 | -24.504880 | -27.890407 | -21.766517 | 0.0 | -28.970134 | -31.607297 | -26.506868 |
| 22 | Oklahoma | -24.830578 | -24.728318 | -27.990126 | -21.769768 | 0.0 | -29.124361 | -31.684499 | -26.509778 |
| 23 | Pennsylvania | -24.783906 | -24.684439 | -28.098231 | -21.843470 | 0.0 | -29.085503 | -31.767996 | -26.575675 |
| 24 | Rhode Island | -24.952726 | -24.870130 | -28.273064 | -21.842592 | 0.0 | -29.225114 | -31.902603 | -26.574891 |
| 25 | South Carolina | -24.707224 | -24.614700 | -27.833410 | -21.666864 | 0.0 | -29.021220 | -31.698840 | -26.564266 |
| 26 | South Dakota | -24.704447 | -24.659946 | -27.873576 | -21.640057 | 0.0 | -29.019927 | -31.594249 | -26.393513 |
| 27 | Texas | -24.999439 | -24.915462 | -28.279389 | -21.986283 | 0.0 | -29.264616 | -31.907462 | -26.703031 |
| 28 | Utah | -26.729732 | -26.695510 | -30.077684 | -23.467381 | 0.0 | -30.669334 | -33.389181 | -28.136983 |
| 29 | Vermont | -24.842653 | -24.769752 | -28.059841 | -21.799595 | 0.0 | -29.135093 | -31.782332 | -26.585943 |
| 30 | Virginia | -24.808938 | -24.738285 | -28.019640 | -21.667039 | 0.0 | -29.106540 | -31.913996 | -26.646994 |
| 31 | West Virginia | -24.352560 | -24.268431 | -27.468262 | -21.542406 | 0.0 | -28.727719 | -31.278531 | -26.305744 |
| 32 | Wisconsin | -24.658915 | -24.555231 | -27.968222 | -21.646692 | 0.0 | -28.980978 | -31.667555 | -26.399470 |
| 33 | Wyoming | -24.832200 | -24.729708 | -28.297419 | -21.988267 | 0.0 | -29.125699 | -31.928855 | -26.712680 |
How to read this table. Each row shows the effect estimate after dropping a single donor state from the pool. The dropped_unit column identifies which state was excluded; the remaining columns show the causal impact summary from the re-estimated SC. Look for two things:
Stability of point estimates. If the
meancolumn is broadly consistent across rows, the result is not driven by any single donor. Large swings when a particular state is dropped indicate fragility.Consistency of credible intervals. If one row’s HDI includes zero while the others exclude it, the causal claim depends critically on that donor — a red flag.
Donors whose removal causes the largest shifts are typically those receiving the highest weight in the original SC fit. This connects back to the weight concentration metric from the design phase: a donor pool with more evenly distributed weights is less vulnerable to LOO perturbations.
Show code cell source
fig, ax = LeaveOneOut.plot(loo_result, baseline_stats=stats)
Prior sensitivity#
For Bayesian SC, it is important to check that the posterior effect estimates are not unduly driven by the prior specification. Kim et al. [2020] discuss prior specification for Bayesian synthetic control and recommend checking sensitivity to prior choices — a standard practice in Bayesian inference [Gelman et al., 2013]. The PriorSensitivity check re-fits the model with alternative prior specifications and compares the resulting effect summaries. If conclusions shift substantially under different priors, the data may not be informative enough to support the causal claim independently of the prior.
import numpy as np
from pymc_extras.prior import Prior
from causalpy.checks import PriorSensitivity
n_donors = len(control_units)
prior_check = PriorSensitivity(
alternatives=[
{
"name": "diffuse (conc=0.5)",
"model": cp.pymc_models.WeightedSumFitter(
priors={
"beta": Prior(
"Dirichlet",
a=np.ones(n_donors) * 0.5,
dims=["treated_units", "coeffs"],
),
},
sample_kwargs={"target_accept": 0.95, "random_seed": seed},
),
},
{
"name": "concentrated (conc=10)",
"model": cp.pymc_models.WeightedSumFitter(
priors={
"beta": Prior(
"Dirichlet",
a=np.ones(n_donors) * 10,
dims=["treated_units", "coeffs"],
),
},
sample_kwargs={"target_accept": 0.95, "random_seed": seed},
),
},
]
)
prior_result = prior_check.run(result, context)
print(prior_result.text)
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 1_000 tune and 1_000 draw iterations (4_000 + 4_000 draws total) took 18 seconds.
There were 7 divergences after tuning. Increase `target_accept` or reparameterize.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [beta, y_hat_sigma]
Sampling 4 chains for 1_000 tune and 1_000 draw iterations (4_000 + 4_000 draws total) took 4 seconds.
Sampling: [beta, y_hat, y_hat_sigma]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Sampling: [y_hat]
Prior sensitivity analysis: compared 2 alternative prior specifications.
prior_result.table
| prior_spec | mean | median | hdi_lower | hdi_upper | p_gt_0 | relative_mean | relative_hdi_lower | relative_hdi_upper | |
|---|---|---|---|---|---|---|---|---|---|
| 0 | diffuse (conc=0.5) | -21.889012 | -21.782694 | -24.497220 | -19.348522 | 0.0 | -26.596155 | -28.872154 | -24.277141 |
| 1 | concentrated (conc=10) | -34.582034 | -34.550000 | -36.889684 | -32.324165 | 0.0 | -36.418637 | -37.968334 | -34.913852 |
How to read these results. Each row shows the effect summary under a different Dirichlet concentration parameter for the donor weights. Compare across rows: if the point estimates and intervals are broadly consistent, the conclusions are robust to prior choice. If they diverge — particularly if one specification produces a credible interval that includes zero while another excludes it — the data may not be informative enough to support the causal claim independently of the prior.
Show code cell source
fig, ax = PriorSensitivity.plot(
prior_result, baseline_stats=stats, baseline_label="baseline (conc=1)"
)
Running all checks in a pipeline#
The checks above were run individually for exposition. In practice, CausalPy’s SensitivityAnalysis pipeline step can run all applicable checks in a single call, accumulating results into a SensitivitySummary. Combined with EstimateEffect, this gives a reproducible end-to-end workflow:
from causalpy.steps.sensitivity import SensitivityAnalysis
sensitivity = SensitivityAnalysis(
checks=[
PlaceboInSpace(),
LeaveOneOut(),
]
)
context_with_sensitivity = sensitivity.run(context)
for check_result in context_with_sensitivity.sensitivity_results:
print(f"--- {check_result.check_name} ---")
print(check_result.text)
See the Pipeline Workflow notebook for a full walkthrough of CausalPy’s pipeline API, including how to chain EstimateEffect, SensitivityAnalysis, and GenerateReport into a single reproducible pipeline.
Which checks matter most in your setting?#
The four robustness checks above probe different failure modes, and their relative importance depends on your data structure:
Few donor units? The leave-one-out test may reveal that one unit drives the entire result. Consider augmented SC [Ben-Michael et al., 2021] as a remedy. (With 38 states in the Proposition 99 example, this is less of a concern.)
Short pre-period? The placebo-in-time test helps gauge whether the model has enough pre-treatment data to learn stable donor weights, while a longer pre-period improves asymptotic properties [Ferman, 2021]. The 19-year pre-period here is reasonably long for annual data.
Uncertain priors? If you are using diffuse priors on a sparse donor pool, the prior sensitivity check reveals whether your Bayesian conclusions are data-driven or prior-dominated.
Strong result? A placebo-in-space test that shows your effect is larger than all placebo effects provides the strongest permutation-based evidence for a causal claim [Abadie et al., 2010]. For the Proposition 99 analysis, Abadie et al. [2010] found that California’s effect was an extreme outlier relative to placebo states.
References#
Alberto Abadie, Alexis Diamond, and Jens Hainmueller. Synthetic control methods for comparative case studies: estimating the effect of california's tobacco control program. Journal of the American Statistical Association, 105(490):493–505, 2010.
Alberto Abadie. Using synthetic controls: feasibility, data requirements, and methodological aspects. Journal of Economic Literature, 59(2):391–425, 2021.
Susan Athey and Guido W. Imbens. The state of applied econometrics: causality and policy evaluation. Journal of Economic Perspectives, 31(2):3–32, 2017.
Meta Incubator. GeoLift: synthetic control method to calculate lift at a geo level. https://facebookincubator.github.io/GeoLift/, 2022. R package and documentation.
Kay H. Brodersen, Fabian Gallusser, Jim Koehler, Nicolas Remy, and Steven L. Scott. Inferring causal impact using Bayesian structural time-series models. The Annals of Applied Statistics, 9(1):247–274, 2015.
Alberto Abadie and Javier Gardeazabal. The economic costs of conflict: a case study of the basque country. American Economic Review, 93(1):113–132, 2003.
Alberto Abadie, Alexis Diamond, and Jens Hainmueller. Comparative politics and the synthetic control method. American Journal of Political Science, 59(2):495–510, 2015.
Jeremy Pickett, Jennifer Hill, and Sarah Cowan. Synthetic control misconceptions: recommendations for practice. arXiv preprint arXiv:2603.19211, 2025.
Nikolay Doudchenko and Guido W. Imbens. Balancing, regression, difference-in-differences and synthetic control methods: a synthesis. NBER Working Paper No. 22791, 2016.
Eli Ben-Michael, Ari Feller, and Jesse Rothstein. The augmented synthetic control method. Journal of the American Statistical Association, 116(536):1789–1803, 2021.
Matias D. Cattaneo, Yingjie Feng, and Rocio Titiunik. Prediction intervals for synthetic control methods. Journal of the American Statistical Association, 116(536):1865–1880, 2021.
Sungil Kim, Chul Lee, and Sachin Gupta. Bayesian synthetic control methods. Journal of Marketing Research, 57(5):831–852, 2020.
Bruno Ferman. On the properties of the synthetic control estimator with many periods and many controls. Journal of the American Statistical Association, 116(536):1764–1772, 2021.
Bruno Ferman, Cristine Pinto, and Vitor Possebom. Cherry picking with synthetic controls. Journal of Policy Analysis and Management, 39(2):510–532, 2020.
Bruno Ferman and Cristine Pinto. Placebo tests for synthetic controls. MPRA Paper No. 78079, Munich Personal RePEc Archive, 2017.
Sergio Firpo and Vitor Possebom. Synthetic control method: inference, sensitivity analysis and confidence sets. Journal of Causal Inference, 2018.
Andrew Gelman, John B. Carlin, Hal S. Stern, David B. Dunson, Aki Vehtari, and Donald B. Rubin. Bayesian Data Analysis. CRC Press, 3 edition, 2013.