Skip to main content

Causal effect estimation using ARIMA models for time series interventions

Project description

pycausalarima

A Python implementation of the C-ARIMA methodology for estimating causal effects of interventions on time series data.

Disclaimer: This package is provided with no warranties of any kind, express or implied.

Overview

pycausalarima estimates the causal effect of an intervention on a univariate time series using ARIMA models. It implements the methodology described in:

Menchetti, F., Cipollini, F., & Mealli, F. (2023). "Combining counterfactual outcomes and ARIMA models for policy evaluation." The Econometrics Journal.

This package is a Python port of the CausalArima R package. Results have been validated against the R implementation across 30 test scenarios covering ARIMA and SARIMA models. See VALIDATION.md for details.

Installation

From source

git clone https://github.com/RobsonTigre/pycausalarima.git
cd pycausalarima
pip install -e .

For development

pip install -e ".[dev]"

Dependencies

Requires Python 3.10+ with:

  • numpy, pandas, scipy
  • statsmodels, pmdarima
  • matplotlib

Quick Start

import numpy as np
import pandas as pd
from pycausalarima import CausalArima

# Create sample data
n = 100
np.random.seed(1)
y = np.cumsum(np.random.normal(0, 1, n)) + 100
y[70:] += 10  # Add intervention effect at day 70

dates = pd.date_range('2020-01-01', periods=n, freq='D')
intervention_date = dates[70]

# Fit model
ca = CausalArima(
    y=y,
    dates=dates,
    intervention_date=intervention_date,
)
result = ca.fit()

# View results
print(ca.summary())

# Visualize
ca.plot(type='forecast')

Complete Example: Replicating the R Package Demo

This example replicates the canonical example from the original R CausalArima package, demonstrating detection of a +10 unit intervention effect with an exogenous regressor.

import numpy as np
import pandas as pd
from pycausalarima import CausalArima

# Simulate data (matches R package example)
n = 100
np.random.seed(1)

# Generate AR(1)-like covariate
x1 = np.zeros(n)
x1[0] = 100
for t in range(1, n):
    x1[t] = 100 + 0.999 * (x1[t-1] - 100) + np.random.normal(0, 1)

# Response with relationship to x1
y = 1.2 * x1 + np.random.normal(0, 1, n)

# Add intervention effect (+10) starting at 71% of the series
intervention_start = int(n * 0.71)
y[intervention_start:] += 10

# Create dates
dates = pd.date_range(start='2014-01-05', periods=n, freq='D')
intervention_date = pd.Timestamp('2014-03-16')

# Fit model with exogenous regressor and bootstrap inference
ca = CausalArima(
    y=y,
    dates=dates,
    intervention_date=intervention_date,
    xreg=x1.reshape(-1, 1),  # Include covariate
    n_boot=1000              # Enable bootstrap inference
)
result = ca.fit()

# View summary
print(ca.summary())

Expected Output:

                              Estimate     SD    P-value (2-sided)
Point causal effect            12.257  1.211                 0.000
Cumulative causal effect      310.709  6.634                 0.000
Temporal average effect        10.357  0.221                 0.000

Note: Output format shown is for illustration. Actual summary() returns a pandas DataFrame with full inference statistics including left-sided, bidirectional, and right-sided p-values. Values are from the R reference implementation; your results may differ slightly due to bootstrap sampling and numerical precision.

Interpretation:

  • True intervention effect: +10 units
  • Estimated temporal average: 10.357 (within 4% of true value)
  • P-value ≈ 0: Strong evidence of a significant positive effect
  • Cumulative effect (310.7): Total impact over 30 post-intervention days

Visualizations

# Forecast plot: observed vs counterfactual
ca.plot(type='forecast')

# Impact plot: point effects and cumulative effects
ca.plot(type='impact')

# Residual diagnostics: ACF, PACF, Q-Q plot
ca.plot(type='residuals')

