Skip to content

statspai.core

core

Core package initialization

BaseModel

Bases: ABC

Abstract base class for all econometric models

fit abstractmethod

fit(**kwargs: Any) -> EconometricResults

Fit the econometric model

Returns:

Type Description
EconometricResults

Fitted model results

predict abstractmethod

predict(data: Optional[DataFrame] = None) -> ndarray

Generate predictions from the fitted model

Parameters:

Name Type Description Default
data DataFrame

Data for prediction. If None, uses training data.

None

Returns:

Type Description
ndarray

Predicted values

summary

summary() -> str

Return a summary of the fitted model

Returns:

Type Description
str

Model summary

BaseEstimator

Bases: ABC

Abstract base class for estimation algorithms

estimate abstractmethod

estimate(y: ndarray, X: ndarray, **kwargs: Any) -> Dict[str, Any]

Estimate model parameters

Parameters:

Name Type Description Default
y ndarray

Dependent variable

required
X ndarray

Independent variables

required
**kwargs Any

Additional estimation options

{}

Returns:

Type Description
Dict[str, Any]

Estimation results including parameters, standard errors, etc.

EconometricResults

Unified results class for econometric models

This class provides a consistent interface for accessing results from different econometric estimators, similar to R's broom package.

Examples:

>>> import statspai as sp
>>> import numpy as np
>>> import pandas as pd
>>> rng = np.random.default_rng(0)
>>> x = rng.normal(size=50)
>>> df = pd.DataFrame({"y": 2.0 * x + rng.normal(size=50), "x": x})
>>> res = sp.regress("y ~ x", data=df)
>>> type(res).__name__
'EconometricResults'
>>> round(float(res.params["x"]), 1)
1.9

nobs property

nobs: Optional[int]

Estimation sample size (data_info['nobs']), None if unrecorded.

n_obs property writable

n_obs: int

Alias of :attr:nobs, the name :class:CausalResult uses.

A fit that recorded no sample size has no n_obs (the attribute raises AttributeError), so hasattr(result, "n_obs") keeps meaning "the sample size is known".

r2 property

r2: Optional[float]

R-squared as recorded in diagnostics, None if the fit has none.

r2_adj property

r2_adj: Optional[float]

Adjusted R-squared (e(r2_a)), None if the fit has none.

vcov

vcov() -> DataFrame

Covariance matrix of :attr:params, labelled by coefficient.

The matrix behind the reported standard errors (its diagonal equals std_errors ** 2). Raises when the fit stores no such matrix -- e.g. standard errors replaced after the fit by a bootstrap -- rather than returning a diagonal matrix that would claim the estimates are uncorrelated.

Examples:

>>> import statspai as sp, numpy as np, pandas as pd
>>> rng = np.random.default_rng(0)
>>> x = rng.normal(size=50)
>>> df = pd.DataFrame({"y": 2.0 * x + rng.normal(size=50), "x": x})
>>> res = sp.regress("y ~ x", data=df)
>>> V = res.vcov()
>>> bool(np.allclose(np.sqrt(np.diag(V)), res.std_errors))
True

cov_params

cov_params() -> DataFrame

Labelled coefficient covariance; the same matrix as vcov().

summary

summary(alpha: Optional[float] = None) -> str

Generate a summary table of results

Parameters:

Name Type Description Default
alpha float

Significance level for confidence intervals. Defaults to the level requested at fit time (0.05 unless the estimator was called with alpha=).

None

Returns:

Type Description
str

Formatted summary table

inference_rule

inference_rule() -> Dict[str, Any]

The variance, reference distribution and small-sample convention.

Coefficients and standard errors can agree with another program while t / F / p values do not, because the statistics are referred to a different distribution (t(N-K), t(G-1) or z) or the variance carries a different finite-sample factor. This states the rule the fit used, as recorded by the estimator; summary() prints it.

Returns:

Type Description
dict

vcov, distribution ('t' / 'z'), df, joint_test, n_obs, and when recorded n_clusters, small, ssc, n_rows, n_groups, notes.

Examples:

