Errors
How to read factrix errors and which exception class to catch.
TL;DR¶
import factrix as fx
try:
profile = fx.evaluate(panel, cfg)
except fx.UserInputError as exc:
# User typed the wrong thing — typo, unknown name, wrong column.
# The message carries a fuzzy suggestion + a docs link.
print(exc)
except fx.ConfigError as exc:
# AnalysisConfig validation / dispatch failure.
# exc.suggested_fix may carry a nearest-legal AnalysisConfig.
...
except fx.FactrixError as exc:
# Catch-all for anything else factrix raises.
...
All factrix-raised exceptions inherit from FactrixError, so a single
except fx.FactrixError blocks every library-raised failure.
Exception hierarchy¶
FactrixError # base
├── ConfigError # AnalysisConfig validation / dispatch
│ ├── MissingConfigError # evaluate(panel) called without a config
│ ├── IncompatibleAxisError # (scope, signal, metric) is not a legal cell
│ ├── ModeAxisError # legal cell, no procedure at runtime mode
│ └── InsufficientSampleError # T below MIN_PERIODS_HARD on a TIMESERIES procedure
└── UserInputError # named-set typo / type mismatch
| Exception | When you see it | What it carries |
|---|---|---|
MissingConfigError |
evaluate(panel) called without an AnalysisConfig |
— (call fx.suggest_config(panel) to recover) |
IncompatibleAxisError |
(scope, signal, metric) is not a legal cell |
optional .suggested_fix |
ModeAxisError |
Legal cell has no procedure at the runtime Mode |
typically .suggested_fix: AnalysisConfig |
InsufficientSampleError |
T below the procedure floor |
.actual_periods, .required_periods |
UserInputError |
Unknown metric / p_stat / context key, column not in panel, wrong type |
structured .field, .value, .candidates, .suggestions, .expected, .docs_url |
Error → fix mapping¶
Concrete messages, what triggers them, and where to look for the fix. Use the table to skim; jump to the linked page for the why.
Panel-schema failures¶
| Message hint | Trigger | Fix |
|---|---|---|
factor_col 'X' not in panel columns |
Typo or wrong column name | Check panel.columns; pass the actual name to factor_col=. See Panel schema § factor_col=. |
Both 'factor' and 'X' present |
Wide panel still has stale "factor" column alongside the renamed one |
panel.drop("factor") before calling. |
forward_return column missing |
Forgot the preprocess step | compute_forward_return(raw, forward_periods=h) before evaluate. See Panel schema § Preprocess pipeline. |
Config failures (ConfigError family)¶
| Exception / message | Trigger | Fix |
|---|---|---|
MissingConfigError: evaluate(panel) needs AnalysisConfig |
evaluate(panel) called with no cfg |
fx.suggest_config(panel) returns a SuggestConfigResult with .suggested (an AnalysisConfig) and per-axis reasoning. See suggest_config. |
IncompatibleAxisError: (scope, signal, metric) is not a legal cell |
Combination like (INDIVIDUAL, SPARSE, IC) that the dispatch table never registers |
Use one of the four factories (individual_continuous, individual_sparse, common_continuous, common_sparse) — illegal combos are unreachable via factories. See Concepts. |
ModeAxisError: no procedure at runtime Mode=TIMESERIES |
Legal cell, but N = panel["asset_id"].n_unique() triggers a Mode the cell does not implement (typical case: individual_continuous at N=1) |
Read .suggested_fix — it carries the nearest-legal AnalysisConfig for the actual Mode. See Quickstart § N = 1. |
InsufficientSampleError: T below required |
n_periods below the procedure's MIN_PERIODS_HARD floor |
Read .actual_periods and .required_periods. The fix is either more data, or — if the procedure is not the right one for short series — switching to a TIMESERIES-friendly cell. See Panel vs timeseries. |
User-input failures (UserInputError)¶
Every UserInputError carries structured attributes (see
Reading a UserInputError). Common
triggers and fix paths:
| Message hint | Trigger | Fix |
|---|---|---|
unknown metric='...' |
Typo or metric not applicable to the cell | list_metrics(scope, signal) enumerates the applicable set; exc.suggestions carries the top-3 fuzzy candidates. See list_metrics. |
unknown estimator='...' |
Typo or estimator not applicable to the cell | list_estimators(scope, signal) enumerates the applicable set. See list_estimators. |
unknown expand_over='...' |
Context key not present on every profile in the family | All profiles in the family must carry the key in .context; check that the caller is populating it consistently at evaluate time. See Cross-function reference § expand_over for the three different expand_over semantics across functions. |
expand_over=[...] requires every profile to carry key 'X' |
One or more profiles missing the key | Confirm the upstream evaluate call records the key in context=.... |
Expected: list[FactorProfile], got list[MetricsBundle] |
Passing the wrong artefact family to a screening function | Screening (bhy, partial_conjunction, bhy_hierarchical) consumes list[FactorProfile] (primary-p carriers). For descriptive cross-factor views use compare(bundles). See API reference § Typical patterns. |
Reading a UserInputError¶
Every user-facing raise that takes a named input renders the same three-part message:
bhy(): unknown expand_over='univere_id'
Did you mean: "universe_id"?
Available: ['regime_id', 'sector', 'universe_id']
Docs: https://awwesomeman.github.io/factrix/api/bhy#expand_over
| Line | What to look at |
|---|---|
<func_name>(): unknown <field>=<value> |
Which kwarg / column triggered the raise, and what value was received. |
Did you mean: "..." |
Top-3 fuzzy candidates (omitted when nothing matches above the cutoff). |
Available: [...] |
The full legal set — sorted, so the same set always renders identically. |
Docs: https://... |
The function's deployed-docs anchor. |
For type / shape mismatches the second line reads Expected: <shape>
instead of Did you mean: ... — same three-part structure, different
diagnostic.
Programmatic recovery¶
The structured attributes are the contract — read them, do not parse the rendered message:
import factrix as fx
bad: dict[str, object] = {}
for cfg in candidates:
try:
profiles.append(fx.evaluate(panel, cfg))
except fx.UserInputError as exc:
bad[exc.field] = exc.value
# exc.suggestions carries top-3 fuzzy matches when applicable
| Attribute | Meaning |
|---|---|
func_name |
The calling function (e.g. "bhy", "evaluate"). |
field |
The kwarg / column name that failed validation. |
value |
The value the caller passed in. |
candidates |
Sorted tuple of legal names (named-set branch); () otherwise. |
suggestions |
difflib top-3 matches against candidates; () when none. |
expected |
Human-readable shape (mismatch branch); None otherwise. |
docs_url |
Resolved deployed-docs URL for the function. |
Raising your own UserInputError¶
If you build functions on top of factrix and want the same canonical
format, construct a UserInputError directly — it is keyword-only and
renders its own message:
import factrix as fx
if metric_name not in fx.list_metrics(cfg):
raise fx.UserInputError(
func_name="run_metrics",
field="metrics",
value=metric_name,
candidates=fx.list_metrics(cfg),
docs_path="api/run_metrics#metrics",
)
Pass exactly one of candidates / expected. The rendered message is
human-readable output; downstream code should rely on the attributes
above, not on substring matches against str(exc).
Class reference¶
Autodoc anchors for cross-references of the form
[`<Error>`][factrix.<Error>] from any docs page.
Base¶
factrix.FactrixError ¶
Bases: Exception
Base for all factrix-raised errors.
User-input failures¶
factrix.UserInputError ¶
UserInputError(*, func_name: str, field: str, value: object, candidates: Iterable[object] | None = None, expected: str | None = None, docs_path: str)
Bases: FactrixError, ValueError
User-supplied input does not match expected names or types.
Raised for typos in named-set kwargs (metric / estimator / context key
/ column name) or input-type mismatches. Multi-inherits from
:class:ValueError so ecosystem code (pytest.raises(ValueError),
generic except ValueError) keeps working.
Structured attributes carry the diagnostic so callers (sub-issue raises, LLM agents) do not parse the rendered message:
func_name: the calling function name (no parens)field: the kwarg / column name that failed validationvalue: the value the caller passed incandidates: tuple of legal names (named-set branch); empty otherwisesuggestions: difflib top-3 fuzzy matches againstcandidatesexpected: human-readable shape (type-mismatch branch);Noneotherwisedocs_url: deployed-docs URL for the function
Config failures¶
factrix.ConfigError ¶
ConfigError(message: str, *, suggested_fix: AnalysisConfig | None = None)
Bases: FactrixError
Base for AnalysisConfig validation / dispatch errors.
suggested_fix is populated from _FALLBACK_MAP when the
nearest legal cell is unambiguous (e.g. ModeAxisError on
(INDIVIDUAL, CONTINUOUS, N=1) suggests common_continuous).
Stays None when the failure is a data limitation rather than
an axis-tuple miswire.
factrix.MissingConfigError ¶
MissingConfigError(message: str, *, suggested_fix: AnalysisConfig | None = None)
Bases: ConfigError
evaluate(panel) called without an AnalysisConfig.
Friendly replacement for the bare TypeError from the private
_evaluate signature. suggested_fix stays None — call
factrix.suggest_config(panel) to get a concrete recommendation.
factrix.IncompatibleAxisError ¶
IncompatibleAxisError(message: str, *, suggested_fix: AnalysisConfig | None = None)
Bases: ConfigError
(scope, signal, metric) tuple is not a legal analysis cell.
Reachable via direct construction or from_dict; factory methods
never trigger this. Covers e.g. signal=SPARSE paired with
metric=IC, or (INDIVIDUAL, CONTINUOUS) with metric=None.
factrix.ModeAxisError ¶
ModeAxisError(message: str, *, suggested_fix: AnalysisConfig | None = None)
Bases: ConfigError
Axis tuple is legal but undefined at the runtime Mode.
Raised at evaluate-time (Mode is not part of AnalysisConfig).
Canonical example: (INDIVIDUAL, CONTINUOUS, IC) with N == 1
has no cross-sectional dispersion → information coefficient (IC) undefined; suggested_fix
points the user at common_continuous(...).
factrix.InsufficientSampleError ¶
InsufficientSampleError(message: str, *, actual_periods: int, required_periods: int, suggested_fix: AnalysisConfig | None = None)
Bases: ConfigError
T < MIN_PERIODS_HARD for a TIMESERIES procedure.
Below the floor, Newey-West (NW) heteroskedasticity-and-autocorrelation-consistent (HAC) SE is too biased for primary_p to be
trustworthy. Raised at evaluate-time. suggested_fix is None
— this is a data limitation, not an axis-tuple miswire. actual_periods
and required_periods carry the numbers so callers can recover or
aggregate programmatically (review fix UX-3).
factrix.UnknownEstimatorError ¶
UnknownEstimatorError(message: str, *, suggested_fix: AnalysisConfig | None = None)
Bases: ConfigError, ValueError
get_estimator(name) lookup miss (#163).
Inherits ValueError so pytest.raises(ValueError) and the
ecosystem UserInputError convention both catch it. Raised by
factrix.stats.get_estimator and by AnalysisConfig.from_dict
when estimator name is not in the registry.
Run-metrics failures¶
factrix.RunMetricsError ¶
Bases: FactrixError
Unexpected exception inside run_metrics dispatch.
Wraps an unexpected error raised by a stage-1 helper or a metric
consumer so the caller can identify which metric / stage failed
without parsing tracebacks. The original exception is chained via
raise ... from exc so __cause__ carries the full stack.
A wrapped exception means the failure is not a known
sample-floor / data-quality short-circuit — those are converted to
short-circuit MetricOutput entries inside the bundle. Treat
RunMetricsError as a likely factrix bug and report.
Attributes:
| Name | Type | Description |
|---|---|---|
cell |
|
|
metric_name |
The metric whose call raised, or the stage-1
helper name when |
|
stage |
Which dispatch stage raised — |