Skip to content

Commit c78c168

Browse files
Merge pull request #30 from YuminosukeSato/remove/date-decomposition-retrospective
remove: DATE decomposition and retrospective mode
2 parents f3a1995 + 9f39487 commit c78c168

16 files changed

Lines changed: 18 additions & 1650 deletions

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,14 @@ The format is based on Keep a Changelog.
66

77
## [Unreleased]
88

9+
### Removed
10+
11+
- DATE decomposition (`ci.decompose()`, `DateDecomposition`, `EffectComponent`).
12+
The linear trend basis poses confounding risk with seasonal patterns; removed
13+
to prevent misinterpretation.
14+
- Retrospective mode (`mode="retrospective"`): treatment indicator covariate approach.
15+
Removed along with DATE decomposition.
16+
917
### Added
1018

1119
- Horseshoe prior as alternative to spike-and-slab via `ModelOptions(prior_type='horseshoe')`

README.md

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -203,8 +203,6 @@ Evidence per implementation (all verified from source code, not documentation cl
203203
| Seasonal component (`nseasons`, `season_duration`) | Matching | State-space model matching R bsts `AddSeasonal()` (±1% CI parity) |
204204
| Dynamic regression | Supported | Time-varying coefficients via random-walk FFBS; `dynamic_regression=True` |
205205
| Local linear trend | Supported | Opt in with `state_model="local_linear_trend"` |
206-
| DATE decomposition | Extended | Decomposes effects into spot/persistent/trend (arXiv:2602.00836) |
207-
| Retrospective mode | Extended | Treatment indicators as covariates; effects from beta posteriors (arXiv:2602.00836) |
208206
| Placebo test | Extended | Null distribution from pre-period splits |
209207
| Horseshoe prior | Extended | Continuous shrinkage alternative to spike-and-slab (Kohns & Bhattacharjee 2022) |
210208
| Conformal inference | Extended | Distribution-free prediction intervals |
@@ -220,8 +218,6 @@ Features that go beyond R's CausalImpact. These have no R equivalent.
220218

221219
| Feature | Method | What it does | Reference |
222220
|---|---|---|---|
223-
| DATE decomposition | `ci.decompose()` | Decomposes causal effect into spot, persistent, and trend | Schaffe-Odeleye et al. (2026), arXiv:2602.00836 |
224-
| Retrospective mode | `mode="retrospective"` | Treatment indicators as covariates; effects extracted from beta posteriors | Schaffe-Odeleye et al. (2026), arXiv:2602.00836 |
225221
| Placebo test | `ci.run_placebo_test()` | Validates effect against null distribution from pre-period splits | |
226222
| Conformal inference | `ci.run_conformal_analysis()` | Distribution-free prediction intervals | Vovk et al. (2005) |
227223
| DTW control selection | `select_controls()` | Automatic covariate selection via Dynamic Time Warping | Sakoe & Chiba (1978) |
@@ -255,8 +251,6 @@ Features that go beyond R's CausalImpact. These have no R equivalent.
255251
| `dynamic_regression` | `False` | Enable time-varying regression coefficients (random-walk beta) |
256252
| `state_model` | `"local_level"` | `"local_level"` or `"local_linear_trend"` |
257253
| `prior_type` | `"spike_slab"` | `"spike_slab"` or `"horseshoe"` (continuous shrinkage for dense DGP) |
258-
| `mode` | `"forward"` | `"forward"` (counterfactual prediction) or `"retrospective"` (treatment indicators as covariates) |
259-
260254
#### Methods and Properties
261255

262256
| Name | Returns | Description |
@@ -268,7 +262,6 @@ Features that go beyond R's CausalImpact. These have no R equivalent.
268262
| `summary_stats` | `dict` | Aggregate statistics (effect mean, CI, p-value, etc.) |
269263
| `posterior_inclusion_probs` | `ndarray \| None` | Posterior inclusion probability per covariate (spike-and-slab only) |
270264
| `posterior_shrinkage` | `ndarray \| None` | Mean shrinkage factor per covariate (horseshoe only) |
271-
| `decompose(alpha=None)` | `DateDecomposition` | DATE decomposition into spot/persistent/trend components |
272265
| `run_placebo_test(...)` | `PlaceboTestResults` | Placebo test for effect validation |
273266
| `run_conformal_analysis(...)` | `ConformalResults` | Distribution-free conformal prediction intervals |
274267

@@ -295,8 +288,6 @@ python/causal_impact/
295288
analysis.py # CausalAnalysis: effect computation, CI, p-values
296289
summary.py # SummaryFormatter: tabular and narrative reports
297290
plot.py # Plotter: matplotlib visualization
298-
decomposition.py # DATE decomposition (spot/persistent/trend)
299-
retrospective.py # Retrospective attribution mode
300291
301292
src/ (Rust)
302293
lib.rs # PyO3 entry point: run_gibbs_sampler()

docs/api.md

Lines changed: 1 addition & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,7 @@ ci = CausalImpact(data, pre_period, post_period, model_args=None, alpha=0.05)
2424
|---|---|---|
2525
| `summary(output="summary", digits=2)` | `str` | Tabular summary of causal effects. Set `output="report"` for narrative form. |
2626
| `report()` | `str` | Narrative interpretation of results (shortcut for `summary(output="report")`) |
27-
| `plot(metrics=None)` | `Figure` | Matplotlib figure with original/pointwise/cumulative panels. Pass a list like `["original", "cumulative"]` to select panels. Add `"decomposition"` for the DATE panel. |
28-
| `decompose(alpha=None)` | `DateDecomposition` | DATE decomposition of pointwise effects into spot/persistent/trend. Call before plotting with `"decomposition"` metric. |
27+
| `plot(metrics=None)` | `Figure` | Matplotlib figure with original/pointwise/cumulative panels. Pass a list like `["original", "cumulative"]` to select panels. |
2928
| `run_placebo_test(n_placebos=None, min_pre_length=3)` | `PlaceboTestResults` | Validates the causal effect against a null distribution from pre-period splits. |
3029
| `run_conformal_analysis(alpha=None)` | `ConformalResults` | Distribution-free prediction intervals via split conformal inference. |
3130

@@ -64,19 +63,6 @@ ci = CausalImpact(data, pre_period, post_period, model_args=opts)
6463
| `nseasons` | `int \| None` | `None` | Seasonal cycle count. `nseasons=1` is equivalent to no seasonal component. |
6564
| `season_duration` | `int \| None` | `None` | Duration of each seasonal block; defaults to 1 when `nseasons` is set. Requires `nseasons` to be set. |
6665

67-
### Analysis Mode
68-
69-
`mode` controls forward vs retrospective analysis. Pass via `model_args` dict (not `ModelOptions`).
70-
71-
| Value | Description |
72-
|---|---|
73-
| `"forward"` (default) | Counterfactual prediction: fit on pre-period, predict post-period |
74-
| `"retrospective"` | Treatment indicators as covariates: fit on entire series |
75-
76-
```python
77-
ci = CausalImpact(data, pre, post, model_args={"mode": "retrospective"})
78-
```
79-
8066
## `CausalImpactResults`
8167

8268
Returned by `ci._results`. A frozen dataclass containing all computed quantities.
@@ -138,7 +124,6 @@ print(ci.posterior_shrinkage) # mean(kappa_j), 0=included 1=shrunk
138124
### Incompatible combinations
139125

140126
- `prior_type='horseshoe'` + `dynamic_regression=True` raises `ValueError`
141-
- `prior_type='horseshoe'` + `mode='retrospective'` raises `ValueError`
142127

143128
### References
144129

@@ -150,54 +135,6 @@ print(ci.posterior_shrinkage) # mean(kappa_j), 0=included 1=shrunk
150135

151136
## Beyond R Extensions
152137

153-
### Retrospective Mode
154-
155-
In retrospective mode (`mode="retrospective"`), treatment indicator columns
156-
(spot, persistent, trend) are added as covariates and the BSTS model is fit on
157-
the entire time series. Treatment effects are extracted directly from the beta
158-
posteriors for the treatment columns, rather than from counterfactual predictions.
159-
160-
Key differences from forward mode:
161-
162-
- The model fits on all data (pre + post), not just pre-period
163-
- Spike-and-slab variable selection is auto-disabled
164-
- `ci._decomposition` is populated automatically (no need to call `ci.decompose()`)
165-
- `ci.decompose()` is still available but uses the forward-mode OLS projection
166-
167-
Reference: Schaffe-Odeleye et al. (2026), arXiv:2602.00836.
168-
169-
### `DateDecomposition`
170-
171-
Returned by `ci.decompose()` or auto-populated in retrospective mode.
172-
A frozen dataclass containing the DATE decomposition results.
173-
174-
Reference: Schaffe-Odeleye et al. (2026), arXiv:2602.00836.
175-
176-
| Field | Type | Description |
177-
|---|---|---|
178-
| `spot` | `EffectComponent` | Immediate impulse effect at intervention |
179-
| `persistent` | `EffectComponent` | Sustained level shift from intervention onward |
180-
| `trend` | `EffectComponent \| None` | Linearly growing/decaying effect. `None` when `T_post < 3`. |
181-
| `residual_mean` | `ndarray` | Mean residual trajectory after decomposition |
182-
| `design_matrix` | `ndarray` | The D matrix used for OLS projection |
183-
| `alpha` | `float` | Significance level used for credible intervals |
184-
185-
### `EffectComponent`
186-
187-
Each component of the DATE decomposition.
188-
189-
| Field | Type | Description |
190-
|---|---|---|
191-
| `name` | `str` | Component name: `"spot"`, `"persistent"`, or `"trend"` |
192-
| `coefficient` | `float` | Posterior mean of the OLS coefficient |
193-
| `ci_lower` | `float` | Lower credible interval bound on coefficient |
194-
| `ci_upper` | `float` | Upper credible interval bound on coefficient |
195-
| `samples` | `ndarray` | Per-sample trajectories, shape `(n_samples, T_post)` |
196-
| `coefficients` | `ndarray` | OLS coefficient per sample, shape `(n_samples,)` |
197-
| `mean` | `ndarray` | Posterior mean trajectory, shape `(T_post,)` |
198-
| `lower` | `ndarray` | Lower CI on trajectory, shape `(T_post,)` |
199-
| `upper` | `ndarray` | Upper CI on trajectory, shape `(T_post,)` |
200-
201138
### `PlaceboTestResults`
202139

203140
Returned by `ci.run_placebo_test()`. Validates the effect against a null distribution.

docs/compatibility-matrix.md

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,8 +82,6 @@ Features that extend beyond R CausalImpact. No R equivalent exists.
8282

8383
| Feature | Method | Description |
8484
|---|---|---|
85-
| DATE decomposition | `ci.decompose()` | Decomposes causal effect into spot/persistent/trend (arXiv:2602.00836) |
86-
| Retrospective mode | `mode="retrospective"` | Treatment indicators as covariates; effects from beta posteriors (arXiv:2602.00836) |
8785
| Placebo test | `ci.run_placebo_test()` | Validates effect against null distribution from pre-period splits |
8886
| Conformal inference | `ci.run_conformal_analysis()` | Distribution-free prediction intervals (Vovk et al., 2005) |
8987
| DTW control selection | `select_controls()` | Automatic covariate selection via Dynamic Time Warping |

docs/examples.md

Lines changed: 1 addition & 122 deletions
Original file line numberDiff line numberDiff line change
@@ -437,131 +437,10 @@ print(ci.posterior_inclusion_probs)
437437
| No Effect (negative control) | 0.0 | -0.55 | [-1.50, 0.48] | 0.118 | No (correct) |
438438
| Multiple Covariates | 5.0 | 6.53 | [3.64, 9.67] | 0.001 | Yes |
439439

440-
---
441-
442-
## 7. DATE Decomposition (Spot / Persistent / Trend)
443-
444-
Decompose the estimated causal effect into three interpretable components.
445-
This example uses synthetic data with a known persistent effect of +3 and
446-
a trend of +0.1 per time step.
447-
448-
Reference: Schaffe-Odeleye et al. (2026), arXiv:2602.00836.
449-
450-
```python
451-
rng = np.random.default_rng(42)
452-
453-
n_pre, n_post = 100, 30
454-
n = n_pre + n_post
455-
x = np.zeros(n)
456-
x[0] = 100
457-
for t in range(1, n):
458-
x[t] = 0.999 * x[t - 1] + rng.normal(0, 1)
459-
460-
y = 1.2 * x + rng.normal(0, 1, size=n)
461-
# Inject a persistent shift + trend
462-
y[n_pre:] += 3.0 + 0.1 * np.arange(n_post)
463-
464-
dates = pd.date_range("2020-01-01", periods=n, freq="D")
465-
data = pd.DataFrame({"y": y, "x": x}, index=dates)
466-
pre_period = ["2020-01-01", "2020-04-09"]
467-
post_period = ["2020-04-10", "2020-05-09"]
468-
469-
ci = CausalImpact(data, pre_period, post_period, model_args={"seed": 42})
470-
dec = ci.decompose()
471-
472-
print(f"Spot: {dec.spot.coefficient:+.2f} [{dec.spot.ci_lower:+.2f}, {dec.spot.ci_upper:+.2f}]")
473-
print(f"Persistent: {dec.persistent.coefficient:+.2f} [{dec.persistent.ci_lower:+.2f}, {dec.persistent.ci_upper:+.2f}]")
474-
if dec.trend is not None:
475-
print(f"Trend: {dec.trend.coefficient:+.2f} [{dec.trend.ci_lower:+.2f}, {dec.trend.ci_upper:+.2f}]")
476-
```
477-
478-
### Interpreting the results
479-
480-
| Component | True value | What it means |
481-
|---|---|---|
482-
| Spot | 0.0 | No immediate one-time impact |
483-
| Persistent | 3.0 | Permanent baseline shift |
484-
| Trend | 0.1 | Effect grows by 0.1 per time step |
485-
486-
### Plotting with decomposition
487-
488-
```python
489-
fig = ci.plot(metrics=["original", "pointwise", "cumulative", "decomposition"])
490-
fig.savefig("example_decomposition.png", dpi=150, bbox_inches="tight")
491-
```
492-
493-
The fourth panel shows the three components with credible intervals.
494-
495-
---
496-
497-
## 8. Retrospective Attribution Mode
498-
499-
In retrospective mode, treatment indicator columns (spot, persistent, trend)
500-
are added as covariates and the model is fit on the entire time series.
501-
Treatment effects are extracted directly from the beta posteriors.
502-
503-
This approach avoids the counterfactual prediction step of forward mode and
504-
instead estimates treatment effects within a single model fit.
505-
506-
```python
507-
import numpy as np
508-
import pandas as pd
509-
from causal_impact import CausalImpact
510-
511-
rng = np.random.default_rng(42)
512-
513-
n_pre, n_post = 200, 50
514-
n = n_pre + n_post
515-
y = np.cumsum(rng.normal(0, 0.3, n)) + 50
516-
y += rng.normal(0, 0.5, n)
517-
# Inject a persistent level shift of +5 at intervention
518-
y[n_pre:] += 5.0
519-
520-
dates = pd.date_range("2020-01-01", periods=n, freq="D")
521-
data = pd.DataFrame({"y": y}, index=dates)
522-
pre_period = [dates[0], dates[n_pre - 1]]
523-
post_period = [dates[n_pre], dates[-1]]
524-
525-
ci = CausalImpact(
526-
data, pre_period, post_period,
527-
model_args={
528-
"niter": 2000, "nwarmup": 1000, "seed": 42,
529-
"mode": "retrospective",
530-
"prior_level_sd": 0.001,
531-
},
532-
)
533-
534-
# Decomposition is auto-populated in retrospective mode
535-
dec = ci._decomposition
536-
print(f"Spot: {dec.spot.coefficient:+.2f}")
537-
print(f"Persistent: {dec.persistent.coefficient:+.2f}")
538-
if dec.trend is not None:
539-
print(f"Trend: {dec.trend.coefficient:+.2f}")
540-
541-
# Standard summary and plot still work
542-
print(ci.summary())
543-
fig = ci.plot()
544-
```
545-
546-
### When to use retrospective mode
547-
548-
- Forward mode (default): Use when you have a clear pre/post split and want counterfactual predictions. Standard CausalImpact workflow.
549-
- Retrospective mode: Use when you want to decompose the treatment effect into spot/persistent/trend directly from the model, or when the treatment timing is known and you want a single-model-fit approach.
550-
551-
### Note on `prior_level_sd`
552-
553-
In retrospective mode, the local level state (random walk) and the persistent
554-
treatment indicator (step function) are partially collinear. Setting
555-
`prior_level_sd` to a small value (e.g., 0.001) constrains the state variation
556-
and forces the model to attribute level shifts to the treatment columns.
557-
558-
---
559-
560440
## Summary of All Examples
561441

562-
All eight examples produce correct results:
442+
All six examples produce correct results:
563443

564444
- When there is a true effect, the 95% credible interval contains the true value
565445
- When there is no effect, the model correctly reports non-significance
566446
- Results are consistent with the R CausalImpact package
567-
- Retrospective mode extracts treatment components directly from beta posteriors

0 commit comments

Comments
 (0)