Skip to content

Commit cc2028f

Browse files
release: v1.0.0
1 parent e0a50b1 commit cc2028f

23 files changed

Lines changed: 902 additions & 74 deletions

.github/workflows/release.yml

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,63 @@ jobs:
5454
name: sdist
5555
path: dist/*.tar.gz
5656

57+
smoke-test-wheels:
58+
needs: build-wheels
59+
runs-on: ${{ matrix.os }}
60+
strategy:
61+
fail-fast: false
62+
matrix:
63+
include:
64+
- os: ubuntu-latest
65+
target: x86_64
66+
- os: macos-14
67+
target: aarch64
68+
- os: macos-latest
69+
target: x86_64
70+
- os: windows-latest
71+
target: x64
72+
steps:
73+
- uses: actions/setup-python@v5
74+
with:
75+
python-version: "3.12"
76+
- uses: actions/download-artifact@v4
77+
with:
78+
name: wheels-${{ matrix.os }}-${{ matrix.target }}
79+
path: dist
80+
- name: Install built wheel
81+
shell: python
82+
run: |
83+
import glob
84+
import subprocess
85+
import sys
86+
87+
wheels = glob.glob("dist/*.whl")
88+
assert wheels, "No wheel artifact downloaded"
89+
subprocess.check_call([sys.executable, "-m", "pip", "install", wheels[0]])
90+
- name: Smoke test wheel
91+
shell: python
92+
run: |
93+
import numpy as np
94+
import pandas as pd
95+
from causal_impact import CausalImpact, ModelOptions
96+
97+
dates = pd.date_range("2020-01-01", periods=24, freq="D")
98+
x = np.linspace(0.0, 1.0, 24)
99+
y = 5.0 + 0.2 * np.arange(24) + 1.5 * x
100+
y[18:] += 2.0
101+
data = pd.DataFrame({"y": y, "x1": x}, index=dates)
102+
103+
ci = CausalImpact(
104+
data,
105+
["2020-01-01", "2020-01-18"],
106+
["2020-01-19", "2020-01-24"],
107+
model_args=ModelOptions(niter=100, nwarmup=50, seed=42),
108+
)
109+
assert ci.summary()
110+
assert len(ci.inferences.columns) >= 11
111+
57112
publish:
58-
needs: [build-wheels, build-sdist]
113+
needs: [build-sdist, smoke-test-wheels]
59114
runs-on: ubuntu-latest
60115
environment: pypi
61116
permissions:

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ __pycache__/
44
*.pyc
55
*.pyo
66
*.so
7+
*.dSYM/
78
*.egg-info/
89
dist/
910
build/

CHANGELOG.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# Changelog
2+
3+
All notable changes to this project will be documented in this file.
4+
5+
The format is based on Keep a Changelog.
6+
7+
## [1.0.0] - 2026-03-23
8+
9+
### Added
10+
11+
- Added `state_model` with `local_level` and `local_linear_trend` support.
12+
- Added `actual` and `predictions_sd` to `ci.inferences`.
13+
- Added `py.typed` for PEP 561 type discovery.
14+
- Added wheel smoke tests to the release workflow.
15+
16+
### Changed
17+
18+
- Made summary and report CI labels respect `alpha`.
19+
- Unified `expected_model_size` defaults at `2`.
20+
- Clarified installation guidance around binary wheels versus source builds.
21+
22+
## [0.3.0]
23+
24+
### Added
25+
26+
- Added dynamic regression with time-varying coefficients.
27+
- Added seasonal components and MkDocs documentation site.
28+
29+
### Changed
30+
31+
- Improved numerical equivalence coverage against R fixtures.

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "causal_impact_core"
3-
version = "0.3.0"
3+
version = "1.0.0"
44
edition = "2021"
55

66
[lib]

README.md

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@ If any of these assumptions are violated, the causal estimate will be unreliable
1717

1818
## Installation
1919

20-
Requires Python 3.10+ and a Rust toolchain.
20+
Requires Python 3.10+.
21+
Binary wheels are intended for supported platforms, so Rust is only required
22+
when building from source.
2123

2224
```bash
2325
pip install bsts-causalimpact
@@ -113,12 +115,12 @@ Tests run on every commit with seed-fixed MCMC for deterministic reproduction.
113115
| Post-period Random Walk propagation | Matching | Forward simulation from last pre-period state |
114116
| Data standardization (standardize.data=TRUE) | Matching | (y - mean) / sd using pre-period moments |
115117
| prior.level.sd = 0.01 | Matching | Same default, same semantics |
116-
| Spike-and-slab variable selection | Partial | Coordinate-wise sampling works; prior parameters (expected.r2, prior.df) are approximate |
117-
| expected.model.size | Partial | `CausalImpact` preserves the legacy default `2`; `ModelOptions` keeps explicit default `1` |
118-
| expected.r2 = 0.8, prior.df = 50 | Partial | Static regression prior is tuned for close R parity, not a byte-for-byte port |
118+
| Spike-and-slab variable selection | Matching | Coordinate-wise sampling with StudentSpikeSlabPrior defaults (`expected.r2=0.8`, `prior.df=50`, `prior.information.weight=0.01`, `diagonal.shrinkage=0.5`) |
119+
| expected.model.size | Matching | Unified default `2` in `CausalImpact` and `ModelOptions` |
120+
| expected.r2 = 0.8, prior.df = 50 | Matching | Same documented residual variance prior defaults as BoomSpikeSlab / bsts |
119121
| Seasonal component (`nseasons`, `season_duration`) | Supported | R-compatible API with seasonal fixture coverage |
120122
| Dynamic regression | Supported | Time-varying coefficients via random-walk FFBS; `dynamic_regression=True` |
121-
| Local linear trend | Planned | R uses AddLocalLevel only by default; trend option exists but not ported |
123+
| Local linear trend | Supported | Opt in with `state_model="local_linear_trend"` |
122124

123125
Covariate CI bounds are enforced twice: the legacy parity fixture remains tighter than
124126
Phase 2 requirements, and a separate Phase 2 acceptance test keeps the threshold at `±3%`.
@@ -145,10 +147,11 @@ Phase 2 requirements, and a separate Phase 2 acceptance test keeps the threshold
145147
| `seed` | 0 | Random seed for reproducibility |
146148
| `prior_level_sd` | 0.01 | Prior standard deviation for the local level |
147149
| `standardize_data` | `True` | Standardize data before fitting |
148-
| `expected_model_size` | 2 | Expected number of active covariates (spike-and-slab prior); `ModelOptions` keeps `1` |
150+
| `expected_model_size` | 2 | Expected number of active covariates (spike-and-slab prior) |
149151
| `nseasons` | `None` | Optional seasonal cycle count (R-compatible API) |
150152
| `season_duration` | `None` | Optional duration of each seasonal block; defaults to `1` when `nseasons` is set |
151153
| `dynamic_regression` | `False` | Enable time-varying regression coefficients (random-walk beta) |
154+
| `state_model` | `"local_level"` | `"local_level"` or `"local_linear_trend"` |
152155

153156
#### Methods and Properties
154157

@@ -157,7 +160,7 @@ Phase 2 requirements, and a separate Phase 2 acceptance test keeps the threshold
157160
| `summary(output="summary")` | `str` | Tabular summary of causal effects |
158161
| `report()` | `str` | Narrative interpretation of results |
159162
| `plot(metrics=None)` | `Figure` | Matplotlib figure with original/pointwise/cumulative panels |
160-
| `inferences` | `DataFrame` | Per-timestep effects, predictions, and credible intervals |
163+
| `inferences` | `DataFrame` | Per-timestep actuals, predictions, prediction s.d., and effect intervals |
161164
| `summary_stats` | `dict` | Aggregate statistics (effect mean, CI, p-value, etc.) |
162165
| `posterior_inclusion_probs` | `ndarray \| None` | Posterior inclusion probability per covariate |
163166

docs/api.md

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ ci = CausalImpact(data, pre_period, post_period, model_args=None, alpha=0.05)
3030

3131
| Property | Type | Description |
3232
|---|---|---|
33-
| `inferences` | `DataFrame` | Per-timestep effects, predictions, and credible intervals |
33+
| `inferences` | `DataFrame` | Per-timestep actuals, predictions, prediction s.d., and effect intervals |
3434
| `summary_stats` | `dict` | Aggregate statistics (effect mean, CI, p-value, etc.) |
3535
| `posterior_inclusion_probs` | `ndarray \| None` | Posterior inclusion probability per covariate (requires covariates) |
3636

@@ -53,15 +53,12 @@ ci = CausalImpact(data, pre_period, post_period, model_args=opts)
5353
| `seed` | `int` | 0 | Random seed for reproducibility |
5454
| `prior_level_sd` | `float` | 0.01 | Prior standard deviation for the local level |
5555
| `standardize_data` | `bool` | `True` | Standardize data before fitting |
56-
| `expected_model_size` | `int` | 1 | Expected number of active covariates for spike-and-slab prior |
56+
| `expected_model_size` | `int` | 2 | Expected number of active covariates for spike-and-slab prior |
57+
| `dynamic_regression` | `bool` | `False` | Enable time-varying regression coefficients |
58+
| `state_model` | `str` | `"local_level"` | `"local_level"` or `"local_linear_trend"` |
5759
| `nseasons` | `int \| None` | `None` | Seasonal cycle count |
5860
| `season_duration` | `int \| None` | `None` | Duration of each seasonal block; defaults to 1 when `nseasons` is set |
5961

60-
!!! note "expected_model_size defaults"
61-
`CausalImpact` sets `expected_model_size=2` by default (matching R).
62-
`ModelOptions` keeps `expected_model_size=1` as the explicit default.
63-
When passing a `ModelOptions` instance, the `ModelOptions` value takes precedence.
64-
6562
## `CausalImpactResults`
6663

6764
Returned by `ci._results`. A frozen dataclass containing all computed quantities.
@@ -72,14 +69,15 @@ Returned by `ci._results`. A frozen dataclass containing all computed quantities
7269
|---|---|---|
7370
| `actual` | `ndarray` | Observed y values in the post period |
7471
| `point_effects` | `ndarray` | Mean effect per time point |
75-
| `point_effect_lower` | `ndarray` | Lower 95% CI per time point |
76-
| `point_effect_upper` | `ndarray` | Upper 95% CI per time point |
72+
| `point_effect_lower` | `ndarray` | Lower pointwise credible interval per time point |
73+
| `point_effect_upper` | `ndarray` | Upper pointwise credible interval per time point |
7774
| `point_effect_mean` | `float` | Mean of point effects across time |
7875
| `ci_lower` | `float` | Lower CI bound on average effect |
7976
| `ci_upper` | `float` | Upper CI bound on average effect |
8077
| `cumulative_effect_total` | `float` | Total cumulative effect |
8178
| `relative_effect_mean` | `float` | Relative effect (effect / predicted) |
8279
| `p_value` | `float` | Bayesian one-sided tail probability |
8380
| `predictions_mean` | `ndarray` | Mean counterfactual prediction |
81+
| `predictions_sd` | `ndarray` | Posterior standard deviation of the counterfactual prediction |
8482
| `predictions_lower` | `ndarray` | Lower CI on counterfactual |
8583
| `predictions_upper` | `ndarray` | Upper CI on counterfactual |

docs/compatibility-matrix.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@ Comparison of features between R CausalImpact (bsts 1.4.1) and this Python imple
77
| Feature | R | Python | Notes |
88
|---|---|---|---|
99
| Local level | Yes | Yes | Identical algorithm |
10-
| Local linear trend | Yes | No | Not yet implemented |
10+
| Local linear trend | Yes | Yes | `state_model="local_linear_trend"` |
1111
| Seasonality | Yes | Yes | R-compatible API with seasonal fixture coverage |
12-
| Dynamic regression | Yes | No | Not yet implemented |
12+
| Dynamic regression | Yes | Yes | `dynamic_regression=True` |
1313
| Regression (static) | Yes | Yes | Identical algorithm |
1414

1515
## MCMC Parameters
@@ -21,7 +21,8 @@ Comparison of features between R CausalImpact (bsts 1.4.1) and this Python imple
2121
| season.duration | Yes | Yes | `ModelOptions.season_duration` or `model_args["season.duration"]` |
2222
| prior.level.sd | Yes | Yes | Same default (0.01) |
2323
| standardize.data | Yes | Yes | Same default (True) |
24-
| expected.model.size | Yes | Yes | Legacy `CausalImpact` default is 2; `ModelOptions` keeps 1 |
24+
| expected.model.size | Yes | Yes | Unified default `2` |
25+
| state model selection | Via bsts state spec | Yes | `state_model="local_level"` or `"local_linear_trend"` |
2526

2627
## Warmup Semantics
2728

docs/index.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ No TensorFlow required. 10-30x faster than R.
1111
pip install bsts-causalimpact
1212
```
1313

14-
Requires Python 3.10+ and a Rust toolchain (only for building from source).
14+
Requires Python 3.10+.
15+
Rust is only needed when building from source instead of installing a wheel.
1516

1617
## Example: Measuring the Effect of an Intervention
1718

@@ -168,6 +169,8 @@ print(ci.posterior_inclusion_probs)
168169
| `prior_level_sd` | 0.01 | Prior standard deviation for the local level |
169170
| `standardize_data` | `True` | Standardize data before fitting |
170171
| `expected_model_size` | 2 | Expected number of active covariates (spike-and-slab prior) |
172+
| `dynamic_regression` | `False` | Enable time-varying regression coefficients |
173+
| `state_model` | `"local_level"` | `"local_level"` or `"local_linear_trend"` |
171174
| `nseasons` | `None` | Seasonal cycle count |
172175
| `season_duration` | `None` | Duration of each seasonal block (defaults to 1 when `nseasons` is set) |
173176

docs/migration-from-r.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ fig = ci.plot()
4343
| `season.duration` | `season_duration` or `model_args["season.duration"]` | `1` when `nseasons` is set |
4444
| `prior.level.sd` | `prior_level_sd` | 0.01 |
4545
| `standardize.data` | `standardize_data` | True |
46-
| `expected.model.size` | `expected_model_size` | 2 in `CausalImpact`; `ModelOptions` keeps 1 |
46+
| `expected.model.size` | `expected_model_size` | 2 |
4747

4848
## Data Format
4949

@@ -85,9 +85,11 @@ This library verifies ±3% agreement with R CausalImpact on point estimates and
8585

8686
Differences arise from independent RNG implementations (R's `set.seed` vs Rust's `ChaCha8Rng`), not from algorithmic differences.
8787

88+
Use `model_args={"state_model": "local_linear_trend"}` when you want the
89+
optional local linear trend state instead of the default local level model.
90+
8891
## What Is Not Supported
8992

9093
- Custom bsts model objects
91-
- `model.args$dynamic.regression`
9294

9395
These features may be added in future versions.

0 commit comments

Comments
 (0)