chc.scm¶
Stability tier: evolving.
Synthetic control and augmented synthetic control for a single treated unit.
The synthetic control method (SCM, Abadie-Diamond-Hainmueller) builds a counterfactual for one treated unit as a convex combination of donor units matched on the pre-treatment outcome path; the treatment effect is the treated-minus-synthetic gap in the post period. Its weakness is the simplex constraint: when the treated unit lies outside the donors' convex hull no weighting balances the pre-period, so the estimate is biased.
Augmented SCM (ASCM, Ben-Michael-Feller-Rothstein) corrects exactly this: it keeps the SCM donor mix but adds a ridge outcome-model correction for the residual pre-period imbalance, which is allowed to extrapolate. When SCM already balances, the correction is ~0 and ASCM reduces to SCM.
A statistical estimator, so NumPy float64 throughout (like chc.did), independent of the JAX
x64 flag -- which must not change an estimate.
synthetic_control_inference adds Abadie, Diamond and Hainmueller's in-space placebo test and
an interval from Chernozhukov, Wüthrich and Zhu's conformal test. Both are owned here. diff-diff
carries both and is the tests' oracle, but its placebo leaves the treated unit out of every
placebo's donor pool, which gives up the test's exact level, and its weights come from a
Frank-Wolfe solver that needed 200 000 steps to converge; over this module's exact weights each test
is a few lines.
Neither test holds when a design chose the treated unit, as a design that picks its test market
does. blank_period_inference gives such a test the reading Abadie and Zhao's design
justifies: the weights fit on the earlier periods before treatment, the latest ones left blank,
and the gaps after treatment ranked against every arrangement of the blank and post periods. Its
serial="ar1" reading keeps that test's level where the errors persist, as a geo panel's do.
SyntheticControlResult
dataclass
¶
A fitted synthetic control: post-period effect path, donor mix, and pre-period fit.
SyntheticControlInference
dataclass
¶
synthetic_control's estimate, with a placebo test and a conformal interval.
Experimental.
placebo_p_value is Abadie, Diamond and Hainmueller's in-space placebo for the null that the
treatment moved nothing, built as they built it: each unit's synthetic control from all the
others, and the share of the N units whose ratio of post- to pre-period prediction error is
at least the treated unit's. A pre-period error below sqrt(eps) of the panel's spread before
treatment counts as that much, so units their donors reproduce, as they can when there are more
donors than periods before treatment, rank by their error after it. When the treated unit was
drawn at random from the units, the test holds its level exactly; it moves in steps of
1 / N.
interval holds a constant effect with probability 1 - alpha: the hull of the effects
Chernozhukov, Wüthrich and Zhu's conformal test does not reject, as a search finds them. The
test takes the effect out of the treated unit's periods after treatment, refits its synthetic
control over every period, and ranks the residuals after treatment against each cyclic shift of
the residuals in time. So it needs the residuals exchangeable in time, not the treatment random.
Its p-values move in steps of 1 / T: below alpha = 1 / T the interval is unbounded, and
it is (nan, nan) when the test rejects every effect the search tries, which can miss a set
of accepted effects narrower than its step. The p-value need not fall away from the estimate,
which is not refitted after treatment, so the interval can leave the estimate out; and a hull
can hold effects the test rejects, the estimate among them.
Measured on factor panels of 20 periods before treatment and 5 after, 1000 a case
(scripts/bench_scm_inference.py). With the treated unit drawn at random, the placebo
rejected a true null 3.8-4.7% of the time at 5% with 19 donors, and 9.2-9.6% at 10% with 9 or
19. The interval covered the effect 97.2-98.4% of the time at alpha = 0.05 and 93.1-94.0% at
0.10, where the test's own levels, on 25 periods, are 4% and 8%. Neither holds when the treated
unit was chosen as the one its donors fit best, as a design that picks its test market chooses:
the placebo then rejected 27.8% at 5%, and the interval covered 94.4% and 81.7%.
BlankPeriodInference
dataclass
¶
A designed test's synthetic control fitted on the earlier periods before treatment, with a permutation test and split-conformal intervals read from the periods it left blank. Experimental.
estimate is the synthetic control fitted on the fitting periods alone: its pre_rmspe is
their error, and its att and overall are the gaps after treatment. blank_gaps are
the gaps, under the same weights, in the blank periods, which the weights never saw.
p_value is Abadie and Zhao's (arXiv 2108.02196 v5, eq. 19) for the null that the treatment
moved nothing: the share of the arrangements ways of choosing T_post of the blank and
post periods whose mean absolute gap is at least the post periods'. It moves in steps of
1 / arrangements, C(T_blank + T_post, T_post), and is never below one step.
Read with serial="ar1", ar_coefficient is an AR(1) coefficient of the gaps, estimated
from the donors alone, and p_value ranks the gaps' innovations instead: e_t = u_t -
ar_coefficient * u_{t-1} over the blank and post periods, u the gaps and the first lag
the last fitting period's. Its statistic is |e_first + (1 - ar_coefficient) * (sum of the
other post innovations)|, which a constant effect moves most. The arrangements are each
choice of T_post innovations and which of them comes first, T_post C(T_blank + T_post,
T_post) of them. ar_coefficient is None in the default reading.
intervals[k] holds the effect in post period k with probability at least 1 - alpha:
the gap there, plus or minus the ceil((1 - alpha) (T_blank + 1))-th smallest absolute
blank gap, which is infinite when that rank exceeds T_blank, as it does for every alpha
< 1 / (T_blank + 1). That is split conformal prediction's finite-sample rank, one above Abadie
and Zhao's eq. 20, which reaches 1 - alpha only as T_blank grows.
Measured on factor panels of 19 donors, 15 fitting, 5 blank and 5 post periods, the treated
unit the one its donors fit best on the fitting periods, 4000 a case
(scripts/bench_scm_inference.py blank). With white-noise factors and errors, the test
rejected a true null 4.4% of the time at 5%, where synthetic_control_inference's
placebo rejected 23.5% on 400 such panels. With AR(1) factors and errors of coefficient 0.5 it
rejected 8.7%, against that placebo's 27.8%: errors that persist break the exchangeability, and
the rate grew to 21.5% at 0.8. Factors that persist did not, 4.8% with white-noise errors. The
serial="ar1" reading rejected 4.9%, 4.8% and 5.3% at coefficients 0, 0.5 and 0.8 (standard
errors 0.35). The intervals, the same in both readings, covered 83.6%, 79.6% and 70.7% at
alpha = 0.2; at 0.05 and 0.10 they were unbounded.
synthetic_control(outcomes, treated_unit, n_pre, *, steps=5000)
¶
Classic simplex synthetic control for one treated unit against the remaining donor units.
outcomes is (N, T); treatment starts at period n_pre (so periods 0..n_pre-1 are
pre-treatment). Donor weights lie on the probability simplex; att[k] is the treated-minus-
synthetic gap in post period k. The weights are the exact optimum, whatever the outcomes'
units; steps caps the solver's iterations, and running out raises RuntimeError.
augmented_synthetic_control(outcomes, treated_unit, n_pre, *, ridge=0.1, steps=5000)
¶
Ridge-augmented synthetic control (Ben-Michael-Feller-Rothstein).
Starts from the SCM donor weights, then de-biases each post period by the residual pre-period
imbalance passed through a ridge outcome model fit on the donors:
Y_1(0)_t = w' Y_post_t + (treated_pre - w' donor_pre)' theta_t, with theta_t the
slopes of a ridge regression of the donors' Y_post_t on their pre-period outcomes with a
free intercept, as Ben-Michael, Feller and Rothstein's has: theta_t = (Zc'Zc + ridge * v *
I)^{-1} Zc' Y_post_t, with Zc each pre-period centred on the donors' mean, or zero where
the donors moved apart by rounding alone (chc._units), and v the mean of Zc**2,
the donors' variance before treatment, or 1 where Zc is all zero. The intercept cancels
from the estimate, since the weights sum to one. The correction vanishes when SCM already
balances the pre-period; ridge controls how far the outcome model may extrapolate, and
ridge = lam / v is a ridge of lam in the outcomes' squared units. As a share of v
it shrinks the same in any units and from any origin: a ridge in the outcomes' squared units
outweighed donors logged in small ones, so that at 1e-6 of them the estimate was plain SCM's,
and without the intercept the estimate moved with the outcomes' level. weights are the
(interpretable) SCM donor weights; the augmentation is an additive outcome correction, not
folded into them.
The default share is a tenth, a compromise, since no one share suits every panel. On 200 panels of 30 donors and 25 periods before treatment, the root mean squared error of the effect at a tenth was 0.0526 where the factors are white noise, against 0.0502 at the best share, 1. Where they are random walks it was 0.110, against 0.105 at the best share, 0.03, and 0.359 at 1. With those panels indexed to their first period it was 0.142, against 0.126 at the best, 0.01: a tenth errs by up to 1.133 times the best fixed share. 0.07 would have stayed within 1.1 times it on all three kinds, but the default was set before they were measured, and moving it to suit them would tune it on them. The earlier default, a ridge of 1 in the outcomes' squared units, erred less on these panels, 0.0497, 0.107 and 0.113, by a coincidence of units: their noise is 0.1 in those units, so it was a fixed multiple of the noise variance. At 1e-3 of the units it gave plain SCM's estimate.
synthetic_control_inference(outcomes, treated_unit, n_pre, *, alpha=0.05)
¶
synthetic_control with a placebo test and a conformal interval.
Experimental: it may change or be withdrawn in any release.
Takes synthetic_control's arguments, and computes its estimate. The placebo test fits a
synthetic control to every unit. The interval's search tries 401 effects over ten errors either
side of the estimate, a twentieth of an error apart, the error being the root mean square of the
treated unit's residual, over every period, at the estimate. It pushes the outermost effects not
rejected outwards, and bisects each end to a millionth of that error.
Raises:
| Type | Description |
|---|---|
ValueError
|
on |
blank_period_inference(outcomes, treated_unit, n_pre, n_blank, *, alpha=0.05, serial='exchangeable')
¶
A designed geo test read as Abadie and Zhao read it: fit on the earlier periods before
treatment, hold out the last n_blank of them, permute over the blank and post periods.
Experimental: it may change or be withdrawn in any release.
outcomes, treated_unit and n_pre are synthetic_control's. The weights fit
on periods 0 .. n_pre - n_blank - 1; periods n_pre - n_blank .. n_pre - 1 are blank.
The treated unit may be any unit a design chose by its fit on the fitting periods, or on
anything else outside the blank and post periods.
Abadie and Zhao's Theorem 2 brackets the test's size, alpha - 1 / arrangements <= P(p <=
alpha) <= alpha, when the factors over the blank and post periods are exchangeable and
independent of the noise, and each unit's noise is i.i.d. over time; then the gaps over those
periods are exchangeable, whatever the fit. The upper end needs only that exchangeability; the
lower one needs no ties. So:
- Noise that persists over time breaks it: the mean gap over adjacent post periods then varies
more than over scattered ones, and the test rejects too often, 8.7% at 5% with AR(1) errors
of coefficient 0.5. Factors that persist do not.
serial="ar1"is the remedy, below. - The design must not look at the blank periods. A unit chosen for its fit there has blank gaps selected small, and the test then rejects too often.
- The blank periods are the latest before treatment, so that a season or a trend moves them as little apart from the post periods as the panel allows. Nothing in the data checks exchangeability; a season that turns at treatment breaks it.
- The blank periods leave the fit: the weights see
n_pre - n_blankperiods, notn_pre. - The p-value moves in steps of
1 / C(n_blank + T_post, T_post): with 5 blank and 5 post periods, of 1/252; with 1 blank period, of 1/6, so no test at 5% rejects.
intervals hold under the same exchangeability, the effect being additive and the noise
after treatment distributed as before it, one period at a time, not jointly.
serial="ar1" reads gaps that follow an AR(1), u_t = c + rho u_{t-1} + e_t with e
i.i.d., as they do when every unit's errors and the factors share one coefficient. Then the
innovations over the blank and post periods are i.i.d. given the fitting periods, and with the
true rho the test is exact again, whatever the design did with the fitting periods
(Chernozhukov, Wüthrich and Zhu, JASA 2021, §2.4.2 and Lemma 7, filter a synthetic control's
residuals so before permuting them). rho is estimated:
- Not from the treated unit's gaps over the fitting periods. The fit absorbs their persistence: on the bench's panels their AR(1) estimate averaged -0.13 where the errors' was 0.5.
- From the donors: each donor's synthetic control from the other donors, the treated unit left
out, fitted on the first
ceil(T_fit / 2)periods; its gaps on every later period; and Han, Phillips and Sul's panel fully aggregated estimator over them (Econometric Theory 30, 2014, §2), which removes each donor's level without the within-estimator's bias, kept in[-1, 1]. An effect on the treated unit does not reach it. It assumes the donors' gaps persist as the treated unit's do. - An estimate below
rholeaves the innovations positively correlated, and the test rejects too often: withrhofixed 0.1 low, 5.8% at 5% at 0.5. On the bench's panels the estimate averaged 0.49 at 0.5 (s.d. 0.08) and 0.77 at 0.8, and the test rejected 4.8% and 5.3%. With 10 blank periods, 5 fitting for the donors, it rejected 5.5% on 12000 panels. - Its power is for a constant effect: an effect of 1 and 2 on the bench's panels, rejected 14.9% and 45.2% at 0.5, where the exchangeable reading rejected 19.7% and 44.3% at a size of 8.7%, and 27.5% and 76.8% where the errors do not persist, against 13.5% and 48.5%.
- The intervals are the exchangeable reading's, and persistence breaks them: at 0.8 the 80% intervals covered 70.7%.
Raises:
| Type | Description |
|---|---|
ValueError
|
on |