>>> import numpy as np, pandas as pd
>>> import statspai as sp
>>> rng = np.random.default_rng(0)
>>> df = pd.DataFrame({"x": rng.normal(size=60), "g": np.repeat(range(12), 5)})
>>> df["y"] = df.x + rng.normal(size=60)
>>> sp.regress("y ~ x", data=df, cluster="g").inference_rule()["df"]
11

conf_int

conf_int(alpha: Optional[float] = None) -> DataFrame

Return confidence intervals for parameters

Parameters:

Name Type Description Default
alpha float

Significance level. Defaults to the fit-time level (0.05 unless the estimator was called with alpha=).

None

Returns:

Type Description
DataFrame

Confidence intervals

tidy

tidy(conf_level: Optional[float] = None) -> DataFrame

Return a long-format DataFrame of coefficients, broom-style.

Parameters:

Name Type Description Default
conf_level float

Confidence level of the interval columns. Defaults to the fit-time level (0.95 unless the estimator was called with alpha=).

None
Columns

term : str Variable / coefficient name. estimate : float std_error : float statistic : float t-statistic. p_value : float conf_low, conf_high : float Two-sided conf_level CI.

Examples:

>>> import numpy as np, pandas as pd
>>> import statspai as sp
>>> rng = np.random.default_rng(0)
>>> df = pd.DataFrame({"x1": rng.normal(size=100), "x2": rng.normal(size=100)})
>>> df["y"] = 1 + df.x1 - df.x2 + rng.normal(size=100)
>>> result = sp.regress("y ~ x1 + x2", data=df)
>>> result.tidy().columns[:3].tolist()
['term', 'estimate', 'std_error']
See Also

glance : 1-row model-level summary (R^2, F, N, AIC, BIC).

coef

coef(term: str, conf_level: Optional[float] = None) -> Series

One coefficient's full inference row as a named Series.

Shorthand for result.tidy().set_index("term").loc[term] — the estimate, standard error, t-statistic, p-value, and CI for a single term without the indexing dance.

Parameters:

Name Type Description Default
term str

Coefficient name as it appears in params / tidy().

required
conf_level float

CI level for conf_low / conf_high. Defaults to the fit-time level, as :meth:tidy does (0.95 unless the estimator was called with alpha=).

None

Returns:

Type Description
Series

Index: estimate, std_error, statistic, p_value, conf_low, conf_high; name = term.

Examples:

>>> import numpy as np, pandas as pd
>>> import statspai as sp
>>> rng = np.random.default_rng(0)
>>> x = rng.normal(size=200)
>>> df = pd.DataFrame({"y": 2.0 * x + rng.normal(size=200), "x": x})
>>> row = sp.regress("y ~ x", data=df).coef("x")
>>> bool(abs(row["estimate"] - 2.0) < 0.2)
True
>>> bool(row["p_value"] < 0.01)
True

test

test(hypothesis: str) -> Dict[str, Any]

Wald test of linear restrictions -- Stata's test after a fit.

Shorthand for :func:statspai.test (result, hypothesis).

Parameters:

Name Type Description Default
hypothesis str

"x1 = 0", "x1 = x2", "x1 = x2 = 0" (joint) or "x1 + x2 = 1".

required

Returns:

Type Description
dict

statistic (F), pvalue, df, chi2, hypothesis.

Examples:

>>> import numpy as np, pandas as pd
>>> import statspai as sp
>>> rng = np.random.default_rng(0)
>>> x1, x2 = rng.normal(size=200), rng.normal(size=200)
>>> df = pd.DataFrame({"y": x1 + x2 + rng.normal(size=200),
...                    "x1": x1, "x2": x2})
>>> out = sp.regress("y ~ x1 + x2", data=df).test("x1 = x2")
>>> bool(out["pvalue"] > 0.01)
True

lincom

lincom(expression: str, alpha: float = 0.05) -> Dict[str, Any]

Linear combination of coefficients -- Stata's lincom.

Shorthand for :func:statspai.lincom (result, expression, alpha).

Parameters:

Name Type Description Default
expression str

"x1 + x2", "x1 - x2", "2*x1 + 3*x2".

required
alpha float

Significance level for the confidence interval.

0.05

Returns:

