Skip to content

Commit 7e864e1

Browse files
docs: correct sp.dml_panel + clusterSC citations
Two citation-correctness fixes: 1. sp.dml_panel (originally shipped in v1.7) was attributed to "Semenova & Chernozhukov (2023) Econometrics Journal 26(2)" — that paper does not exist (the cited title belongs to Semenova & Chernozhukov 2021 ECTJ 24(2) on CATE / debiased ML, unrelated to long-panel PLR with fixed effects). The estimator's actual reference is Clarke & Polselli (2025) "Double Machine Learning for Static Panel Models with Fixed Effects" Econometrics Journal 29(1) 69-86, DOI 10.1093/ectj/utaf011, arXiv:2312.08174. Updated callsites: paper.bib (new clarke2025double entry), src/statspai/dml/panel_dml.py (module docstring + within- transform comment), src/statspai/dml/__init__.py (lazy-export tag), src/statspai/registry.py (FunctionSpec description + reference field), README.md (Long-panel Double-ML row), and the historical v1.7 CHANGELOG entry (annotated, not silently rewritten). No code logic, numerical path, API signature, or test changed — pure citation correction. 2. sp.synth(method='cluster') method-citations registry had "Rho, S., Yan, X. et al. (2025)"; the actual ClusterSC paper (arXiv:2503.21629) is Rho, Tang, Bergam, Cummings, Misra. src/statspai/synth/report.py "Yan, X." -> "Tang, A." (paper.bib was already correct). Also: tests/test_audit_citations.py CLI smoke test now accepts exit code 1 (auditor ran successfully and emitted findings) in addition to 0/2, with an explicit `Traceback` not-in-stderr guard so a real crash still fails the test. Refs verified via Crossref `10.1093/ectj/utaf011` (clarke2025double) and arXiv 2503.21629 (rho2025clustersc) on 2026-05-06.
1 parent 44ebfda commit 7e864e1

8 files changed

Lines changed: 96 additions & 25 deletions

File tree

CHANGELOG.md

Lines changed: 48 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,41 @@
22

33
All notable changes to StatsPAI will be documented in this file.
44

