*! version 1.0.0 05aug2026

Title

causalimpact rcheck -- Verification against the R package
CausalImpact 1.4.1

Why the two cannot agree exactly

causalimpact is a port of the R package CausalImpact 1.4.1, whose
complete source is at
github.com/google/CausalImpact
and can be compared line by line against the step-to-equation map in
help causalimpact methods.

The model, the priors, the sampler and every reported statistic follow the R
package. What cannot be reproduced is the arithmetic itself, because both are
Monte Carlo procedures: R draws from its own random-number stream through
C++ code in Boom, Stata draws from Mata's. Two correct implementations of
the same posterior will land on slightly different numbers, and the difference
shrinks as niter() grows. Even in R, two runs with different seeds
disagree.

So the standard of proof is not equality. It is: given the same data, the
same model and the same priors, do the two land in the same place to within
Monte Carlo error, and do they reach the same conclusion every time?

One thing is matched exactly and deliberately. Quantiles use R's default
type-7 definition, h = (n-1)p + 1 with linear interpolation, so that given
identical draws the interval endpoints would be identical too. Any remaining gap
is sampling, not convention.

How the check was run

The weak version of this check is to simulate in R, simulate separately in
Stata, and compare. That confounds two things: differences in the estimator and
differences in the simulated data.

The check used during development avoids that. Every dataset was
generated in R, exported to CSV, and read back into Stata, so both programs
saw byte-identical data. The only remaining source of difference is the MCMC
stream. The verification materials -- the R driver script, the full R log, the
shared CSVs, and the side-by-side Stata do-file -- are in the project repository
at
github.com/merwanroudane, and are not
part of the installed package, which ships only code, help and the self-test.

Results

Static regression -- the branch the paper's own empirical sections use, and the
default here:

quantity Stata R diff
------------------------------------------------------------
vignette, cumulative 315.430 315.340 0.03%
vignette, pointwise 10.514 10.511 0.03%
vignette, relative 9.872% 9.873% 0.01%
vignette, counterfactual sum 3196.026 3196.115 0.00%
selection, cumulative 175.709 176.249 0.31%
seasonal, cumulative 298.885 298.487 0.13%
seasonal, relative 11.097% 11.052% 0.41%
no covariates, cumulative 134.683 134.354 0.24%
placebo, cumulative -13.973 -12.843 both null

Variable selection, ten candidate controls of which only two are informative:

covariate Stata P(incl) R P(incl)
-----------------------------------------
x1 1.000 1.000
x2 1.000 1.000
x3 ... x10 <=0.012 <=0.019

Significance verdicts agree in every section, including the placebo, where both
correctly fail to find an effect (Stata p = 0.160, R p = 0.185).

Two remarks on reading that table. The placebo row shows an 8.8% relative gap,
which looks large until you notice both numbers are near zero -- a percentage
difference between two near-zero quantities is not informative, and the verdict
is what matters. And on the vignette design neither program recovers the
true cumulative effect of 300; both return about 315. That is a property of the
data, not of either implementation: the covariate is an AR(0.999) and the
counterfactual level is genuinely uncertain. The published R vignette reports
the same overshoot.

The one place they part company

The dynamic-regression branch does not match R closely, and this is expected
rather than discovered:

quantity Stata R
-------------------------------------------------
paper Sec. 3, cumulative 135.798 45.008
paper Sec. 3, relative 8.165% 2.989%
paper Sec. 3, p 0.153 0.372

The cause is deviation 4 below: Boom places a hierarchical Gamma prior on
the innovation precisions of the time-varying coefficients, shared across
coefficients, whereas this command implements eq. (2.6) as the paper writes
it, with an independent inverse-Gamma per coefficient. Different priors, different
posteriors.

Both nonetheless reach the same conclusion: the effect is not detected,
p well above 0.05 in each, and both 95% intervals cover the true +10%. On that
draw Stata's +8.2% is in fact nearer the truth than R's +3.0%, but this is one
replication and should not be read as evidence that either prior is better.

If exact agreement with R on the dynamic branch matters more to you than
following the paper, use the static branch, which is faithful to both.

The four documented deviations

These are stated in advance rather than discovered afterwards. The full
discussion is in help causalimpact methods.

1. The Zellner g default. Sec. 2.2 of the paper states g = 1. The R package
never passes prior.information.weight to SpikeSlabPrior(), so it
inherits that function's default of 0.01. Since compatibility with R is the
stated goal, ginfo() defaults to 0.01. Use ginfo(1) to follow
the printed paper instead. The effect on results is small because the prior
carries only a hundredth of one observation's worth of information either way.

2. The exponent in eq. (2.13). The typeset marginal in the paper carries
the exponent (N/2) - 1 on S. The conjugate normal-inverse-Gamma marginal, for
the prior actually stated in eq. (2.11), has exponent -N/2, which is what
BoomSpikeSlab computes. This command uses -N/2, following the reference
implementation.

3. The observation-variance prior with no covariates. With a regression
present, the residual variance comes from the spike-and-slab block exactly as in
R. With no covariates at all, the R package relies on a bsts() internal
default that the published source does not make explicit. A very diffuse prior
is used here -- guess = sd(y), nu = 0.01, upper = 1.2 sd(y) -- which has
essentially no influence on standardised data. The empirical check above shows
the no-covariate case agreeing with R to 0.24%.

4. The dynamic-regression innovation prior. As described above. The dynamic
branch is paper-faithful and R-approximate; the static branch is faithful to
both.

Running the check yourself

The installed package carries a self-testing harness that validates the command
without needing R at all. Every example has a known truth, and the last
section runs the paper's own Monte Carlo design for interval coverage and power:

. net get causalimpact

. do causalimpact_example.do

To repeat the head-to-head against R, take the verification materials from the
project repository at
github.com/merwanroudane. They
contain the R driver script, the shared CSVs, the full R log and a do-file that
prints R's number beside Stata's on every line. Regenerating the R half needs R
with CausalImpact 1.4.1 and bsts installed; re-running the Stata half
needs only the CSVs, which are included there.

What counts as a real discrepancy

When comparing your own run against R, judge agreement as:

o cumulative and pointwise effects within a few percent

o relative effects within a few tenths of a percentage point

o the same significance verdict in every case

o inclusion probabilities near 1 for genuinely informative controls and near 0
for noise

o a placebo that is null in both

Expect more disagreement, in both directions, when the post-period is long, when
the controls are weak or near-integrated, when niter() is small, or on the
dynamic branch. A gap far larger than Monte Carlo error, or a reversed
verdict, is a real discrepancy and worth reporting as a bug.

Finally, do not use closeness to R as evidence that the answer is right.
Both programs share the same assumptions, and if the controls were contaminated
by the intervention, the two will agree precisely on the wrong number. Agreement
with R validates the implementation, not the design. For that, see the checklist
in help causalimpact interpretation.

Author

Dr Merwan Roudane

merwanroudane920@gmail.com

https://github.com/merwanroudane

Also see

Help: causalimpact,
causalimpact methods,
causalimpact interpretation,
causalimpact postestimation