Skip to content

Commit 35ebd49

Browse files
docs: update seasonal model documentation with state-space details and tightened tolerances
- Update CI tolerance from ±5% to ±1% for seasonal (README, compatibility-matrix) - Add season_duration usage examples to docs/examples.md - Document state-space seasonal model matching R bsts AddSeasonal()
1 parent bfece49 commit 35ebd49

3 files changed

Lines changed: 35 additions & 4 deletions

File tree

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ Tests run on every commit with seed-fixed MCMC for deterministic reproduction.
103103
|---|---|---|
104104
| `point_effect_mean` | ±3% relative | Passing on core scenarios |
105105
| `cumulative_effect_total` | ±3% relative | Passing on core scenarios |
106-
| `ci_lower` / `ci_upper` | Tight parity | `±1.5%` no-covariates, `±1%` covariates, explicit Phase 2 acceptance `±3%`, seasonal fixture `±5%` |
106+
| `ci_lower` / `ci_upper` | Tight parity | `±1%` no-covariates, `±1%` covariates, explicit Phase 2 acceptance `±3%`, seasonal `±1%` |
107107
| `p_value` | Significance match | Classification at alpha=0.05 |
108108

109109
### What is matching R and what is not
@@ -118,7 +118,7 @@ Tests run on every commit with seed-fixed MCMC for deterministic reproduction.
118118
| 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`) |
119119
| expected.model.size | Matching | Unified default `2` in `CausalImpact` and `ModelOptions` |
120120
| expected.r2 = 0.8, prior.df = 50 | Matching | Same documented residual variance prior defaults as BoomSpikeSlab / bsts |
121-
| Seasonal component (`nseasons`, `season_duration`) | Supported | R-compatible API with seasonal fixture coverage |
121+
| Seasonal component (`nseasons`, `season_duration`) | Matching | State-space model matching R bsts `AddSeasonal()` (±1% CI parity) |
122122
| Dynamic regression | Supported | Time-varying coefficients via random-walk FFBS; `dynamic_regression=True` |
123123
| Local linear trend | Supported | Opt in with `state_model="local_linear_trend"` |
124124

docs/compatibility-matrix.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ Comparison of features between R CausalImpact (bsts 1.4.1) and this Python imple
88
|---|---|---|---|
99
| Local level | Yes | Yes | Identical algorithm |
1010
| Local linear trend | Yes | Yes | `state_model="local_linear_trend"` |
11-
| Seasonality | Yes | Yes | R-compatible API with seasonal fixture coverage |
11+
| Seasonality | Yes | Yes | State-space model matching R bsts `AddSeasonal()` |
1212
| Dynamic regression | Yes | Yes | `dynamic_regression=True` |
1313
| Regression (static) | Yes | Yes | Identical algorithm |
1414

@@ -71,7 +71,7 @@ Comparison of features between R CausalImpact (bsts 1.4.1) and this Python imple
7171
|---|---|---|
7272
| point_effect_mean | ±3% relative | Passing |
7373
| cumulative_effect_total | ±3% relative | Passing |
74-
| ci_lower / ci_upper | Tight parity (`±1.5%` no-cov, `±1%` covariates, `±5%` seasonal) | Passing |
74+
| ci_lower / ci_upper | Tight parity (`±1%` no-cov, `±1%` covariates, `±1%` seasonal) | Passing |
7575
| p_value significance | Match at alpha=0.05 | Passing |
7676

7777
Tests run against R CausalImpact 1.4.1 fixtures on every PR.

docs/examples.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,37 @@ Posterior prob. of a causal effect: 99.95%
183183
True effect = 8.0, estimated = 7.57. The 95% CI [5.99, 9.18] contains the true
184184
value.
185185

186+
### How it works
187+
188+
The seasonal component uses a state-space model matching R bsts `AddSeasonal()`.
189+
The state vector is `[μ_t, s_1(t), ..., s_{S-1}(t)]` where `S = nseasons`.
190+
Seasonal states evolve via a sum-to-zero transition: the next season equals
191+
the negative sum of the previous `S-1` seasons plus noise.
192+
193+
### Using `season_duration`
194+
195+
When each season spans multiple time steps (e.g., daily data with weekly
196+
seasonality where each day of the week lasts one step), set `season_duration`
197+
accordingly. The seasonal transition only fires at season boundaries
198+
(`t % season_duration == 0`); in between, the seasonal state is frozen.
199+
200+
```python
201+
# Daily data with 7-day weekly pattern (default: season_duration=1)
202+
ci = CausalImpact(
203+
data, pre_period, post_period,
204+
model_args={"nseasons": 7, "season_duration": 1, "seed": 42},
205+
)
206+
207+
# Bi-weekly data with 26 two-week seasons per year
208+
ci = CausalImpact(
209+
data, pre_period, post_period,
210+
model_args={"nseasons": 26, "season_duration": 2, "seed": 42},
211+
)
212+
```
213+
214+
When `season_duration` is omitted, it defaults to 1 (every time step is a new
215+
season).
216+
186217
---
187218

188219
## 4. Dynamic Regression

0 commit comments

Comments
 (0)