Skip to content

factrix.compare

compare(results: list[EvaluationResult], *, metrics: list[str], sort_by: str | None = None, descending: bool = True) -> DataFrame

Render a wide leaderboard pl.DataFrame for multiple metrics.

One row per :class:EvaluationResult; two columns per requested metric label — <metric_label> (MetricResult.value) and <metric_label>_p_value (MetricResult.p_value when present, else null). The label is the key in EvaluationResult.metrics, usually the user-supplied key from evaluate(metrics={...}).

Parameters:

Name Type Description Default
results list[EvaluationResult]

Non-empty list of :class:EvaluationResult. Each must carry every spec in metrics.

required
metrics list[str]

list[str] — list-only canonical form (element type strictly :class:str). Single-metric callers still pass a one-element list; mirrors the metrics contract on fx.multi_factor.bhy so the whole multi-factor API surface uses one shape.

required
sort_by str | None

Optional :class:str naming any output column produced before ranking: identity columns, params keys, metric value columns, or <metric_label>_p_value columns. None keeps input order and omits the rank column.

None
descending bool

Sort direction applied to sort_by. Default True (higher-is-better, the common case for ic / alpha / information ratio). Pass descending=False for lower-is-better metrics such as rank_turnover or any cost / drag metric. :class:str deliberately does not carry a higher_is_better flag — encoding sort direction in the type system bakes in a default that silently mis-ranks when wrong. No-op when sort_by is None.

True

Returns:

Type Description
DataFrame

pl.DataFrame with column order factor,

DataFrame

forward_periods, params keys (union across results,

DataFrame

first-seen order), then <metric_label> /

DataFrame

<metric_label>_p_value pairs

DataFrame

in metrics order, then rank when sort_by is set.

Raises:

Type Description
UserInputError

Empty results; metrics not a non-empty list[str]; any metric absent from any result's outputs; sort_by not present in the output columns.

Examples:

Multi-metric wide leaderboard sorted on IC:

>>> board = fx.compare(
...     results, metrics=["ic", "sharpe"], sort_by="ic"
... )

Lower-is-better metric (descending=False):

>>> board = fx.compare(
...     results, metrics=["rank_turnover"], sort_by="rank_turnover", descending=False
... )

Leaderboard renderer that stacks N evaluation results side by side as a polars DataFrame. Pure projection — no metric is recomputed.

import factrix as fx
from factrix.metrics import ic, quantile_spread

results = fx.evaluate(
    data,
    metrics={"ic": ic(inference=fx.inference.NEWEY_WEST), "spread": quantile_spread()},
    factor_cols=candidates,
)
# evaluate() returns a dict keyed by factor; compare() takes the list of results.
df = fx.compare(list(results.values()), metrics=["ic", "spread"], sort_by="ic")

Input parameters

compare(
    results: list[EvaluationResult],
    *,
    metrics: list[str],
    sort_by: str | None = None,
    descending: bool = True,
) -> pl.DataFrame

Column layout

The returned pl.DataFrame contains the following columns:

  • factor: The name of the evaluated factor.
  • forward_periods: The forward periods horizon.
  • Params keys: All params keys present across the evaluation results, ordered by first appearance.
  • <metric_label>: The metric value, where metric_label is the key in EvaluationResult.metrics and the string passed in metrics=[...] (e.g. ic).
  • <metric_label>_p_value: The metric p-value if applicable (e.g. ic_p_value). With a custom evaluation label such as metrics={"ic_nw": ic(...)}, pass metrics=["ic_nw"] to compare() and the p-value column is ic_nw_p_value.
  • rank: Rank column, present only when sort_by is set (the column is absent otherwise).

Parameter details

Kwarg Default Meaning
metrics (required) list[str] of metric labels to include in the leaderboard.
sort_by None Any output column produced before ranking: factor, forward_periods, params keys, metric value columns such as ic, or p-value columns such as ic_p_value / <metric_label>_p_value. None keeps the original list order.
descending True Whether to sort in descending order (higher is better). Set to False for lower-is-better metrics.

rank is created after sorting, so it is not a valid sort_by key. To sort by significance, use the p-value column and set descending=False:

df = fx.compare(results, metrics=["ic"], sort_by="ic_p_value", descending=False)

For signed metrics such as predictive_beta, sorting by the raw value answers "largest positive effect", not "strongest evidence". A strongly negative but highly significant factor will rank low under sort_by="predictive_beta" with the default descending order. Use the p-value column for significance screens, or sort on abs(value) in caller code when magnitude regardless of sign is the intended ranking.