Type Description
dict

Estimate, standard error, test statistic, p-value and CI.

Examples:

>>> import numpy as np, pandas as pd
>>> import statspai as sp
>>> rng = np.random.default_rng(0)
>>> x1, x2 = rng.normal(size=200), rng.normal(size=200)
>>> df = pd.DataFrame({"y": x1 + x2 + rng.normal(size=200),
...                    "x1": x1, "x2": x2})
>>> out = sp.regress("y ~ x1 + x2", data=df).lincom("x1 + x2")
>>> sorted(out)
['ci', 'df', 'distribution', 'estimate', 'expression', 'pvalue', 'se',
 'statistic', 'z']

glance

glance() -> DataFrame

Return a 1-row DataFrame of model-level statistics, broom-style.

Columns (present subset depends on the model type)

nobs : int r_squared : float adj_r_squared : float f_statistic : float f_p_value : float aic, bic : float df_resid, df_model : int method : str Estimation method label.

See Also

tidy : long-format coefficient table.

fixef

fixef() -> Dict[str, Series]

Estimated fixed effects of a sp.feols / fepois / feglm fit.

R fixest::fixef / Stata reghdfe, savefe: one Series per absorbed effect, indexed by its levels, on the scale of the linear index. Effects are identified up to one constant per connected set of levels (per additional effect); the normalisation is fixest's (see :mod:statspai.fixest._fixef). Sums of effects, and so predictions, do not depend on it.

Returns:

Type Description
dict of str to pd.Series

Examples:

>>> import numpy as np, pandas as pd
>>> import statspai as sp
>>> rng = np.random.default_rng(0)
>>> d = pd.DataFrame({"g": rng.integers(0, 5, 200), "x": rng.normal(size=200)})
>>> d["y"] = d.g + 0.5 * d.x + rng.normal(size=200)
>>> fit = sp.feols("y ~ x | g", d)
>>> fe = fit.fixef()["g"]
>>> len(fe)
5

predict

predict(data: Optional[DataFrame] = None, what: str = 'mean', alpha: float = 0.05, *, newdata: Optional[DataFrame] = None) -> Union[ndarray, DataFrame]

Generate predictions from the fitted model.

Parameters:

Name Type Description Default
data DataFrame

New data for out-of-sample prediction, holding the variables of the formula (the design -- I(x**2), np.log(x), C(g), interactions -- is rebuilt from them). If None, the estimation sample is used. After a fit with absorbed fixed effects (sp.feols / fepois / feglm with | fe) the prediction adds the estimated effects of each row's levels, as R fixest's predict does; a level the fit never saw gives NaN, with a warning.

None
what (mean, link, confidence, prediction)
  • "mean" -- the predicted mean of the outcome. For a model with a link function (sp.glm, sp.poisson, sp.nbreg) this is on the scale of the outcome, in and out of sample.
  • "link" -- the linear index x'b.
  • "confidence" -- the predicted mean with its standard error and a 1 - alpha confidence interval for E[y | x]. With a link function the interval is built for the linear index and mapped to the scale of the outcome.
  • "prediction" -- a 1 - alpha prediction interval for a new observation of a linear model, which adds the error variance s^2 to the variance of the estimated mean. It assumes homoskedastic errors whatever covariance estimator the fit used, and is not defined after a weighted fit.
"mean"
alpha float

One minus the coverage of the interval.

0.05
newdata DataFrame

Alias of data (the R spelling).

None

Returns:

Type Description
ndarray or DataFrame

The predictions ("mean", "link"), or a DataFrame with columns yhat, se, lower, upper.

Examples:

>>> import numpy as np, pandas as pd
>>> import statspai as sp
>>> rng = np.random.default_rng(0)
>>> df = pd.DataFrame({"x": rng.normal(size=200)})
>>> df["y"] = 1.0 + 0.5 * df["x"] + rng.normal(size=200)
>>> fit = sp.regress("y ~ x + I(x**2)", data=df)
>>> new = pd.DataFrame({"x": [0.0, 1.0]})
>>> fit.predict(new).shape
(2,)
>>> list(fit.predict(new, what="prediction").columns)
['yhat', 'se', 'lower', 'upper']

