Skip to content

statspai.workflow

workflow

End-to-end causal-inference workflow orchestrator.

sp.causal(df, y=, treatment=, ...) stitches the full analysis pipeline into one call: diagnose identification -> recommend an estimator -> fit it -> run the standard robustness suite -> produce an HTML / Markdown / LaTeX report.

This module materialises the agent-native workflow as an API while keeping each stage's statistical assumptions explicit.

CausalWorkflow dataclass

Holds state across the diagnose -> estimate -> report pipeline.

diagnose

diagnose()

Run sp.check_identification, cache the report.

recommend

recommend()

Run sp.recommend() to pick an estimator.

estimate

estimate()

Fit the top-recommended estimator.

Returns the result object (CausalResult or EconometricResults). Uses the workflow's dataset and column mappings; when the top recommendation is plain OLS with a treatment-only formula, enrich the formula with the user's covariates so confounders are actually adjusted for (sp.recommend by default leaves this to the caller; the workflow takes responsibility).

Sprint-B preference: when the caller supplied one of the new causal-method hints (proxy_z/proxy_w, tv_confounders, post_treat_strata, mediator), that hint wins over the design-based default in the top recommendation — the user signalled the estimand explicitly.

robustness

robustness()

Run design-appropriate robustness checks on the fitted result.

Delegates to :func:statspai.workflow._robustness.run_robustness_battery — the shared battery that powers both the natural-language path (sp.paper(data, question, ...)) and the estimand-first path (sp.paper(CausalQuestion(...))). The battery never raises; per-check failures land as severity='check_failed' findings.

Backwards compatibility: self.robustness_findings keeps the flat Dict[str, Any] shape that callers (and the existing :class:PaperDraft renderer) expect — populated from :meth:RobustnessReport.to_dict. The structured per-finding records are reachable via self.robustness_findings['_findings'] when a caller wants severity-aware rendering.

report

report(path: Optional[str] = None, fmt: str = 'html') -> str

Generate an end-to-end report and optionally write to disk.

Parameters:

Name Type Description Default
path str

Output path. If omitted, only returns the string.

None
fmt str

One of 'html' (default) or 'markdown'.

'html'

Returns:

Type Description
str

The report content.

compare_estimators

compare_estimators() -> DataFrame

Run a design-appropriate panel of estimators for robustness.

For DiD: CS + SA + BJS + Wooldridge (+ Stacked when feasible). For IV: 2SLS + LIML + JIVE + GMM. For observational: OLS + Entropy Balancing + CBPS + SBW + DML. For RD: Sharp RDD (rdrobust) at MSE-optimal + CERD ±50% bandwidths.

Rows with a failed estimator carry NaN and an error string.

sensitivity_panel

sensitivity_panel() -> DataFrame

Stack E-value + Rosenbaum Γ + Oster δ for one-page sensitivity.

Only runs the tests whose required inputs are present: * E-value requires a risk-ratio or the primary estimate + SE. * Rosenbaum Γ requires a matched/weighted comparison. * Oster δ requires OLS residual variances.

cate

cate() -> DataFrame

Quick heterogeneity pass: X-Learner + Causal Forest summary.

Skipped if the design is IV / RD / DiD with only the canonical columns and no covariates — heterogeneity needs an X matrix.

run

run(full: bool = True)

Run the causal pipeline.

Parameters:

Name Type Description Default
full bool

When True, also runs the v0.9.17 extended stages: compare_estimators, sensitivity_panel, and cate. Set to False for a quick single-estimator pass.

True

PaperDraft dataclass

Draft causal-analysis report assembled by :func:sp.paper.

Attributes:

Name Type Description
question str

The original natural-language question.

sections dict[str, str]

Mapping section_title -> markdown_body. Always includes at least: Question, Data, Identification, Estimator, Results, Robustness, References.

workflow CausalWorkflow

The underlying workflow object — exposes the raw fitted result (workflow.result), the diagnostics, the recommendation, etc.

fmt str

Default output format (markdown / tex / docx).

citations list of str

BibTeX-style entries collected from each estimator's cite().

parsed_hints dict

What the question parser extracted, for transparency / debugging.

degradations list of dict

Structured record of optional sub-steps that failed and were skipped (covariate balance, CI rendering, DAG appendix, citation extraction, provenance attachment, …). Each entry has at least section, error_type, message; some carry detail. Empty when the draft is fully populated. See :class:statspai.workflow.WorkflowDegradedWarning.

to_tex

to_tex() -> str

Render to a LaTeX article skeleton.

Each section becomes \section{...}; markdown bullet lists and code fences are translated to LaTeX equivalents.

to_docx

to_docx(path: str) -> None

Write a Word document to path.

Uses the workflow's already-fit result's to_docx if available; otherwise falls back to dropping a markdown file with a .docx warning header (no python-docx hard dep).

to_qmd

to_qmd(*, title: str = 'Causal Analysis Draft', author: Optional[str] = None, formats: Optional[List[str]] = None, bibliography: Optional[str] = None, csl: Optional[str] = None, include_provenance: bool = True) -> str

