statspai.causal_impact¶
causal_impact ¶
Causal Impact module for StatsPAI.
Estimates the causal effect of an intervention on a time series by constructing a synthetic counterfactual from control series using a structural time-series model (regression + AR(1) state, fit by plug-in estimates and the Kalman filter).
Same estimand and interface idea as Google's R CausalImpact package, but a different, frequentist model: R CausalImpact fits a Bayesian local-level + spike-and-slab regression by MCMC, so the numbers differ.
References
Brodersen, K.H., Gallusser, F., Koehler, J., Remy, N., and Scott, S.L. (2015). "Inferring Causal Impact Using Bayesian Structural Time-Series Models." Annals of Applied Statistics, 9(1), 247-274. [@brodersen2015inferring]
CausalImpactEstimator ¶
Causal Impact estimator using a structural time-series model.
Fits a regression + AR(1) state-space model on the
pre-intervention period, then forecasts the counterfactual into the
post-intervention period. The high-level :func:causal_impact
wrapper is the usual entry point; instantiate this class directly
only when you want to drive .fit() yourself.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
DataFrame
|
Time-series data, one row per period. |
required |
y
|
str
|
Outcome (intervened) series. |
required |
time
|
str
|
Time column used for ordering. |
required |
intervention_time
|
Any
|
First post-intervention period (inclusive). |
required |
covariates
|
list of str
|
Control series unaffected by the intervention. |
None
|
alpha
|
float
|
Significance level for the credible intervals. |
0.05
|
n_seasons
|
int
|
Not used (no seasonal component); a value > 1 warns. |
None
|
n_boot
|
int
|
Bootstrap draws per pass for the critical value; 0 for the t
quantile (see :func: |
199
|
seed
|
int
|
Seed of the bootstrap draws. |
0
|
Examples:
>>> import numpy as np
>>> import pandas as pd
>>> import statspai as sp
>>> rng = np.random.default_rng(0)
>>> n = 60
>>> week = np.arange(n)
>>> ad_spend = rng.normal(100, 5, size=n)
>>> sales = 2.0 * ad_spend + rng.normal(0, 3, size=n)
>>> sales[40:] += 15.0
>>> df = pd.DataFrame({'week': week, 'sales': sales,
... 'ad_spend': ad_spend})
>>> est = sp.CausalImpactEstimator(df, y='sales', time='week',
... intervention_time=40,
... covariates=['ad_spend'])
>>> result = est.fit()
>>> (est.n_pre, est.n_post)
(40, 20)
>>> bool(result.estimate > 0)
True
causal_impact ¶
causal_impact(data: DataFrame, y: str, time: str, intervention_time: Any, covariates: Optional[List[str]] = None, alpha: float = 0.05, n_seasons: Optional[int] = None, weights: Optional[str] = None, n_boot: int = 199, seed: int = 0) -> CausalResult
Estimate the causal impact of an intervention on a time series.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
DataFrame
|
Time-series data (one row per time period). |
required |
y
|
str
|
Outcome variable (the series that was intervened upon). |
required |
time
|
str
|
Time column (used for ordering; can be int, date, etc.). |
required |
intervention_time
|
any
|
First period of the intervention (inclusive). |
required |
covariates
|
list of str
|
Control time series (not affected by the intervention). If None, uses a local-level model without covariates. |
None
|
alpha
|
float
|
Significance level for the (frequentist) prediction intervals. |
0.05
|
n_seasons
|
int
|
Accepted for API compatibility but currently not used: the model has no seasonal component. |
None
|
weights
|
None
|
Not supported, and refused with |
None
|
n_boot
|
int
|
Parametric-bootstrap draws per pass for the critical value of the
average / cumulative effect (bias-corrected bootstrap-t, see
Notes); |
199
|
seed
|
int
|
Seed of those draws (the result is reproducible). |
0
|
Returns:
| Type | Description |
|---|---|
CausalResult
|
|
Notes
estimate is the average post-period effect and se the standard
deviation of the error of the forecast actually made,
u_post - L u_pre, which accounts for estimating the regression
coefficients exactly; the error covariance is an AR(1)-plus-noise model
fitted by REML on the pre-period, and intervals and p-values use a t
distribution with n_pre - p - 3 degrees of freedom
(model_info['df']). The uncertainty of that covariance is carried
by the critical value of the average / cumulative effect: a two-pass
parametric bootstrap-t (n_boot draws each) that first corrects the
small-sample bias of the REML AR(1) coefficient and noise share, then
simulates the studentised forecast error with the covariance re-fitted
on every draw. model_info['crit'] is the larger of that quantile
and the t quantile, and the p-value the larger of the bootstrap share
and the t p-value, so the interval is never shorter than the t one. The t quantile alone covered 89.5% for a nominal 95% at
n_pre = 40, rho = 0.9 (see tests/reference_parity/
test_causal_impact_uncertainty.py for the calibrated figures); the
pointwise bands in detail keep the t quantile. Until 2026-10-19 the SE took all
parameters as known and covered 57.5%-90.5% in the same designs; that
number is kept as model_info['se_avg_conditional']. The
detail column effect_se carries the pointwise version
(predicted_se stays the filter's conditional one-step value).
This is a frequentist regression +
AR(1) state-space model, not the Bayesian local-level + spike-and-slab
model of R CausalImpact; the numbers are not comparable one for one.
Examples:
Effect of an intervention introduced at week 40, recovered against a counterfactual built from an ad-spend control series:
>>> import numpy as np
>>> import pandas as pd
>>> import statspai as sp
>>> rng = np.random.default_rng(0)
>>> n = 60
>>> week = np.arange(n)
>>> ad_spend = rng.normal(100, 5, size=n)
>>> sales = 2.0 * ad_spend + rng.normal(0, 3, size=n)
>>> sales[40:] += 15.0 # intervention lifts sales from week 40
>>> df = pd.DataFrame({'week': week, 'sales': sales,
... 'ad_spend': ad_spend})
>>> result = sp.causal_impact(df, y='sales', time='week',
... intervention_time=40,
... covariates=['ad_spend'])
>>> result.n_obs
60
>>> bool(result.estimate > 0) # positive average impact recovered
True
References
brodersen2015inferring
impactplot ¶
impactplot(result: CausalResult, type: str = 'all', ax: Any = None, figsize: tuple[float, float] = (12, 9), title: Optional[str] = None) -> Any
Causal Impact visualization (Google-style 3-panel plot).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
result
|
CausalResult
|
Result from |
required |
type
|
str
|
'all': 3-panel (original + pointwise + cumulative). 'original': actual vs counterfactual. 'pointwise': pointwise causal effect. 'cumulative': cumulative effect. |
'all'
|
ax
|
matplotlib Axes
|
Only for single-panel types. |
None
|
figsize
|
tuple
|
|
(12, 9)
|
title
|
str
|
|
None
|
Returns:
| Type | Description |
|---|---|
(fig, ax) or (fig, axes)
|
|
Examples:
>>> import numpy as np
>>> import pandas as pd
>>> import matplotlib
>>> matplotlib.use('Agg')
>>> import statspai as sp
>>> rng = np.random.default_rng(0)
>>> n = 60
>>> week = np.arange(n)
>>> ad_spend = rng.normal(100, 5, size=n)
>>> sales = 2.0 * ad_spend + rng.normal(0, 3, size=n)
>>> sales[40:] += 15.0
>>> df = pd.DataFrame({'week': week, 'sales': sales,
... 'ad_spend': ad_spend})
>>> result = sp.causal_impact(df, y='sales', time='week',
... intervention_time=40,
... covariates=['ad_spend'])
>>> fig, axes = sp.impactplot(result, type='all')
>>> len(axes)
3
>>> fig.savefig('impact.png')