residuals

residuals() -> Optional[ndarray]

Return model residuals if available

Returns:

Type Description
ndarray or None

Residuals

fitted_values

fitted_values() -> Optional[ndarray]

Return fitted values if available

Returns:

Type Description
ndarray or None

Fitted values

next_steps

next_steps(print_result: bool = False) -> List[Dict[str, str]]

Agent-native workflow guidance: what to do after fitting this model.

Returns a list of recommended next steps — diagnostics, robustness checks, sensitivity analysis, and export options — tailored to the model type (OLS, IV, panel, etc.).

Parameters:

Name Type Description Default
print_result bool

Also print the formatted recommendations to stdout. Off by default so calling next_steps() from an MCP stdio server, a CLI pipe or a batch job never writes a banner into the output stream; the return value is identical.

False

Returns:

Type Description
list of dict

Each dict has keys: action, reason, priority, category.

Examples:

>>> import numpy as np, pandas as pd
>>> import statspai as sp
>>> rng = np.random.default_rng(0)
>>> df = pd.DataFrame({"x1": rng.normal(size=100), "x2": rng.normal(size=100)})
>>> df["y"] = 1 + df.x1 - df.x2 + rng.normal(size=100)
>>> result = sp.regress("y ~ x1 + x2", data=df)
>>> steps = result.next_steps(print_result=False)
>>> steps[0]["action"]
"sp.estat(result, 'all')"

violations

violations() -> List[Dict[str, Any]]

Agent-native structured list of assumption / diagnostic issues.

Inspects stored diagnostics (first-stage F, standard error finiteness, …) and returns any flagged concerns as dicts with keys kind, severity, test, value, threshold, message, recovery_hint, alternatives.

Returns:

Type Description
list of dict

Empty list if nothing flagged.

Examples:

>>> import numpy as np, pandas as pd
>>> import statspai as sp
>>> rng = np.random.default_rng(0)
>>> df = pd.DataFrame({"z": rng.normal(size=300), "c": rng.normal(size=300)})
>>> df["x"] = df.z + rng.normal(size=300)
>>> df["y"] = 1 + 0.5 * df.x + df.c + rng.normal(size=300)
>>> result = sp.iv("y ~ (x ~ z) + c", data=df)
>>> for v in result.violations():
...     if v['severity'] == 'error':
...         print(v['recovery_hint'])

to_agent_summary

to_agent_summary() -> Dict[str, Any]

JSON-ready nested summary for agent consumption.

Unlike summary() (prose for humans) and tidy() (long-form DataFrame), this returns a plain dict with coefficients, scalar diagnostics, violations, and recommended next steps — suitable for feeding into an LLM tool loop or logging.

Returns:

Type Description
dict

Keys: kind, model_type, robust, n_obs, df_resid, dependent_var, coefficients, diagnostics, violations, next_steps.

See Also

to_dict : Canonical flat agent payload — prefer to_dict(detail="agent") for new code. to_agent_summary is kept because it surfaces a richer kind / model_type / robust triplet that to_dict collapses into a single method field; two methods, two intentionally different shapes.

Examples:

>>> import numpy as np, pandas as pd
>>> import statspai as sp
>>> rng = np.random.default_rng(0)
>>> df = pd.DataFrame({"x1": rng.normal(size=100), "x2": rng.normal(size=100)})
>>> df["y"] = 1 + df.x1 - df.x2 + rng.normal(size=100)
>>> result = sp.regress("y ~ x1", data=df)
>>> import json
>>> agent_payload = json.dumps(result.to_agent_summary(), default=str)
>>> agent_payload.startswith("{")
True

to_docx

to_docx(filename: str, title: Optional[str] = None) -> None

Export results to a Word (.docx) document.

Parameters:

Name Type Description Default
filename str

Output path (.docx).

required
title str

Table title. Defaults to model type.

None

to_latex

to_latex(path: Optional[str] = None, *, caption: Optional[str] = None, label: Optional[str] = None, siunitx: bool = False, threeparttable: bool = False, siunitx_preamble: bool = False, booktabs: bool = False, **kwargs: Any) -> str

