Skip to content

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 validation
  • value: the value the caller passed in
  • candidates: tuple of legal names (named-set branch); empty otherwise
  • suggestions: difflib top-3 fuzzy matches against candidates
  • expected: human-readable shape (type-mismatch branch); None otherwise
  • docs_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

RunMetricsError(message: str, *, cell: str, metric_name: str, stage: str)

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

"<scope>/<signal>" of the dispatched cell.

metric_name

The metric whose call raised, or the stage-1 helper name when stage == "stage1".

stage

Which dispatch stage raised — "stage1" (shared helper failed; the whole module's consumers are skipped) or "consumer" (an individual metric raised).