cs_report() — the one-call report card¶
sp.cs_report() runs the full Callaway–Sant'Anna workflow under a
single random seed and bundles the outputs into a CSReport
dataclass that pretty-prints, plots, and exports.
Minimal example¶
import statspai as sp
rpt = sp.cs_report(
df,
y='y', g='g', t='t', i='id',
n_boot=500,
random_state=0,
verbose=True, # prints the report to stdout
)
Structured fields¶
rpt.overall # dict: overall ATT, SE, CI, p
rpt.simple # DataFrame: simple aggregation
rpt.dynamic # DataFrame: event study with uniform bands
rpt.group # DataFrame: per-cohort θ(g)
rpt.calendar # DataFrame: per-calendar-time θ(t)
rpt.pretrend # dict: χ² pre-trend Wald test
rpt.breakdown # DataFrame: R-R breakdown M* per post event time
rpt.meta # dict: run metadata (n_units, estimator, …)
Export formats¶
rpt.to_text() # fixed-width ASCII
rpt.to_markdown() # GitHub-flavoured Markdown (floatfmt configurable)
rpt.to_latex() # booktabs LaTeX fragment (no jinja2 needed)
rpt.to_excel('out.xlsx') # six-sheet workbook
rpt.plot() # 2×2 summary figure via matplotlib
One-call bundle¶
Pass save_to='prefix' to emit every format in one go:
sp.cs_report(
df, y='y', g='g', t='t', i='id',
n_boot=500, random_state=0,
save_to='~/studies/cs_v1',
)
# writes:
# ~/studies/cs_v1.txt .md .tex .xlsx .png
Missing parent directories are created on the fly; optional
dependencies (openpyxl, matplotlib) are skipped silently.
From a pre-fitted result¶
Skip re-running estimation if you already have a
callaway_santanna() result:
For Agents¶
Pre-conditions - panel data with unit × time × outcome - g column is integer: first-treated period or 0 for never-treated - at least one never-treated or late-treated control group - ≥ 2 pre-treatment periods per cohort - data is panel or repeated cross-section with a time column - treat column is binary (0/1) for 2x2, or first-treatment-period (int) for staggered - at least one pre-treatment period (≥ 2 periods for 2x2; ≥ 3 recommended for event study) - for staggered designs: id column identifying units across time
Identifying assumptions - Parallel trends conditional on X (if covariates supplied) - No anticipation (or adjust via anticipation= parameter) - Overlap: positive propensity for each cohort - SUTVA - Parallel trends: treated and control groups would have followed the same trajectory absent treatment - No anticipation: outcomes in pre-treatment periods are unaffected by future treatment - SUTVA: no spillovers between units - For staggered / heterogeneous effects: use CS or SA — TWFE can produce negative weights (Goodman-Bacon)
Failure modes → recovery
| Symptom | Exception | Remedy | Try next |
|---|---|---|---|
| Pre-trend test on aggregated ATT(g,t) rejects | AssumptionViolation |
Use sp.sensitivity_rr for honest CI, or add covariates for conditional parallel trends. | sp.sensitivity_rr |
| Cohort with only one unit — insufficient variation | DataInsufficient |
Aggregate small cohorts or drop; check sp.diagnose_result. | |
| All units treated at the same time (no staggering) | MethodIncompatibility |
Fall back to 2x2 DID via sp.did(method='2x2'). | sp.did |
| Pre-trend joint test p < 0.05 (or underpowered at 0.10) | AssumptionViolation |
Use sp.sensitivity_rr (Rambachan & Roth honest CI) or switch to sp.callaway_santanna. | sp.sensitivity_rr |
| Staggered treatment timing with TWFE method | AssumptionWarning |
TWFE can give negative weights; use Callaway-Sant'Anna, Sun-Abraham, or BJS imputation. | sp.callaway_santanna |
| Pre-trend test underpowered (Roth 2022) | AssumptionWarning |
Check sp.pretrends_power — if low, report honest CI via sp.sensitivity_rr. | sp.sensitivity_rr |
| Few clusters at unit level | AssumptionWarning |
Use wild cluster bootstrap (sp.wild_cluster_bootstrap). | sp.wild_cluster_bootstrap |
Alternatives (ranked)
- sp.sun_abraham
- sp.did_imputation
- sp.sdid
- sp.did
- sp.callaway_santanna
- sp.synth
Typical minimum N: 50