Render to a Quarto (.qmd) document.

Quarto is the multi-format manuscript default: a single source compiles to PDF / HTML / DOCX / Beamer with cross-refs, citations (CSL), and embedded code chunks. sp.paper() already produces all the prose; this method just wraps it in the correct YAML frontmatter so quarto render paper.qmd Just Works.

Parameters:

Name Type Description Default
title str

title: field in the YAML frontmatter.

'Causal Analysis Draft'
author str

author:. When omitted, no author line is emitted (Quarto handles that fine).

None
formats list of str

Output formats Quarto should support. Default ["pdf", "html", "docx"] covers the common journal workflows. Pass e.g. ["pdf", "beamer"] for slide decks.

None
bibliography str

Path Quarto should resolve for citation lookup, e.g. "paper.bib". When omitted, the YAML omits the field entirely (so Quarto won't error if no .bib file exists); but if self.citations is non-empty we default to "paper.bib" because :func:sp.replication_pack writes citations there alongside the rendered draft.

None
csl str

CSL style file (e.g. "american-economic-association.csl"). Pure pass-through.

None
include_provenance bool

Append a Reproducibility appendix with :func:format_provenance when self.workflow.result carries a _provenance record.

True

Returns:

Type Description
str

The complete .qmd document as a single string.

Notes
  • The body sections are the same as :meth:to_markdown — standard markdown with ## H2 headers, which Quarto will render natively.
  • Code chunks are not injected by default. When the calling script wants the .qmd to re-execute the analysis on each render, pass it through :func:sp.replication_pack which writes both the .qmd and a code/script.py reproducer.

write

write(path: str) -> None

Write the draft to disk in the format inferred from the path extension (.md / .tex / .docx / .qmd).

causal

causal(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, cutoff: Optional[float] = None, cohort: Optional[str] = None, cluster: Optional[str] = None, design: Optional[str] = None, dag=None, strict: bool = False, auto_run: bool = True, mediator: Optional[str] = None, tv_confounders: Optional[List[str]] = None, proxy_z: Optional[List[str]] = None, proxy_w: Optional[List[str]] = None, post_treat_strata: Optional[str] = None, allow_experimental: bool = False) -> CausalWorkflow

End-to-end causal-inference workflow.

One call that diagnoses identification, picks an estimator, fits it, runs the canonical robustness suite, and produces a report.

Orchestrate the registered StatsPAI stages while keeping each stage's statistical assumptions explicit.

Parameters:

Name Type Description Default
data DataFrame
required
y DataFrame
required
treatment DataFrame
required
covariates DataFrame
required
id DataFrame
required
time DataFrame
required
running_var DataFrame
required
instrument DataFrame
required
cutoff Optional[float]

Passed through to sp.check_identification and sp.recommend.

None
cohort Optional[float]

Passed through to sp.check_identification and sp.recommend.

None
cluster Optional[float]

Passed through to sp.check_identification and sp.recommend.

None
design Optional[float]

Passed through to sp.check_identification and sp.recommend.

None
dag Optional[float]

Passed through to sp.check_identification and sp.recommend.

None
strict Optional[float]

Passed through to sp.check_identification and sp.recommend.

None
auto_run bool

If True (default), immediately runs all 5 stages and returns the fully-populated workflow. If False, returns the workflow object with no stages executed — call .diagnose(), .estimate(), etc. manually for finer control.

True
allow_experimental bool

Forwarded to :func:sp.recommend. When False (the agent-safe default), recommendations pointing at functions registered as stability='experimental' or 'deprecated' are dropped from the ranked output, and the workflow's :attr:pipeline_notes records what was filtered. Pass True when you are explicitly exploring frontier methods. See docs/guides/stability.md.

False

Returns:

Type Description
CausalWorkflow

A workflow object with .diagnostics, .recommendation, .result, .robustness_findings, and .report().

Examples:

One-call full analysis:

>>> import statspai as sp
>>> w = sp.causal(df, y='wage', treatment='training',
...               id='worker', time='year', design='did')
>>> w.report('analysis.html')

Fine-grained control:

>>> w = sp.causal(df, y='y', treatment='d', auto_run=False)
>>> w.diagnose()        # -> IdentificationReport
>>> if w.diagnostics.verdict == 'BLOCKERS':
...     raise SystemExit(1)
>>> w.estimate()
>>> print(w.report(fmt='markdown'))

parse_question

parse_question(question: str, columns: List[str]) -> Dict[str, Any]

Heuristic parse of a natural-language causal question.

Returns a dict of hints the caller can fall back on when explicit column kwargs aren't provided. Never overrides explicit args.

Parameters:

Name Type Description Default
question str

Natural-language question, e.g. "effect of training on wages".

required
columns list of str

Columns of the dataset; the parser only proposes column names present in this list.

required

Returns:

Type Description
dict

Possible keys: y, treatment, design, instrument, running_var, cutoff, raw_question.