Main Features

  • Automatic ARIMA selection via pmdarima (similar to R's auto.arima)
  • Normal-based and bootstrap inference for uncertainty quantification
  • Three causal estimands: point effect, cumulative effect, temporal average
  • Visualization: forecast plots, impact plots, residual diagnostics
  • Exogenous regressors support
  • Full SARIMA support: Tested with 20+ seasonal model configurations

API

CausalArima Constructor

CausalArima(
    y,                          # Time series (array-like)
    dates,                      # Dates (DatetimeIndex)
    intervention_date,          # Intervention date (Timestamp)
    auto=True,                  # Auto-select ARIMA order
    order=(0, 0, 0),            # Manual ARIMA order (p, d, q)
    seasonal_order=(0, 0, 0, 1),# Seasonal order (P, D, Q, s)
    xreg=None,                  # Exogenous regressors
    ic='aic',                   # Information criterion
    n_boot=None,                # Bootstrap iterations
    alpha=0.05                  # Significance level
)

Methods

Method Description
fit() Fit model and compute effects
summary() Summary table of effects
plot(type=...) Visualizations ('forecast', 'impact', 'residuals'). Returns matplotlib.figure.Figure or dict of figures
impact() Detailed impact tables
get_residuals(standardized=False) Model residuals for diagnostics

Methodology

The C-ARIMA approach:

  1. Fits an ARIMA model on pre-intervention data
  2. Forecasts the counterfactual scenario (what would have happened without intervention)
  3. Computes causal effects as observed minus counterfactual
  4. Provides inference via analytical variance formulas or bootstrap

Causal Assumptions

The C-ARIMA method produces valid causal estimates under three key assumptions:

Assumption Description How to Check
Continuation Pre-intervention dynamics would have continued absent intervention Examine residual ACF; verify stable pre-trend
No Anticipation Intervention was not anticipated or acted upon early Verify timing; check for pre-intervention drift
Persistence Single, sustained intervention (not multiple or time-varying) Review study design
Exogeneity Exogenous regressors (xreg) are not affected by the intervention Verify regressors are determined outside the causal pathway

State-Space Consistency

The method assumes the ARIMA model correctly captures pre-intervention dynamics:

  • Residuals should be white noise (no autocorrelation)
  • Model order should be appropriate (use auto=True or validate with AIC/BIC)
  • Seasonal patterns must be explicitly modeled if present

Check with: ca.plot(type='residuals') to examine ACF/PACF of residuals.

What Happens When Assumptions Fail

Violation Consequence Symptom
Pre-trend instability Biased effect estimates Poor fit in pre-intervention period
Anticipation effects Effect appears before intervention Gradual drift near intervention date
Multiple interventions Confounded estimates Large residuals after first intervention
Wrong ARIMA order Invalid inference Autocorrelated residuals
Unmodeled seasonality Periodic bias Seasonal patterns in residuals

When NOT to Use C-ARIMA

This method may not be appropriate if:

  • Multiple interventions occur at different times
  • The intervention effect varies over time (use dynamic regression instead)
  • There's a control group available (use difference-in-differences or synthetic control)
  • Pre-intervention series is very short (< 30 observations recommended)
  • Strong non-stationarity that differencing cannot address

Recommended Diagnostics

result = ca.fit()

# 1. Residual diagnostics - should show no significant ACF/PACF
ca.plot(type='residuals')

# 2. Visual inspection - does the counterfactual look reasonable?
ca.plot(type='forecast')

# 3. Check model order (if auto=True)
print(f"Selected order: {result.order}")

Testing

pip install -e ".[dev]"
pytest tests/ -v

Current test suite includes:

  • Unit tests: Core functionality, edge cases, reporting, visualization
  • R comparison tests: Validates results match R implementation
  • Cross-validation: 211 parametrized pytest tests across 30 DGPs (7 metrics each)
# Run cross-validation only
pytest tests/test_dgp_cross_validation.py -v

# Filter by suite
pytest -m dgp_main -v        # DGPs 1-8 (basic ARIMA)
pytest -m dgp_sarima -v      # DGPs 9-18 (seasonal differencing)
pytest -m dgp_extended -v    # DGPs 19-30 (edge cases)

See VALIDATION.md for methodology and comparison/ for full reports.

Validation Summary

Cross-validation against the R CausalArima package across 30 Data Generating Processes:

Metric Tested Pass Status
Point causal effects 30 DGPs 30 All match (max diff < 1.8%)
Cumulative effects 30 DGPs 30 All match
Temporal average effects 30 DGPs 30 All match
Bidirectional p-values 30 DGPs 30 All match (< 0.01 abs diff)
Tau time series correlation 30 DGPs 30 All > 0.99 (29/30 = 1.0000)
Cumulative series correlation 30 DGPs 30 All = 1.0000
Standard deviations 30 DGPs 30 All within 20% relative tolerance

Note on SD tolerance: Standard deviations use a wider tolerance (20%) because Python (statsmodels state-space MLE) and R (CSS-ML) estimate the innovation variance (sigma2) differently. This is a known cross-library numerical characteristic, especially for models with seasonal differencing (D > 0) or high regular differencing (d >= 2). Point estimates and statistical significance are unaffected.

Known Limitations

  • SD tolerance for seasonal models: Standard deviations for seasonal and differenced ARIMA models may differ from R by up to 20% due to sigma2 estimation method differences (Python state-space MLE vs R CSS-ML). Point estimates and p-values match closely. See VALIDATION.md for details.
  • Bootstrap compatibility: Bootstrap simulation uses the statsmodels simulate() API, which may change in future statsmodels versions. Normal-based inference is unaffected.
  • Very short time series (< 20 observations) may produce unstable estimates

Next Steps

  • Exogenous regressor (xreg) cross-validation: Current 30 DGPs test ARIMA-only models. Add DGPs with exogenous regressors to validate ARIMAX behavior against R.
  • Bootstrap inference cross-validation: Add DGPs comparing bootstrap CIs and p-values between R and Python (current tests validate normal-based inference only).
  • Increase coverage for edge cases: Near-unit-root with seasonal components, very long series (n > 500), multiple exogenous regressors.
  • CI/CD integration: Add the cross-validation suite to CI pipeline (runs without R using pre-computed reference data).

License

GPL-3.0 (same as original R package)

References

@article{menchetti2023combining,
  title={Combining counterfactual outcomes and ARIMA models for policy evaluation},
  author={Menchetti, Fiammetta and Cipollini, Fabrizio and Mealli, Fabrizia},
  journal={The Econometrics Journal},
  year={2023}
}

Acknowledgments

This is a Python port of the CausalArima R package by Fabrizio Cipollini, Fiammetta Menchetti, and Eugenio Palmieri.

See Also

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

pycausalarima-0.1.0.tar.gz (58.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

pycausalarima-0.1.0-py3-none-any.whl (45.6 kB view details)

Uploaded Python 3

File details

Details for the file pycausalarima-0.1.0.tar.gz.

File metadata

  • Download URL: pycausalarima-0.1.0.tar.gz
  • Upload date:
  • Size: 58.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for pycausalarima-0.1.0.tar.gz
Algorithm Hash digest
SHA256 0b289d216150b64be46ea2e39076b8e05bac8311474527d1b08e4df5d5849a66
MD5 11383bdac712a203112bc180b9acfc39
BLAKE2b-256 ce8d7b164478da408828a7d9081109146080d021db4372f370c07cfad4684405

See more details on using hashes here.

Provenance

The following attestation bundles were made for pycausalarima-0.1.0.tar.gz:

Publisher: publish.yml on RobsonTigre/pycausalarima

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file pycausalarima-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: pycausalarima-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 45.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for pycausalarima-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 0301465e8b4cb96498bda4e7050670ac9e0f73de3b17952dbff516190c3bb181
MD5 2deee438a0aa3024ce5ec901cd90a945
BLAKE2b-256 d64cd8379f3db1a3ba46ecaab34679058cdf180519ae93850a5b25d7730eb8c6

See more details on using hashes here.

Provenance

The following attestation bundles were made for pycausalarima-0.1.0-py3-none-any.whl:

Publisher: publish.yml on RobsonTigre/pycausalarima

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page