*! version 1.0.0 05aug2026

Title

causalimpact -- Causal impact of an intervention on a time series
using a Bayesian structural time-series model

Syntax

causalimpact
depvar
[covariates]
[if] [in],
preperiod(numlist)
postperiod(numlist)
[options]

The data must be tsset as a single time series (not xtset).
depvar is the treated series; covariates are untreated control series
from which the synthetic control is built. depvar may contain missing
values; the covariates may not.

Periods (required)
preperiod(numlist) first and last time point of the
pre-intervention (training) period, in the units of the tsset time
variable

postperiod(numlist) first and last time point of the
post-intervention period whose effect is to be measured

MCMC
niter(#) number of MCMC draws; default niter(1000)

burnin(#) burn-in draws to discard; default is
0.1*niter()

seed(#) random-number seed; sets set seed

nodots suppress the progress dots

State model
priorlevelsd(#) prior standard deviation of the local-level
random walk, in units of the data standard deviation; default
priorlevelsd(0.01)

nseasons(#) period of the seasonal component; default
nseasons(1) means no seasonal component

seasonduration(#) number of time points each season spans;
default seasonduration(1)

dynamicregression use time-varying rather than static regression
coefficients

nostandardize do not standardise the columns using pre-period
moments

noconstant omit the intercept from the regression component

Spike-and-slab prior
modelsize(#) expected number of covariates in the model,
M in eq. (2.9); default modelsize(3)

r2(#) expected R-squared of the regression, used to set the
residual-variance prior; default r2(0.8)

priordf(#) prior degrees of freedom for the residual variance,
nu_eps in eq. (2.11); default priordf(50)

ginfo(#) Zellner g in eq. (2.12); default ginfo(0.01)

dshrinkage(#) diagonal shrinkage 1-w in eq. (2.12); default
dshrinkage(0.5)

maxflips(#) number of inclusion indicators to attempt to flip per
sweep; default maxflips(-1) means all of them

sigmaupper(#) upper limit on the residual standard deviation, in
units of the data standard deviation; default is no limit

Reporting
level(#) credible-interval level; default level(95)

alpha(#) tail-area probability, the R spelling of
level(); default alpha(0.05)

report print the verbal interpretation of the results

digits(#) rounding used in the verbal report; default
digits(2)

notable suppress the summary table

Series and graphs
generate(stub) save the fifteen fitted series as
stub_name

replace overwrite existing generate() variables

graph draw the three-panel impact figure

metrics(string) which panels to draw: any combination of
original, pointwise, cumulative

coefplot draw the posterior inclusion-probability plot

legend add a legend to the top panel (the R plot has none)

name(string) stem for the graph names

saving(filename) save the combined graph to disk

Description

causalimpact estimates the causal effect of a designed intervention on a
time series, using the Bayesian structural time-series (BSTS) approach of
Brodersen, Gallusser, Koehler, Remy and Scott (2015). It is a faithful Stata
implementation of the R package CausalImpact 1.4.1
(github.com/google/CausalImpact),
against which it has been verified on shared data; see
help causalimpact rcheck.

The idea is a generalisation of difference-in-differences to the time-series
setting. A state-space model is fitted to the response over the
pre-intervention period only, using a set of control series that were
not themselves affected by the intervention. The fitted model is then used to
predict what the response would have been over the post-intervention
period had the intervention not taken place. That prediction is the
counterfactual; the difference between the observed response and the
counterfactual is the causal effect.

Three features distinguish the method from a static regression or a classical
synthetic control:

1. The counterfactual is a full posterior predictive trajectory, not a
collection of pointwise predictions, so cumulative effects and their intervals
are coherent.

2. A spike-and-slab prior over the regression coefficients integrates out both
which controls to use and how strongly they matter, so the analyst does not have
to commit to a subset. Controls may enter with any sign and any scale, unlike
the convex weights of Abadie-type synthetic control.

3. Local level and seasonal components absorb the time-series structure the
controls do not explain, so serial correlation does not shrink the intervals
artificially.

The documentation is spread over five linked pages:

help causalimpact this page: syntax, options,
stored results

help causalimpact methods the model
equation by equation, the step-to-equation map, and the documented deviations
from the paper and from R

help causalimpact interpretation a
detailed guide to reading the tables, the verbal report and the figure, with a
worked reading and the common misreadings

help causalimpact rcheck the verification
against R CausalImpact 1.4.1, and how to re-run it

help causalimpact postestimation predict,
replay, and working with e()

Options

Periods

preperiod(numlist) specifies the first and last time point of the
pre-intervention period. This is the training period: the relationship between
the response and the controls is learned here. It must span at least three time
points. Boundaries are given in the units of the tsset time variable, so
with daily data use, for example,
pre(`=td(01jan2014)' `=td(11mar2014)'). If the
response is missing at the start of the sample, the pre-period is silently moved
forward to the first non-missing observation, exactly as the R package does.

postperiod(numlist) specifies the first and last time point of the
post-intervention period. It must start strictly after the pre-period ends; a
gap between the two periods is allowed and is treated as neither training nor
evaluation data. All summary statistics refer to this period only.

MCMC

niter(#) sets the number of Gibbs draws. More draws give more accurate
posterior summaries. The default of 1000 matches the R package. Fewer than 1000
produces a warning; fewer than 10 is an error.

burnin(#) sets how many initial draws to discard. The default,
0.1*niter(), reproduces SuggestBurn(0.1, .) in the R package, so
niter(1000) retains 900 draws.

seed(#) makes the analysis reproducible. Note that the R package hard-codes
seed = 1 inside its bsts() calls; here the seed is yours to choose,
and results will differ across seeds by Monte Carlo error.

State model

priorlevelsd(#) is the prior standard deviation of the random walk that
drives the local level, expressed in units of the standard deviation of the
response. The default of 0.01 suits well-behaved, stable series with little
residual volatility once the controls have been regressed out. If the
counterfactual looks implausibly rigid, or if the series is volatile, 0.1 is the
safer choice, as validated on synthetic data in the R package documentation. Be
aware that this is the single most consequential prior in the model: it governs
how much of the post-period drift the counterfactual is allowed to track.

nseasons(#) adds a seasonal component with the stated period. For daily
data, nseasons(7) gives a day-of-week effect. Only one seasonal component
is supported, as in the R package. nseasons(1) (the default) means none.

seasonduration(#) is the number of consecutive time points that share a
season. Use nseasons(7) seasonduration(1) for a day-of-week effect on daily
data, and nseasons(7) seasonduration(24) for a day-of-week effect on hourly
data.

dynamicregression lets each regression coefficient follow its own random
walk, eq. (2.6), instead of being constant. Use it when the relationship between
the controls and the treated series is believed to drift. Combined with a
flexible local level this easily over-specifies the model, so a static regression
is usually safer; see help causalimpact methods for
what changes in the prior.

nostandardize turns off the pre-analysis standardisation. By default every
column is centred and scaled using its pre-period mean and standard
deviation, which makes the results invariant to linear rescaling of the data and
amounts to an empirical-Bayes choice of prior scale. Predictions are converted
back to the original units before any cumulative sum is taken.

noconstant drops the intercept from the regression design matrix. By
default an intercept is included, as bsts() does. It is largely redundant
with the local level and, when the data are standardised, is close to zero.

Spike-and-slab prior

modelsize(#) sets the expected number of covariates, M. Each covariate
receives prior inclusion probability pi_j = min(1, M/J) in
eq. (2.9). Framing the prior in terms of expected model size lets the model
absorb more and more candidate controls without switching to a hierarchical
prior.

r2(#) and priordf(#) set the prior on the residual variance through
s_eps = nu_eps(1 - R^2)s^2_y in eq. (2.11). The defaults 0.8
and 50 are the values used for the empirical analyses of the paper.

ginfo(#) and dshrinkage(#) control the Zellner g-prior of eq. (2.12),
Omega^-1 = (g/n){(1-d)X'X + d diag(X'X)}. See
help causalimpact methods for why the default g is
0.01 rather than the 1 stated in the paper.

maxflips(#) restricts each sweep to a random subset of inclusion indicators.
With very many controls (thousands) this speeds the sampler up considerably at
the cost of slower mixing.

sigmaupper(#) truncates the residual standard deviation from above, in
units of the data standard deviation. It is off by default.

Reporting, series and graphs

level(#) and alpha(#) are two ways of asking for the same thing:
level(90) is identical to alpha(0.10). The Stata spelling wins if
both are given.

report prints the verbal interpretation of the results, a port of the R
package's summary(impact, "report"). It can also be requested after
estimation by replaying with causalimpact, report.

generate(stub) creates fifteen new double variables holding the complete
set of fitted series, mirroring impact$series in R:

stub_response observed response

stub_cum_response cumulative observed response

stub_pred counterfactual, posterior mean

stub_lower stub_upper counterfactual credible band

stub_cum_pred cumulative counterfactual

stub_cum_lower stub_cum_upper its credible band

stub_effect pointwise causal effect, eq. (2.15)

stub_effect_lower stub_effect_upper its credible band

stub_cum_effect cumulative causal effect, eq. (2.16)

stub_cum_effect_lower stub_cum_effect_upper its band

stub_avg_effect running-average effect, eq. (2.17)

graph draws the three-panel figure of Figure 1 and Figures 5-7 of the
paper: the observed series with its counterfactual and credible band; the
pointwise effect; and the cumulative effect. Each panel is also left in memory
under its own name so that panels can be re-combined or restyled.

The styling reproduces the R package's ggplot2 figure: the credible band
is slategray2 (RGB 185 211 238), the counterfactual is a darkblue
(RGB 0 0 139) dashed line, the observed series is a solid black line, and the
panel uses the theme_bw look, i.e. a white plot region with a thin
grey20 border and pale grey92 gridlines. Period boundaries are dashed grey
vertical lines and the zero line in the lower two panels is solid grey. Panel
labels sit in a boxed strip on the right, as facet_grid(metric ~ .)
produces in R. As in R there are no axis titles and no legend.

legend adds a legend under the top panel identifying the observed series,
the counterfactual and the credible band. The R figure has none, so this is
off by default; turn it on when the figure has to stand alone in a paper.

metrics(string) selects panels, for example
metrics(original pointwise). Drop cumulative whenever the response
is a stock rather than a flow quantity: summing subscribers or prices
across time is not meaningful, whereas summing clicks or sales is (paper,
Sec. 2.4).

coefplot draws posterior inclusion probabilities for the regression
component, the counterpart of plot(bsts.model, "coefficients") in R.
Covariates are ordered with the largest inclusion probability at the top, and
each bar is shaded by the probability that its coefficient is positive given
that it is in the model. R encodes this on a white-to-black ramp; here the same
information runs along a blue ramp from pale blue (negative) to the same
darkblue used for the counterfactual line (positive), so the two figures
share one palette.

Interpreting the output

The header reports the response, the controls, the state components in use, the
periods, and the MCMC bookkeeping. The main table has two columns.

Average summarises the post-period one time point at a time: the mean
observed value, the mean counterfactual, and the mean effect.

Cumulative sums over the post-period. This is the column to read when the
response is a flow quantity (clicks, sales, sign-ups) and the question is "how
many extra units did the intervention produce". It is meaningless for a stock
quantity.

The rows are:

Actual - what was observed.

Prediction - the counterfactual, i.e. what the model expects would have
happened without the intervention, with its posterior standard deviation and
credible interval.

Absolute effect - Actual minus Prediction, with its own posterior standard
deviation and interval. Note that this interval is computed from the posterior
draws of the difference, not by differencing the two intervals above.

Relative effect - the same thing as a percentage of the counterfactual.
The Average and Cumulative entries are deliberately identical: both report the
posterior of sum(y)/sum(counterfactual) - 1, exactly as in the R package.

Two probabilities close the table.
p is the one-sided Bayesian tail-area probability of the cumulative effect:
the posterior probability of having drawn a counterfactual sum at least as
extreme as the observed sum. Posterior probability of an effect is 1 - p.
Checking whether the credible interval excludes zero is a two-sided test;
checking whether p is below alpha is a one-sided test, so the two can disagree in
borderline cases.

When controls are present, a second table reports, for each of them, the
posterior inclusion probability, the model-averaged posterior mean and standard
deviation of its coefficient, and the probability that the coefficient is
positive given that it is in the model. Coefficients are reported in the metric
the model is fitted in, that is, standardised unless nostandardize was
specified. This matches the R package, which likewise never converts them back.

Because the intercept is redundant with the local level, do not read it as a
structural quantity.

Remarks and practical guidance

The assumptions that actually matter

As with every non-experimental approach to causal inference, valid conclusions
require strong assumptions. Two of them do the real work:

The controls must not themselves have been affected by the intervention.
If an advertising campaign in the United States spills over into Canada, and
Canada is used as a control, the counterfactual is contaminated and the effect
is understated. Plot every control and reason about the spillover explicitly.

The relationship between controls and response, learned in the pre-period,
must remain stable through the post-period. Section 3 of the paper shows what
happens when it does not: a structural break in the post-period destroys the
orderly decay of estimation accuracy. dynamicregression relaxes this
somewhat, at the cost of a much more flexible model.

Checking the assumptions

The single most useful diagnostic is a placebo analysis: pick a date in the
pre-period where nothing happened, declare it an intervention, and re-run. You
should find no significant effect. If you do find one, the model is
mis-specified or the controls are inadequate, and the headline result should not
be trusted. Section 7 of the accompanying self-test do-file does exactly this.

Also check the pre-period fit visually in panel A: the counterfactual should
track the observed series closely before the intervention. In the paper's
Figure 5 it even reproduces a spike two weeks before the campaign.

How long a post-period?

Posterior intervals widen as the forecast horizon grows, which is a feature, not
a defect: predicting further into the retrospective future is genuinely more
uncertain. But an over-long post-period dilutes a real effect that has already
worn off, and the cumulative interval can drift back across zero. The paper's
Figure 1(c) shows a real effect losing significance about five months after the
intervention purely because of this.

Flow versus stock

Report the cumulative effect only for flow quantities. For stock quantities use
the running average, eq. (2.17), available as
stub_avg_effect through generate(), and drop the cumulative
panel with metrics(original pointwise).

Sample size, controls, and run time

Aim for a pre-period long enough to pin down the relationship, and prefer many
candidate controls to few: the spike-and-slab prior is designed to choose among
them, and adding an irrelevant control costs little. Controls that are close in
nature to the treated series (the same product elsewhere, industry-wide search
volume) are the most informative.

Run time is roughly linear in niter() times the number of time points, and
quadratic in the state dimension, which is 1 + nseasons() - 1. With very
many controls, maxflips() is the lever to pull.

Missing values

The response may contain missing values in the pre-period; the Kalman filter
simply skips the update step. The controls may not, since they are needed at
every time point to form the counterfactual. Impute them, or drop the control.

Gaps in the time variable

Like the R package, causalimpact models the data in row order and ignores
gaps in the tsset time variable; a note is printed when gaps are found.
This is harmless without a seasonal component but mis-aligns one if present.
tsfill first if you are using nseasons().

Compatibility with the R package

The model, the priors, the sampler and every reported statistic follow R
CausalImpact 1.4.1. Point estimates, intervals and the tail-area
probability agree with the R output up to Monte Carlo error; they cannot agree
bit for bit, because the two implementations draw from different random-number
streams. Quantiles use R's default type-7 definition so that the interval
endpoints are computed identically given the same draws.

Three deliberate, documented differences are described in full in
help causalimpact methods: the treatment of the
Zellner g default, the residual-variance prior in the no-covariate case, and the
prior on the dynamic-regression innovation variances. None affects the default
static-regression analysis that the paper's empirical sections use.

Examples

Set up the toy dataset of the R vignette: a response y, one control
x1, and a lift of 10 units from time 71.

. clear

. set seed 1

. set obs 100

. gen int t = _n

. tsset t

. gen double x1 = 0

. replace x1 = 0.999*x1[_n-1] + rnormal() in 2/l

. replace x1 = x1 + 100

. gen double y = 1.2*x1 + rnormal()

. replace y = y + 10 if t >= 71

The basic analysis:

. causalimpact y x1, pre(1 70) post(71 100)

With the figure and the verbal report:

. causalimpact y x1, pre(1 70) post(71 100) graph report

Dated data; the periods are given as dates:

. causalimpact y x1, pre(`=td(01jan2014)' `=td(11mar2014)') post(`=td(12mar2014)' `=td(10apr2014)')

Many candidate controls, letting the spike-and-slab prior choose:

. causalimpact y x1-x50, pre(1 200) post(201 260) modelsize(5) coefplot

Daily data with a day-of-week effect and 90% intervals:

. causalimpact y x1 x2, pre(1 300) post(301 400) nseasons(7) level(90)

Time-varying coefficients, more draws, and a looser level prior:

. causalimpact y z1 z2, pre(1 365) post(366 546) dynamicregression niter(5000) priorlevelsd(0.1)

Keep the series and plot only what makes sense for a stock quantity:

. causalimpact y x1, pre(1 70) post(71 100) generate(ci) metrics(original pointwise)

. list t ci_pred ci_effect ci_avg_effect if t >= 71

A placebo check on a date where nothing happened:

. causalimpact y x1, pre(1 40) post(41 70) report

The complete, self-checking demonstration:

. do causalimpact_example.do

Stored results

causalimpact is an eclass command. It stores:

Scalars

e(N) number of observations in the estimation sample

e(N_pre) observations in the pre-period

e(N_post) observations in the post-period

e(N_model) observations in the modelling window

e(niter) MCMC draws requested

e(burnin) draws discarded

e(ndraws) draws retained

e(alpha) tail-area probability

e(level) credible-interval level

e(p) one-sided Bayesian tail-area probability

e(prob_effect) posterior probability of an effect, 1 - p

e(actual_avg), e(actual_cum) observed average and sum

e(pred_avg), e(pred_cum) counterfactual average and sum

e(abseffect), e(abseffect_sd) average absolute effect and its s.d.

e(abseffect_cum), e(abseffect_cum_sd) cumulative absolute effect and its s.d.

e(releffect), e(releffect_sd) relative effect and its s.d.

e(sigma_obs) posterior mean observation standard deviation

e(sigma_level) posterior mean local-level standard deviation

e(sigma_seas) posterior mean seasonal standard deviation, if any

e(k_covariates) number of controls supplied

e(priorlevelsd), e(nseasons), e(seasonduration) model settings

e(modelsize), e(r2), e(priordf), e(ginfo), e(dshrinkage), e(maxflips) prior settings

Macros

e(cmd) causalimpact

e(cmdline) command as typed

e(depvar) name of the response

e(covariates) names of the controls

e(timevar) tsset time variable

e(pre_period), e(post_period) period boundaries

e(model) state components used

e(regression) regression type

e(standardize) whether the data were standardised

e(predict) causalimpact_p

Matrices

e(b) model-averaged posterior mean coefficients

e(V) posterior variance matrix of the coefficients

e(summary) 2 x 15 summary table; rows Average and
Cumulative, columns Actual, Pred, Pred_lower,
Pred_upper, Pred_sd, AbsEffect, AbsEffect_lower,
AbsEffect_upper, AbsEffect_sd, RelEffect,
RelEffect_lower, RelEffect_upper, RelEffect_sd,
alpha, p. This is exactly impact$summary in R.

e(inclusion) J x 4 matrix of posterior inclusion probability,
posterior mean, posterior s.d. and P(coefficient > 0 | included)

Functions

e(sample) marks the estimation sample

References

Brodersen, K. H., F. Gallusser, J. Koehler, N. Remy, and S. L. Scott. 2015.
Inferring causal impact using Bayesian structural time-series models.
Annals of Applied Statistics 9(1): 247-274.
DOI: 10.1214/14-AOAS788. Open
access from the Institute of Mathematical Statistics.

Abadie, A., A. Diamond, and J. Hainmueller. 2010. Synthetic control methods for
comparative case studies: estimating the effect of California's tobacco control
program. Journal of the American Statistical Association 105: 493-505.

Durbin, J., and S. J. Koopman. 2002. A simple and efficient simulation smoother
for state space time series analysis. Biometrika 89: 603-615.

George, E. I., and R. E. McCulloch. 1997. Approaches for Bayesian variable
selection. Statistica Sinica 7: 339-374.

Scott, S. L., and H. R. Varian. 2014. Predicting the present with Bayesian
structural time series. International Journal of Mathematical Modeling and
Optimization 5: 4-23.

Zellner, A. 1986. On assessing prior distributions and Bayesian regression
analysis with g-prior distributions. In Bayesian Inference and Decision
Techniques, 233-243. Amsterdam: North-Holland.

Author

Dr Merwan Roudane

merwanroudane920@gmail.com

https://github.com/merwanroudane

Also see

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