Core concepts#
Every estimator in cultivars follows the same few conventions. This page states them once, so that the API reference can assume them.
Model, fit, result#
A model object holds the data and the specification and nothing else.
fit() estimates it and returns a result:
ARMAResult
Arguments that describe what model go to the constructor: orders,
trend, priors, variable names. Arguments that describe how to
estimate it go to fit(): the number of posterior draws, a random
seed. One specification can therefore be fitted several ways without
being rebuilt.
Results are records#
A result is a frozen dataclass. It carries the estimates, the data needed to forecast and simulate from them, and the diagnostics, all as attributes, and it cannot be changed after it is built:
print(round(result.sigma2, 4), result.nobs, result.is_stationary)
try:
result.sigma2 = 1.0
except AttributeError as error:
print(type(error).__name__)
0.927 199 True
FrozenInstanceError
Because results are values rather than handles to a model, any consumer that reads a result’s attributes can take any result that has them:
a structural identification takes any closed reduced-form result (a VAR, a VARMA, an error-correction model);
a backtest takes any fitting function;
a model comparison takes any results fitted to the same sample.
None of them needs to know which estimator produced its input. To change a fit, build a new model and fit it again; the old result stays exactly as it was.
Summaries state their assumptions#
summary() returns a SummaryTable, a
record with four parts:
title: the specification;metadata: label/value pairs such as sample size, log-likelihood and information criteria;rows: one table of estimates or statistics;notes: what the numbers assume and anything the estimator could not deliver.
Printing a result prints its summary, and a result left as the last line of a notebook cell renders it as HTML. The notes are part of the output, not decoration. A summary that says the AR polynomial has a root near the unit circle, or that standard errors are unavailable, is telling you how far to trust the numbers above it.
ARIMA(1, 1, 0) Results
Standard errors are not yet available for this estimator.
With the pandas or polars extra installed, to_pandas() and
to_polars() turn a summary table into a dataframe, and do the same
for results that carry per-observation output (fitted values and
residuals).
Tests return verdicts#
A hypothesis test returns a record too, with the same core fields whatever the test:
Field |
Meaning |
|---|---|
|
The test statistic. |
|
Its p-value under the null. |
|
The null hypothesis, in words. |
|
The verdict at level |
|
The full table: null, statistic, p-value, critical values where they exist, and notes on the reference distribution. |
A test run at several settings at once, such as the BDS test at several
embedding dimensions, returns the headline setting and lists the others
in companions.
Structural analysis comes in two stages#
A reduced form is estimable from the data alone. A structural
interpretation is not: it rests on identifying restrictions that the
data cannot check. cultivars keeps the two apart. A VAR result knows
nothing about economic shocks. A scheme from
cultivars.multivariate.structural takes the fitted reduced form
and returns a separate structural result:
var = VAR(y, order=4, names=names).fit() # reduced form
svar = RecursiveSVAR(var, order=names).identify() # point-identified
accepted = SignRestrictedSVAR(var, signs, draws=1000).identify() # set-identified
A point-identified scheme returns one structural result. A set-identified scheme returns the set of accepted models, summarized by quantiles rather than a point estimate. Either way, the reduced-form result is untouched, so several schemes can be applied to one fit and compared directly.
Data in#
Shape. A single series is any 1-D array-like of length
n; a system is an(n, k)array-like with one column per variable. Rows are time periods, oldest first. Lists, NumPy arrays, and pandas or polars objects are all accepted and converted tofloat64.Names. Pass labels explicitly with
names=. Column names on a dataframe are not read, and withoutnamesvariables are labelledy1,y2, and so on. Methods that take a variable, such asgranger_causality("rate", "growth"), accept these names.Dates. Estimation works on positions, not dates. To attach dates to per-observation output, pass them when exporting, for example
result.to_pandas(index=dates).Missing values. Non-finite values are refused with a
NumericalErrorrather than dropped or filled silently. Handle gaps before fitting, so that the choice is yours and visible.
Randomness#
Every routine that draws random numbers takes a seed argument
(an integer, a numpy.random.Generator, or None), and the
same seed reproduces the same draws. This covers posterior sampling,
rotation draws in sign restrictions, bootstraps and simulation. Fix the
seed in anything you intend to publish.
When something is wrong#
Every error cultivars raises on purpose derives from
CultivarsError. The class tells you what
to fix, and the message names the argument and the values it accepts:
Exception |
Also a |
Raised when |
|---|---|---|
|
An input has the wrong shape, rank or length. |
|
|
An option is outside its allowed values, an order is too small, the sample is too short for what was asked, or a set of restrictions cannot be satisfied. |
|
|
The arithmetic fails: non-finite data, a covariance that is not positive semidefinite, a singular system. |
from cultivars.exceptions import CultivarsError
try:
ARIMA(y, order=(1, 1))
except CultivarsError as error:
print(type(error).__name__, "-", error)
SpecificationError - order must have 3 elements ('p', 'd', 'q'); got (1, 1).
except CultivarsError catches every problem the library detects and
nothing else. Anything that escapes it, such as an AssertionError
or a raw NumPy error, is a bug, and worth
reporting.
Imports and stability#
Names are imported from the leaf module that defines them:
from cultivars.univariate.box_jenkins import ARIMA
from cultivars.multivariate.reduced_form.vector_autoregression import VAR
The subpackages and the top-level cultivars package re-export
nothing, so an import path always tells you the model family. Names
beginning with an underscore, and everything under
cultivars.engine, are implementation detail and may change
without notice. Until 1.0.0b1, public argument names, result fields
and summary wording may still change between pre-releases; each change
is listed in the release notes.