Render the regression result as a publication-quality LaTeX table.

A thin wrapper over :func:~statspai.output.regtable (single column). Produces a \begin{table} float with significance stars and standard errors in parentheses; booktabs=True draws \toprule / \midrule / \bottomrule rules.

Parameters:

Name Type Description Default
path str

If given, the LaTeX source is also written to this file (UTF-8). The string is always returned.

None
caption str

\caption{...} text (maps to regtable(title=...)).

None
label str

\label{...} cross-reference id, injected after the caption.

None
siunitx bool

Decimal-align numeric columns with siunitx S columns (journal style; requires \usepackage{siunitx} v3).

False
threeparttable bool

Wrap the table in threeparttable with a tablenotes block (requires \usepackage{threeparttable}).

False
siunitx_preamble bool

Prepend a comment listing the required \usepackage lines.

False
booktabs bool

booktabs rules instead of \hline (requires \usepackage{booktabs}).

False
**kwargs Any

Forwarded to :func:~statspai.output.regtable — e.g. coef_labels, keep, drop, order, stats, se_type, stars, star_levels, fmt, template, notes.

{}

Returns:

Type Description
str

LaTeX source.

Examples:

>>> import numpy as np, pandas as pd
>>> import statspai as sp
>>> rng = np.random.default_rng(0)
>>> df = pd.DataFrame({"z": rng.normal(size=300), "c": rng.normal(size=300)})
>>> df["x"] = df.z + rng.normal(size=300)
>>> df["y"] = 1 + 0.5 * df.x + df.c + rng.normal(size=300)
>>> r = sp.regress("y ~ x + z", data=df)
>>> tex = r.to_latex(caption="Main results", label="tab:main",
...                  coef_labels={"x": "Treatment"}, template="aer")
>>> tex = r.to_latex(siunitx=True, threeparttable=True)  # journal style

to_html

to_html(path: Optional[str] = None, **kwargs: Any) -> str

Render the regression result as an HTML table.

A thin wrapper over :func:~statspai.output.regtable. Returns the HTML string; also writes it to path when given. See :meth:to_latex for the forwarded **kwargs.

to_markdown

to_markdown(path: Optional[str] = None, *, quarto: bool = False, **kwargs: Any) -> str

Render the regression result as a Markdown table.

A thin wrapper over :func:~statspai.output.regtable. Set quarto=True for Quarto-flavoured output. Returns the Markdown string; also writes it to path when given. See :meth:to_latex for the forwarded **kwargs.

to_excel

to_excel(path: str, **kwargs: Any) -> str

Write the regression result to a styled .xlsx workbook.

A thin wrapper over :func:~statspai.output.regtable with booktabs-style cell borders (requires openpyxl). Returns path. See :meth:to_latex for the forwarded **kwargs.

to_word

to_word(path: str, *, caption: Optional[str] = None, **kwargs: Any) -> str

Write the regression result to a publication-quality .docx.

A thin wrapper over :func:~statspai.output.regtable with AER/QJE booktabs rules and Times New Roman typography (requires python-docx). Returns path. Unlike :meth:to_docx (which renders the broom-style coefficient grid), this routes through the same publication table builder used for multi-model exports, so the single-model output matches a one-column regtable. See :meth:to_latex for the forwarded **kwargs.

plot

plot(type: str = 'auto', **kwargs: Any) -> Any

Coefficient plot of this fit (sp.coefplot).

type is 'auto' or 'coefplot'; **kwargs go to :func:statspai.coefplot (variables=, drop=["Intercept"], alpha=, ax= ...). Returns (fig, ax).

to_dict

to_dict(*, detail: str = 'standard') -> Dict[str, Any]

Return a JSON-safe dict representation of the regression result.

Parameters:

Name Type Description Default
detail (minimal, standard, agent)

Payload depth, bounded by approximate token budget:

  • "minimal" (~ < 600 chars / < 150 tokens) — identity only: method, model_type, dependent_var, n_obs, plus fit_stats (R², F, AIC, BIC) when available. No coefficient table.
  • "standard" (variable, ~ 50 chars × n_terms) — full coefficient table + diagnostics + glance row. Matches the legacy to_dict() shape.
  • "agent" — standard + violations + warnings + next_steps + suggested_functions. Equivalent to legacy :meth:for_agent and the form returned by sp.agent.execute_tool and the MCP server.