5+
## [Unreleased]
6+
7+
### Docs — `sp.dml_panel` citation correction
8+
9+
- ⚠️ **Docs-only correction**`sp.dml_panel` (originally shipped in
10+
v1.7) was attributed in its docstring, registry entry, README blurb,
11+
and CHANGELOG release note to *"Semenova & Chernozhukov (2023)
12+
Econometrics Journal 26(2), Debiased Machine Learning of Conditional
13+
Average Treatment Effects and Other Causal Functions."* That
14+
citation is **fabricated**: independent verification via Crossref
15+
and the Oxford ECTJ issue TOC confirms no Semenova or Chernozhukov
16+
paper appears anywhere in *Econometrics Journal* 26(2) (May 2023),
17+
and the cited title in fact belongs to Semenova & Chernozhukov
18+
**(2021)** *ECTJ* **24(2)** 264-289 (DOI 10.1093/ectj/utaa027) — a
19+
paper on CATE / debiased ML for causal functions, unrelated to
20+
long-panel PLR with fixed effects.
21+
- The estimator's actual reference is **Clarke, P. S. & Polselli, A.
22+
(2025).** *"Double Machine Learning for Static Panel Models with
23+
Fixed Effects."* *The Econometrics Journal* **29(1)** 69-86, DOI
24+
[10.1093/ectj/utaf011](https://doi.org/10.1093/ectj/utaf011),
25+
arXiv:2312.08174. The paper specifies the within-group / first-
26+
difference transform, block-k-fold cross-fitting that allocates
27+
each unit's full time series to a single fold, and cluster-robust
28+
variance at the unit level — point-for-point match with the
29+
StatsPAI implementation. Companion Stata package: `xtdml`.
30+
- Updated callsites: [`paper.bib`](paper.bib) (new
31+
`clarke2025double` entry), [`src/statspai/dml/panel_dml.py`](src/statspai/dml/panel_dml.py)
32+
(module docstring + within-transform comment), [`src/statspai/dml/__init__.py`](src/statspai/dml/__init__.py)
33+
(lazy-export tag), [`src/statspai/registry.py`](src/statspai/registry.py)
34+
(FunctionSpec description + reference field), [`README.md`](README.md)
35+
(Long-panel Double-ML row), and the historical v1.7 entry below
36+
(annotated, not silently rewritten). No code logic, numerical path,
37+
API signature, or test changed — pure citation correction.
38+
- Refs verified via Crossref (DOI 10.1093/ectj/utaf011) and OpenAlex.
39+
540
## [1.15.0] — 2026-05-05
641

742
### Docs — v1.14 GPU sprint follow-up
@@ -4468,12 +4503,19 @@ frontier estimators (`sp.mr_lap` etc.), one long-panel DML estimator
44684503

44694504
### Added — v1.7 long-panel DML (`src/statspai/dml/panel_dml.py`)
44704505

4471-
- **`sp.dml_panel`** — Long-panel Double/Debiased ML (Semenova-
4472-
Chernozhukov 2023 simplified). Absorbs unit (and optional time)
4473-
fixed effects via within-transform, cross-fits ML nuisance learners
4474-
with folds that **split units** (Liang-Zeger compatible), reports
4475-
cluster-robust SE at the unit level. PLR moment for continuous or
4476-
binary treatment; empty-covariate fallback reduces to pure FE-OLS.
4506+
- **`sp.dml_panel`** — Long-panel Double/Debiased ML for static panel
4507+
models with fixed effects (Clarke & Polselli 2025 simplified).
4508+
Absorbs unit (and optional time) fixed effects via within-transform,
4509+
cross-fits ML nuisance learners with folds that **split units**
4510+
(Liang-Zeger compatible), reports cluster-robust SE at the unit
4511+
level. PLR moment for continuous or binary treatment;
4512+
empty-covariate fallback reduces to pure FE-OLS.
4513+
*(Citation corrected post-v1.15: the original v1.7 release note
4514+
attributed this estimator to a "Semenova-Chernozhukov 2023
4515+
Econometrics Journal 26(2)" paper that does not exist; the actual
4516+
reference is Clarke & Polselli (2025) ECTJ 29(1) 69-86, DOI
4517+
10.1093/ectj/utaf011, arXiv:2312.08174. See [Unreleased] for the
4518+
full audit.)*
44774519

44784520
### Added — dispatcher + registry wiring
44794521

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -284,7 +284,7 @@ StatsPAI 1.6.0 is a **pure-additive** minor release pushing two competitive axes
284284
| **LLM × DAG (closed-loop)** | **`sp.llm_dag_constrained`** — iterate **propose → constrained PC → CI-test validate → demote** until convergence. Every kept edge carries `llm_score` + `ci_pvalue` + `source``{required, forbidden, demoted, ci-test}`. `result.to_dag()` round-trips into `statspai.dag.DAG`. **`sp.llm_dag_validate`** audits any declared DAG edge-by-edge for spuriousness. **`sp.pc_algorithm(forbidden=, required=)`** injects background knowledge into PC (default `None` preserves prior contract). Family guide: [`docs/guides/llm_dag_family.md`](docs/guides/llm_dag_family.md). |
285285
| **Causal × Text (experimental)** | **`sp.text_treatment_effect`** — Veitch-Wang-Blei (2020 UAI) text-as-treatment ATE via embedding-projected OLS with HC1 SEs; hash embedder default (deterministic, dependency-free), lazy `sbert` optional. **`sp.llm_annotator_correct`** — Egami-Hinck-Stewart-Wei (2024) Hausman-style measurement-error correction for binary LLM-derived treatments; raises `IdentificationFailure` when the LLM has no information. Both subclass `CausalResult` and ship full agent-card metadata. Family guide: [`docs/guides/causal_text_family.md`](docs/guides/causal_text_family.md). |
286286
| **MR frontier (5 new)** | **`sp.mr_lap`** (Burgess-Davies-Thompson 2016 sample-overlap-corrected IVW). **`sp.mr_clust`** (Foley-Mason-Kirk-Burgess 2021 clustered MR via finite Gaussian mixture on Wald ratios, BIC-selected K). **`sp.grapple`** (Wang-Zhao-Bowden-Hemani 2021 profile-likelihood MR with joint weak-instrument + balanced-pleiotropy robustness). **`sp.mr_cml`** (Xue-Shen-Pan 2021 constrained maximum-likelihood MR with L0-sparse pleiotropy, MR-cML-BIC). **`sp.mr_raps`** (Zhao-Wang-Hemani-Bowden-Small 2020 *Annals of Statistics* robust adjusted profile score with Tukey biweight loss). `sp.mr(method='lap' \| 'clust' \| 'grapple' \| 'cml' \| 'raps')` dispatcher routes all five. 41 new tests in `tests/test_mr_frontier.py`. |
287-
| **Long-panel Double-ML** | **`sp.dml_panel`**Semenova-Chernozhukov (2023) long-panel DML: absorbs unit (+ optional time) fixed effects via within-transform, cross-fits ML nuisance learners with unit-split folds (Liang-Zeger compatible), reports cluster-robust SE at the unit level. Empty-covariate fallback reduces to pure FE-OLS. 13 new tests. |
287+
| **Long-panel Double-ML** | **`sp.dml_panel`**Clarke & Polselli (2025) DML for static panel models with fixed effects: absorbs unit (+ optional time) fixed effects via within-transform, cross-fits ML nuisance learners with unit-split folds (Liang-Zeger compatible), reports cluster-robust SE at the unit level. Empty-covariate fallback reduces to pure FE-OLS. 13 new tests. |
288288
| **Typed exception taxonomy** | `StatsPAIError` root + `AssumptionViolation` / `IdentificationFailure` / `DataInsufficient` / `ConvergenceFailure` / `NumericalInstability` / `MethodIncompatibility`, each carrying `recovery_hint`, machine-readable `diagnostics`, and a ranked `alternative_functions` list. Warning counterparts: `StatsPAIWarning` / `ConvergenceWarning` / `AssumptionWarning` plus a rich-payload `sp.exceptions.warn()` helper. Domain errors subclass `ValueError` / `RuntimeError` → existing `except` blocks keep working unchanged. 13 call-site migrations already shipped (DID, IV, matching, DML-IRM, synth, Bayesian DML). |
289289
| **Agent cards + registry** | `sp.agent_card(name)` and `sp.agent_cards(category=None)` return `pre_conditions` / `assumptions` / `failure_modes` (symptom + exception + remedy + alternative) / `alternatives` / `typical_n_min` for **36 flagship functions** (`regress`, `iv`, `did`, `callaway_santanna`, `rdrobust`, `synth`, `dml`, `dml_panel`, `causal_forest`, `metalearner`, `match`, `tmle`, `bayes_dml`, `bayes_did`, `bayes_iv`, `proximal`, `mr`, `qdid`, `qte`, `dose_response`, `spillover`, `multi_treatment`, `network_exposure`, `paper`, `llm_dag_constrained`, `llm_dag_validate`, `text_treatment_effect`, `llm_annotator_correct`, ...). `sp.recommend()` auto-consumes them: every recommendation now includes `agent_card` / `pre_conditions` / `failure_modes` / `alternatives` / `typical_n_min`, with an auto-warning when `n_obs < typical_n_min`. |
290290
| **Result-object agent hooks** | `CausalResult.violations()` / `EconometricResults.violations()` inspect stored diagnostics (pre-trend p, first-stage F, McCrary, rhat/ESS/divergences, overlap, SMD) and return flagged items with `severity` / `recovery_hint` / `alternatives`. `.to_agent_summary()` returns a JSON-ready structured payload (point estimate, coefficients, scalar diagnostics, violations, next-steps) alongside the existing prose `.summary()` and `tidy()` DataFrame. |

paper.bib

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4869,6 +4869,18 @@ @techreport{chernozhukov2022long
48694869
note={arXiv:2112.13398}
48704870
}
48714871

4872+
@article{clarke2025double,
4873+
title={Double Machine Learning for Static Panel Models with Fixed Effects},
4874+
author={Clarke, Paul S. and Polselli, Annalivia},
4875+
journal={The Econometrics Journal},
4876+
volume={29},
4877+
number={1},
4878+
pages={69--86},
4879+
year={2025},
4880+
doi={10.1093/ectj/utaf011},
4881+
note={arXiv:2312.08174}
4882+
}
4883+
48724884
@article{semenova2021debiased,
48734885
title={Debiased Machine Learning of Conditional Average Treatment Effects and Other Causal Functions},
48744886
author={Semenova, Vira and Chernozhukov, Victor},

src/statspai/dml/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@
4141
'dml_model_averaging',
4242
'model_averaging_dml',
4343
'DMLAveragingResult',
44-
# v1.7 long-panel DML (Semenova-Chernozhukov 2023)
44+
# v1.7 long-panel DML (Clarke & Polselli 2025)
4545
'dml_panel',
4646
'DMLPanelResult',
4747
# v1.13 sensitivity + diagnostics (Chernozhukov-Cinelli-Newey 2022)

src/statspai/dml/panel_dml.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
"""
2-
Long-panel Double/Debiased ML (Semenova-Chernozhukov 2023, simplified).
2+
Long-panel Double/Debiased ML for static panel models with fixed effects
3+
(Clarke & Polselli 2025, simplified).
34
45
Estimates the causal effect of a (continuous or binary) treatment on an
56
outcome from panel data while (i) absorbing unit and optional time
@@ -41,9 +42,10 @@
4142
4243
References
4344
----------
44-
Semenova, V. & Chernozhukov, V. (2023).
45-
"Debiased Machine Learning of Conditional Average Treatment Effects
46-
and Other Causal Functions." *Econometrics Journal*, 26(2).
45+
Clarke, P. S. & Polselli, A. (2025).
46+
"Double Machine Learning for Static Panel Models with Fixed Effects."
47+
*The Econometrics Journal*, 29(1), 69-86. DOI 10.1093/ectj/utaf011
48+
(arXiv:2312.08174). [@clarke2025double]
4749
4850
Chernozhukov, V., Chetverikov, D., Demirer, M., Duflo, E., Hansen, C.,
4951
Newey, W. & Robins, J. (2018).
@@ -426,7 +428,7 @@ def dml_panel(
426428
Y_tilde = _within_transform(Y, unit_ids, time_idx_for_within, sample_weight=w_full)
427429
D_tilde = _within_transform(D, unit_ids, time_idx_for_within, sample_weight=w_full)
428430
# Covariates demeaned the same way so the nuisance learners work on
429-
# within-variation only — matches Semenova-Chernozhukov 2023 §4.
431+
# within-variation only — matches Clarke & Polselli (2025) §3.
430432
if covariates:
431433
X_tilde = np.column_stack([
432434
_within_transform(

src/statspai/registry.py

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4163,16 +4163,16 @@ def _build_registry():
41634163
reference="Ahrens, Hansen, Schaffer & Wiemann (2025). JAE 40(3):249-269. DOI 10.1002/jae.3103.",
41644164
))
41654165

4166-
# -- v1.7 long-panel DML (Semenova-Chernozhukov 2023) -------------- #
4166+
# -- v1.7 long-panel DML (Clarke & Polselli 2025) ------------------ #
41674167
register(FunctionSpec(
41684168
name="dml_panel",
41694169
category="causal",
41704170
description=(
4171-
"Long-panel Double/Debiased ML (Semenova-Chernozhukov 2023 "
4172-
"simplified). Absorbs unit (and optional time) fixed "
4173-
"effects via within-transform, cross-fits ML nuisance "
4174-
"learners with folds that split units, and reports "
4175-
"cluster-robust SE at the unit level. PLR moment "
4171+
"Long-panel Double/Debiased ML for static panel models with "
4172+
"fixed effects (Clarke & Polselli 2025, simplified). Absorbs "
4173+
"unit (and optional time) fixed effects via within-transform, "
4174+
"cross-fits ML nuisance learners with folds that split units, "
4175+
"and reports cluster-robust SE at the unit level. PLR moment "
41764176
"(continuous or binary treatment)."
41774177
),
41784178
params=[
@@ -4204,7 +4204,8 @@ def _build_registry():
42044204
tags=["dml", "causal", "panel", "fixed_effects",
42054205
"cluster_robust_se", "long_panel"],
42064206
reference=(
4207-
"Semenova & Chernozhukov (2023) Econometrics Journal 26(2); "
4207+
"Clarke & Polselli (2025) Econometrics Journal 29(1) 69-86, "
4208+
"DOI 10.1093/ectj/utaf011; "
42084209
"Chernozhukov et al. (2018); Cameron & Miller (2015)."
42094210
),
42104211
pre_conditions=[

src/statspai/synth/report.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,7 @@
210210
"[@sun2023multiple]"
211211
),
212212
"cluster": (
213-
"Rho, S., Yan, X. et al. (2025). "
213+
"Rho, S., Tang, A. et al. (2025). "
214214
'"ClusterSC: Cluster-Aware Synthetic Control." '
215215
"arXiv:2503.21629. [@rho2025clustersc]"
216216
),

tests/test_audit_citations.py

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -361,9 +361,20 @@ def test_cli_runs_without_crash_on_empty_tree(tmp_path):
361361
resolves roots relative to its own ``REPO_ROOT`` constant (not
362362
``cwd``), so in CI it actually scans the real src/ and makes live
363363
arXiv calls — which can hit HTTP 429 rate limits on a cold runner
364-
and would flip ``--strict`` into exit 1. Non-strict mode keeps
365-
exit 0 whenever the script didn't crash, which is what this test
366-
is really checking.
364+
and would flip ``--strict`` into exit 1.
365+
366+
Accepted exits:
367+
368+
* ``0`` — clean run, no findings.
369+
* ``1`` — ran successfully and emitted a report listing
370+
mismatches / unresolved DOIs (regex-level surname false
371+
positives such as treating ``"Form"`` / ``"Behavior"`` /
372+
``"SEs"`` as author surnames are a known auditor limitation,
373+
not a crash).
374+
* ``2`` — soft failure (rate limit, network) — acceptable.
375+
376+
Any other exit (including a traceback in ``stderr``) is a real
377+
failure.
367378
"""
368379
(tmp_path / "src").mkdir()
369380
(tmp_path / "docs").mkdir()
@@ -376,7 +387,10 @@ def test_cli_runs_without_crash_on_empty_tree(tmp_path):
376387
capture_output=True, text=True, check=False,
377388
cwd=tmp_path,
378389
)
379-
assert result.returncode in (0, 2), (
390+
assert "Traceback" not in result.stderr, (
391+
f"auditor crashed with traceback:\n{result.stderr}"
392+
)
393+
assert result.returncode in (0, 1, 2), (
380394
f"unexpected exit {result.returncode}: {result.stderr}"
381395
)
382396

0 commit comments

Comments
 (0)