statspai.smart¶
smart ¶
Smart Workflow Engine.
Registered workflow helpers for planning, diagnostics, sensitivity, and replication support:
- recommend() — DAG + data → estimator selection
- compare_estimators() — run multiple methods, compare, diagnose
- assumption_audit() — comprehensive assumption testing by method
- sensitivity_dashboard() — multi-dimensional sensitivity analysis
- pub_ready() — journal-specific publication readiness checklist
- replicate() — famous paper replication with built-in data
AssumptionResult ¶
Bases: ResultProtocolMixin
Results from comprehensive assumption audit.
Returned by :func:statspai.assumption_audit. Bundles the per-check
diagnostics (checks), an overall letter overall_grade (A–F),
pass/fail/inconclusive counts, and any critical_failures. Use
:meth:summary for a printable report, :meth:failed to list failed
checks with remedies, and :meth:passed_all for a quick gate.
Examples:
>>> import statspai as sp
>>> import numpy as np, pandas as pd
>>> rng = np.random.default_rng(0)
>>> n = 200
>>> educ = rng.integers(8, 20, n)
>>> exper = rng.integers(0, 40, n)
>>> wage = 1.0 + 0.1 * educ + 0.05 * exper + rng.normal(0, 1, n)
>>> df = pd.DataFrame({"wage": wage, "educ": educ, "exper": exper})
>>> result = sp.regress("wage ~ educ + exper", data=df)
>>> audit = sp.assumption_audit(result, data=df, verbose=False)
>>> type(audit).__name__
'AssumptionResult'
>>> bool(audit.overall_grade in set("ABCDF"))
True
>>> isinstance(audit.failed(), list)
True
ComparisonResult ¶
Bases: ResultProtocolMixin
Results from multi-estimator comparison.
Returned by :func:compare_estimators. Bundles the per-method fitted
results (.results), a tidy estimates_table DataFrame, and a
dict of cross-method agreement diagnostics. Use .summary() for
a text report and .plot() for a forest plot.
Examples:
>>> import statspai as sp
>>> import numpy as np, pandas as pd
>>> rng = np.random.default_rng(0)
>>> n = 300
>>> age = rng.normal(40, 10, n)
>>> educ = rng.normal(12, 3, n)
>>> ps = 1 / (1 + np.exp(-(0.05 * (age - 40) + 0.1 * (educ - 12))))
>>> training = rng.binomial(1, ps)
>>> wage = (5 + 2.0 * training + 0.1 * age + 0.3 * educ
... + rng.normal(0, 1, n))
>>> df = pd.DataFrame({"wage": wage, "training": training,
... "age": age, "education": educ})
>>> comp = sp.compare_estimators(
... data=df, y="wage", treatment="training",
... methods=["ols"], covariates=["age", "education"],
... )
>>> type(comp).__name__
'ComparisonResult'
>>> comp.n_obs
300
>>> list(comp.estimates_table.columns)[:2]
['method', 'estimate']
plot ¶
Forest plot comparing estimates across methods.
DiagnosticFinding
dataclass
¶
A single design-level finding.
These are the elements of :attr:IdentificationReport.findings;
each carries a severity ('blocker' / 'warning' / 'info'),
a category, a human-readable message and an optional
suggestion.
Examples:
>>> import statspai as sp
>>> f = sp.DiagnosticFinding(
... severity="warning", category="power",
... message="Minimum detectable effect is large.",
... suggestion="Collect more units.")
>>> f.severity, f.category
('warning', 'power')
>>> f.icon
'[!]'
IdentificationError ¶
Bases: IdentificationFailure
Raised by check_identification(strict=True) when a blocker is found.
Carries the full :class:IdentificationReport on self.report so
downstream code can still inspect findings without re-running. As a
subclass of :class:statspai.IdentificationFailure it is catchable through
the central taxonomy — except sp.IdentificationFailure and
except sp.StatsPAIError both fire — and it carries the standard
recovery_hint / diagnostics payload an agent can branch on.
Examples:
>>> import statspai as sp
>>> rep = sp.IdentificationReport(
... findings=[sp.DiagnosticFinding(
... severity="blocker", category="variation",
... message="No variation in treatment.")],
... design="cross_section", n_obs=30)
>>> err = sp.IdentificationError(rep)
>>> isinstance(err, sp.StatsPAIError)
True
>>> err.report.verdict
'BLOCKERS'
IdentificationReport
dataclass
¶
Report from check_identification.
Aggregates a list of :class:DiagnosticFinding into an overall
:attr:verdict ('OK' / 'WARNINGS' / 'BLOCKERS') and offers
:meth:summary for a printable digest and :meth:by_category to filter
findings. You usually obtain one from sp.check_identification rather
than constructing it directly.
Examples:
>>> import statspai as sp
>>> rep = sp.IdentificationReport(
... findings=[
... sp.DiagnosticFinding(
... severity="warning", category="power",
... message="Minimum detectable effect is large."),
... ],
... design="cross_section", n_obs=120)
>>> rep.verdict
'WARNINGS'
>>> len(rep.by_category("power"))
1
>>> bool("Identification Diagnostics" in rep.summary())
True
IntakeResult
dataclass
¶
MethodSpec
dataclass
¶
A verified methods-appendix entry for one estimator family.
Attributes:
| Name | Type | Description |
|---|---|---|
key |
str
|
Canonical lookup key (matches the citation key where possible). |
name |
str
|
Human-readable estimator name for the section heading. |
estimand_latex |
str
|
LaTeX (no delimiters) for the target parameter. |
estimator_latex |
str
|
LaTeX (no delimiters) for the sample estimator. |
prose |
str
|
One- to three-sentence plain-language description of the estimator. |
assumptions |
list of str
|
Identifying assumptions, one short clause each. |
aliases |
list of str
|
Extra keys / substrings that should resolve to this spec. |
PubReadyResult ¶
Bases: ResultProtocolMixin
Publication readiness checklist results.
Returned by :func:pub_ready. Holds the venue, the resolved checklist
items (each flagged done / not done), a 0-100 readiness score, and the
lists of missing and present items. Call :meth:summary for a
terminal-friendly report.
Examples:
>>> import statspai as sp
>>> df = sp.cps_wage()
>>> res = sp.regress("log_wage ~ education + experience", data=df)
>>> check = sp.pub_ready(results=[res], venue="aej_applied",
... has_robustness=True)
>>> type(check).__name__
'PubReadyResult'
>>> bool(0 <= check.score <= 100)
True
>>> bool(isinstance(check.missing, list))
True
RecommendationResult ¶
Bases: ResultProtocolMixin
Result from the estimator recommendation engine.
Returned by :func:recommend. Holds a ranked list of estimator
recommendations (each a dict with method / function /
reason / assumptions), the detected design, the data_profile
(outcome / treatment types, panel shape, missingness), and any warnings.
Call :meth:summary for a ranked, human-readable report.
Examples:
>>> import statspai as sp
>>> df = sp.cps_wage()
>>> rec = sp.recommend(df, y="log_wage", treatment="union",
... covariates=["education", "experience"])
>>> type(rec).__name__
'RecommendationResult'
>>> bool(len(rec.recommendations) > 0)
True
>>> bool("method" in rec.recommendations[0])
True
to_latex ¶
Export recommendations as a booktabs LaTeX table.
If verify=True was used when calling recommend(), the
table includes the stability-check columns (composite score,
bootstrap stability, placebo pass-rate, subsample agreement).
IMPORTANT CAVEAT FOR AUTHORS: The stability score measures whether a method gives consistent estimates under resampling on the observed data — it does NOT establish identification validity or protect against unobserved confounding. A biased OLS on observational data will typically score high because the bias is stable across resamples. Do not cite this score as evidence that a method is "correct" for a given design; use it only to compare the stability of methods that already satisfy the design's identification assumptions.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
caption
|
str
|
Table caption. Defaults to the detected design. |
None
|
label
|
str
|
LaTeX label for cross-referencing. |
'tab:recommendation'
|
Returns:
| Type | Description |
|---|---|
str
|
LaTeX source (booktabs + threeparttable). |
run ¶
Execute the recommended estimator.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
which
|
int
|
Which recommendation to run (0 = top recommendation). |
0
|
**kwargs
|
Any
|
Override any parameters. |
{}
|
run_all ¶
Run all recommended estimators and return a comparison.
SensitivityDashboard ¶
Multi-dimensional sensitivity analysis results.
Returned by :func:sp.sensitivity_dashboard. Holds the baseline
estimate, a list of per-dimension summaries and an overall
A/B/C/D/F stability grade.
Examples:
>>> import numpy as np
>>> import pandas as pd
>>> import statspai as sp
>>> rng = np.random.default_rng(0)
>>> n = 200
>>> df = pd.DataFrame({
... "x1": rng.normal(size=n),
... "x2": rng.normal(size=n),
... })
>>> df["y"] = 1.0 + 0.5 * df["x1"] - 0.3 * df["x2"] + rng.normal(size=n)
>>> result = sp.regress("y ~ x1 + x2", data=df)
>>> dash = sp.sensitivity_dashboard(result, data=df, verbose=False)
>>> type(dash).__name__
'SensitivityDashboard'
>>> bool(dash.overall_stability in {"A", "B", "C", "D", "F"})
True
>>> isinstance(dash.dimensions, list)
True
assumption_audit ¶
assumption_audit(result: Any, data: Optional[DataFrame] = None, alpha: float = 0.05, verbose: bool = True) -> AssumptionResult
Comprehensive assumption audit for any estimated model.
Run the method's registered assumption checks and provide actionable remedies for failed or inconclusive diagnostics.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
result
|
EconometricResults or CausalResult
|
Estimated model result. |
required |
data
|
DataFrame
|
Original data (needed for some tests). Auto-extracted if available. |
None
|
alpha
|
float
|
Significance level for tests. |
0.05
|
verbose
|
bool
|
Print summary automatically. |
True
|
Returns:
| Type | Description |
|---|---|
AssumptionResult
|
With .summary(), .failed(), .passed_all() methods. |
Examples:
>>> import statspai as sp
>>> import numpy as np, pandas as pd
>>> rng = np.random.default_rng(0)
>>> n = 200
>>> df = pd.DataFrame({
... "educ": rng.integers(8, 18, n).astype(float),
... "exper": rng.integers(0, 30, n).astype(float),
... })
>>> df["wage"] = (5 + 0.4 * df["educ"] + 0.1 * df["exper"]
... + rng.normal(0, 1, n))
>>> result = sp.regress("wage ~ educ + exper", data=df)
>>> audit = sp.assumption_audit(result, data=df, verbose=False)
>>> _ = audit.summary()
>>> if not audit.passed_all():
... for fail in audit.failed():
... _ = fail.remedy
bib_for ¶
Top-level structured citation for a fitted result.
Convenience entry that pairs with result.cite(format="json")
so agents that don't have direct access to the result method can
pull the structured payload via sp.bib_for(...) instead.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
result
|
CausalResult or EconometricResults
|
Any fitted result object exposing a |
required |
Returns:
| Type | Description |
|---|---|
dict
|
Same shape as |
Examples:
>>> import statspai as sp
>>> import numpy as np
>>> import pandas as pd
>>> rng = np.random.default_rng(5)
>>> rows = []
>>> for i in range(200):
... tr = 1 if i < 100 else 0
... for t in (0, 1):
... y = (1.0 + 0.3 * t + 0.5 * tr + 2.0 * tr * t
... + rng.normal(scale=0.5))
... rows.append({'i': i, 't': t, 'treated': tr, 'post': t, 'y': y})
>>> df = pd.DataFrame(rows)
>>> r = sp.did(df, y='y', treat='treated', time='t', post='post')
>>> sp.bib_for(r)['key']
'angrist2009mostly'
bibtex ¶
Resolve verified BibTeX entries from paper.bib by citation key.
The Python twin of the bibtex MCP tool, and the resolver that
:func:bib_for and result.cite(format="json") advertise in their
resolve_with hint: feed the citation_keys they return straight
into sp.bibtex to obtain the full, verified @article{...}
entries from the project's single bibliographic source of truth
(paper.bib). Entries are returned verbatim — never reformatted
or invented (CLAUDE.md §10 zero-hallucination rule).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
keys
|
str or iterable of str
|
One or more BibTeX keys, e.g. |
required |
Returns:
| Type | Description |
|---|---|
str
|
The matching entries in the order requested, separated by a blank
line, ready to paste into a |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
KeyError
|
If any requested key is absent from |
See Also
bib_for : structured citation payload (and citation_keys) for a
fitted result; pipe its keys into this function.
Examples:
render_citation ¶
Render a stored BibTeX string in the requested format.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
bibtex
|
str
|
Raw BibTeX entry as stored on the result class. May contain
multiple |
required |
fmt
|
('bibtex', 'apa', 'json')
|
Output format. |
"bibtex"
|
Returns:
| Type | Description |
|---|---|
str | dict | list
|
|
compare_estimators ¶
compare_estimators(data: DataFrame, y: str, treatment: str, methods: Optional[List[str]] = None, covariates: Optional[List[str]] = None, id: Optional[str] = None, time: Optional[str] = None, instrument: Optional[str] = None, alpha: float = 0.05, method_hints: Optional[Dict[str, Dict[str, Any]]] = None) -> ComparisonResult
Run multiple estimators on the same data and compare.
Run selected estimators and return an agreement-diagnostics table for manual robustness review.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
DataFrame
|
|
required |
y
|
str
|
Outcome variable. |
required |
treatment
|
str
|
Treatment variable (binary). |
required |
methods
|
list of str
|
Estimators to compare. Default auto-selects based on data.
Classical options: Hint-driven Sprint-B options (require |
None
|
method_hints
|
dict
|
Per-method keyword overrides, merged with the shared kwargs when dispatching each estimator. Structure:: Collision rule (docs/ROADMAP.md §6): per-method hints
take precedence over the shared kwargs for the method they
name. If the top-level |
None
|
covariates
|
list of str
|
|
None
|
id
|
str
|
Panel unit ID. |
None
|
time
|
str
|
Time variable. |
None
|
instrument
|
str
|
|
None
|
alpha
|
float
|
|
0.05
|
Returns:
| Type | Description |
|---|---|
ComparisonResult
|
With .summary(), .plot(), .results (dict of individual results). |
Examples:
>>> import statspai as sp
>>> import numpy as np, pandas as pd
>>> rng = np.random.default_rng(0)
>>> n = 300
>>> age = rng.normal(40, 10, n)
>>> educ = rng.normal(12, 3, n)
>>> ps = 1 / (1 + np.exp(-(0.05 * (age - 40) + 0.1 * (educ - 12))))
>>> training = rng.binomial(1, ps)
>>> wage = (5 + 2.0 * training + 0.1 * age + 0.3 * educ
... + rng.normal(0, 1, n))
>>> df = pd.DataFrame({"wage": wage, "training": training,
... "age": age, "education": educ})
>>> comp = sp.compare_estimators(
... data=df, y="wage", treatment="training",
... methods=["ols"], covariates=["age", "education"],
... )
>>> type(comp).__name__
'ComparisonResult'
>>> comp.n_obs
300
check_identification ¶
check_identification(data: DataFrame, y: str, treatment: Optional[str] = None, covariates: Optional[List[str]] = None, id: Optional[str] = None, time: Optional[str] = None, running_var: Optional[str] = None, instrument: Optional[str] = None, cluster: Optional[str] = None, cutoff: Optional[float] = None, design: Optional[str] = None, cohort: Optional[str] = None, dag: Any = None, strict: bool = False) -> IdentificationReport
Run design-level identification diagnostics before fitting an estimator.
This reads your dataframe + design and outputs a prioritised list of pitfalls — bad controls, overlap violations, underpowered designs, small cohorts, clustering ambiguity.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
DataFrame
|
|
required |
y
|
str
|
Outcome column. |
required |
treatment
|
str
|
Binary or continuous treatment column. |
None
|
covariates
|
list of str
|
Candidate control variables. |
None
|
id
|
str
|
Panel identifiers. |
None
|
time
|
str
|
Panel identifiers. |
None
|
running_var
|
str
|
RD running variable. |
None
|
instrument
|
str
|
IV instrument. |
None
|
cluster
|
str
|
Clustering column for inference. |
None
|
cutoff
|
float
|
RD cutoff value. |
None
|
design
|
str
|
Override auto-detected design: one of 'rct', 'did', 'rd', 'iv', 'observational', 'panel'. |
None
|
cohort
|
str
|
First-treatment-period column (for staggered DID). |
None
|
dag
|
DAG
|
Causal DAG. If supplied, runs Cinelli-Forney-Pearl (2022) bad-control detection (mediator, descendant, collider, M-bias) and verifies the covariate set satisfies a valid adjustment criterion. Upgrades correlation heuristic to a principled check. |
None
|
strict
|
bool
|
If True, raise :class: |
False
|
Returns:
| Type | Description |
|---|---|
IdentificationReport
|
With |
Notes
When a supplied dag object errors during a sub-check (bad-control
analysis or adjustment-set verification), that sub-check degrades to
an info-level finding plus a WorkflowDegradedWarning rather than
failing the whole report; the verdict is unaffected.
Examples:
>>> import statspai as sp
>>> import numpy as np, pandas as pd
>>> rng = np.random.default_rng(0)
>>> rows = []
>>> for w in range(40):
... treated, age = w < 20, rng.integers(25, 55)
... educ = rng.integers(10, 18)
... for yr in (2000, 2001):
... eff = 1.0 if (treated and yr == 2001) else 0.0
... rows.append({"worker": w, "year": yr,
... "wage": 10 + 0.1 * age + 0.3 * educ + eff
... + rng.normal(0, 1),
... "training": int(treated),
... "age": age, "education": educ})
>>> df = pd.DataFrame(rows)
>>> report = sp.check_identification(
... df, y="wage", treatment="training",
... covariates=["age", "education"],
... id="worker", time="year", design="did",
... )
>>> type(report).__name__
'IdentificationReport'
>>> bool(report.verdict in ("OK", "WARNINGS", "BLOCKERS"))
True
design_intake ¶
design_intake(*, estimand: Optional[str] = None, assignment: Optional[str] = None, data_topology: Optional[str] = None, controls: Optional[str] = None, identification_support: Optional[str] = None, needs: Optional[str] = None) -> IntakeResult
Route design facts to a method-selection status.
This is a pre-recommend intake helper. It does not fit models and it
does not pretend missing identification facts are harmless.
Examples:
pub_ready ¶
pub_ready(results: Optional[list] = None, venue: str = 'top5_econ', design: Optional[str] = None, has_balance: bool = False, has_pretrends: bool = False, has_robustness: bool = False, has_heterogeneity: bool = False, has_sensitivity: bool = False, has_placebo: bool = False, has_mht: bool = False) -> PubReadyResult
Publication readiness checklist.
Generate a venue-specific checklist for empirical paper submission.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
results
|
list
|
List of estimated result objects. |
None
|
venue
|
str
|
Target venue: 'top5_econ', 'aej_applied', 'rct'. |
'top5_econ'
|
design
|
str
|
Research design: 'rct', 'did', 'rd', 'iv', 'observational'. |
None
|
has_balance
|
bool
|
Already have balance table. |
False
|
has_pretrends
|
bool
|
Already have pre-trend tests. |
False
|
has_robustness
|
bool
|
Already have robustness checks. |
False
|
has_heterogeneity
|
bool
|
Already have subgroup analysis. |
False
|
has_sensitivity
|
bool
|
Already have sensitivity analysis. |
False
|
has_placebo
|
bool
|
Already have placebo tests. |
False
|
has_mht
|
bool
|
Already have MHT correction. |
False
|
Returns:
| Type | Description |
|---|---|
PubReadyResult
|
|
Examples:
list_replications ¶
sensitivity_dashboard ¶
sensitivity_dashboard(result: Any, data: Optional[DataFrame] = None, dimensions: Optional[List[str]] = None, alpha: float = 0.05, verbose: bool = True) -> SensitivityDashboard
Comprehensive multi-dimensional sensitivity analysis.
Test sensitivity across selected dimensions and produce an overall stability grade.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
result
|
EconometricResults or CausalResult
|
Baseline estimated result. |
required |
data
|
DataFrame
|
Original data (auto-extracted if possible). |
None
|
dimensions
|
list of str
|
Which dimensions to test. Default: all applicable. Options: 'sample', 'controls', 'functional_form', 'outliers', 'unobservables'. |
None
|
alpha
|
float
|
|
0.05
|
verbose
|
bool
|
|
True
|
Returns:
| Type | Description |
|---|---|
SensitivityDashboard
|
|
Examples:
>>> import numpy as np
>>> import pandas as pd
>>> import statspai as sp
>>> rng = np.random.default_rng(0)
>>> n = 200
>>> df = pd.DataFrame({
... "x1": rng.normal(size=n),
... "x2": rng.normal(size=n),
... })
>>> df["y"] = 1.0 + 0.5 * df["x1"] - 0.3 * df["x2"] + rng.normal(size=n)
>>> result = sp.regress("y ~ x1 + x2", data=df)
>>> dash = sp.sensitivity_dashboard(
... result, data=df, dimensions=["sample", "outliers"], verbose=False,
... )
>>> type(dash).__name__
'SensitivityDashboard'