"minimal"

Returns:

Type Description
dict

JSON-safe and bounded — round-trips through json.dumps.

Notes

Used by sp.agent.execute_tool to send results back to an LLM, and useful for caching / pickling-free persistence.

result_card

result_card() -> Any

Auditable card: estimand, sample, specification, inference, provenance, configuration-level evidence, assumptions, limitations.

See :func:statspai.result_card.

cite

cite(format: str = 'bibtex') -> Any

Return the canonical citation for this estimator, if registered.

Mirrors :meth:CausalResult.cite so an agent can call .cite() on any fitted result uniformly — and sp.bib_for(result) (which duck-types on .cite) now works for regression results too.

Parameters:

Name Type Description Default
format (bibtex, apa, json)

Same semantics as :meth:CausalResult.cite. "bibtex" / "apa" return str; "json" returns a structured dict.

"bibtex"

Returns:

Type Description
str | dict
Notes

Zero-hallucination (CLAUDE.md §10): BibTeX comes from the single source of truth CausalResult._CITATIONS (mirroring :file:paper.bib); APA / JSON forms are derived from that string, never generated. Resolution is exact on model_info['citation_key'] → model_type → method (normalised), so a textbook estimator with no canonical paper (OLS / logit / probit / poisson) honestly returns a placeholder rather than a fuzzy — and possibly wrong — match. Estimators that do have a canonical reference (e.g. tobit, heckman) should set model_info['citation_key'] or carry a matching model_type.

to_appendix

to_appendix(format: str = 'latex', *, include_assumptions: bool = True, include_diagnostics: bool = True, include_citation: bool = True, include_provenance: bool = True) -> str

Generate a Methods and Formulas appendix for this model.

Shares the curated, zero-hallucination methods table with :class:CausalResult (CLAUDE.md §10). IV / 2SLS regressions resolve to the instrumental-variables entry; other regression families that are not in the causal methods table degrade to an explicit placeholder plus the inference actually used and a provenance trace — never an invented formula.

Parameters:

Name Type Description Default
format (latex, markdown, text)

Output format.

"latex"
include_assumptions bool

Include the identifying-assumptions list (when registered).

True
include_diagnostics bool

Include the inference block read off model_info.

True
include_citation bool

Append the APA-style reference from :meth:cite.

True
include_provenance bool

Append a one-line provenance trace (StatsPAI version + estimator identity + methods-spec key).

True

Returns:

Type Description
str

The assembled appendix.

Examples:

>>> import numpy as np, pandas as pd
>>> import statspai as sp
>>> rng = np.random.default_rng(0)
>>> df = pd.DataFrame({"z": rng.normal(size=300), "c": rng.normal(size=300)})
>>> df["x"] = df.z + rng.normal(size=300)
>>> df["y"] = 1 + 0.5 * df.x + df.c + rng.normal(size=300)
>>> res = sp.ivreg("y ~ c + (x ~ z)", data=df)
>>> md = res.to_appendix(format="markdown")
>>> isinstance(md, str) and len(md) > 0
True

for_agent

for_agent() -> Dict[str, Any]

Agent-ready payload — alias for to_dict(detail="agent").

Kept for backward compatibility with code written before the unified detail parameter. New code should prefer to_dict(detail="agent") for explicit semantics.

brief

brief() -> str

One-line dashboard status string (≤ ~120 chars).

Surfaces the most-significant non-intercept coefficient so agents scanning a list of regressions can spot the active finding without paying a full to_dict round-trip.

to_json

to_json(indent: Optional[int] = None, detail: str = 'standard') -> str

Serialise :meth:to_dict via json.dumps.

detail is forwarded to :meth:to_dict; pass "agent" to include violations / next_steps / degradations in the JSON.

sensitivity

sensitivity(**kwargs: Any) -> Any

Run the unified sensitivity dashboard on this result.

See :func:statspai.robustness.unified_sensitivity.