Skip to content

FBetaScore

yohou.metrics.classification.FBetaScore

Bases: BaseHardLabelScorer

F-beta score from class-probability forecasts.

Computes the weighted harmonic mean of precision and recall. With beta=1.0 this is the standard F1 score.

\[F_\beta = \frac{(1+\beta^2) \cdot TP}{(1+\beta^2) \cdot TP + \beta^2 \cdot FN + FP}\]

Parameters

Name Type Description Default
beta float

Weight of recall relative to precision. beta=1.0 gives equal weight (F1), beta=2.0 weights recall twice as much (F2).

1.0
average str

Class averaging strategy: "macro" (unweighted mean across classes), "micro" (aggregate counts first), or "weighted" (support-weighted mean).

"macro"
zero_division float

Value returned when the denominator is zero.

0.0
aggregation_method list of str or str

Dimensions to aggregate over.

"all"
groups list of str, dict of str to float, or None

Panel group filter or filter with weights.

None
components list of str, dict of str to float, or None

Component filter or filter with weights.

None

Attributes

Name Type Description
lower_is_better bool

Always False (higher F-beta is better).

Examples

>>> import polars as pl
>>> from datetime import datetime
>>> from yohou.metrics.classification import FBetaScore
>>> y_true = pl.DataFrame({
...     "time": [datetime(2020, 1, i) for i in range(1, 6)],
...     "weather": ["sunny", "rainy", "cloudy", "sunny", "rainy"],
... })
>>> y_pred = pl.DataFrame({
...     "vintage_time": [datetime(2019, 12, 31)] * 5,
...     "time": [datetime(2020, 1, i) for i in range(1, 6)],
...     "weather_proba_sunny": [0.7, 0.1, 0.2, 0.2, 0.1],
...     "weather_proba_rainy": [0.2, 0.8, 0.1, 0.1, 0.8],
...     "weather_proba_cloudy": [0.1, 0.1, 0.7, 0.7, 0.1],
... })
>>> scorer = FBetaScore(beta=1.0)
>>> _ = scorer.fit(y_true)
>>> scorer.score(y_true, y_pred)
0.777...

See Also

  • Precision : Precision (positive predictive value).
  • Recall : Recall (sensitivity).

Source Code

Show/Hide source
class FBetaScore(BaseHardLabelScorer):
    r"""F-beta score from class-probability forecasts.

    Computes the weighted harmonic mean of precision and recall. With
    ``beta=1.0`` this is the standard F1 score.

    $$F_\beta = \frac{(1+\beta^2) \cdot TP}{(1+\beta^2) \cdot TP + \beta^2 \cdot FN + FP}$$

    Parameters
    ----------
    beta : float, default=1.0
        Weight of recall relative to precision. ``beta=1.0`` gives
        equal weight (F1), ``beta=2.0`` weights recall twice as much (F2).
    average : str, default="macro"
        Class averaging strategy: ``"macro"`` (unweighted mean across
        classes), ``"micro"`` (aggregate counts first), or ``"weighted"``
        (support-weighted mean).
    zero_division : float, default=0.0
        Value returned when the denominator is zero.
    aggregation_method : list of str or str, default="all"
        Dimensions to aggregate over.
    groups : list of str, dict of str to float, or None, default=None
        Panel group filter or filter with weights.
    components : list of str, dict of str to float, or None, default=None
        Component filter or filter with weights.

    Attributes
    ----------
    lower_is_better : bool
        Always False (higher F-beta is better).

    Examples
    --------
    >>> import polars as pl
    >>> from datetime import datetime
    >>> from yohou.metrics.classification import FBetaScore
    >>> y_true = pl.DataFrame({
    ...     "time": [datetime(2020, 1, i) for i in range(1, 6)],
    ...     "weather": ["sunny", "rainy", "cloudy", "sunny", "rainy"],
    ... })
    >>> y_pred = pl.DataFrame({
    ...     "vintage_time": [datetime(2019, 12, 31)] * 5,
    ...     "time": [datetime(2020, 1, i) for i in range(1, 6)],
    ...     "weather_proba_sunny": [0.7, 0.1, 0.2, 0.2, 0.1],
    ...     "weather_proba_rainy": [0.2, 0.8, 0.1, 0.1, 0.8],
    ...     "weather_proba_cloudy": [0.1, 0.1, 0.7, 0.7, 0.1],
    ... })
    >>> scorer = FBetaScore(beta=1.0)
    >>> _ = scorer.fit(y_true)
    >>> scorer.score(y_true, y_pred)  # doctest: +ELLIPSIS
    0.777...

    See Also
    --------
    - [`Precision`][yohou.metrics.classification.Precision] : Precision (positive predictive value).
    - [`Recall`][yohou.metrics.classification.Recall] : Recall (sensitivity).

    """

    _parameter_constraints: dict = {
        **BaseHardLabelScorer._parameter_constraints,
        "beta": "no_validation",
    }

    _lower_is_better = False

    @property
    def _metric_name(self) -> str:
        """Return metric name based on beta value."""
        if self.beta == 1.0:
            return "f1"
        if self.beta == 2.0:
            return "f2"
        return "fbeta"

    def __init__(
        self,
        beta: float = 1.0,
        average: str = "macro",
        zero_division: float = 0.0,
        aggregation_method: list[str] | str = "all",
        groups: list[str] | dict[str, float] | None = None,
        components: list[str] | dict[str, float] | None = None,
    ):
        super().__init__(
            average=average,
            zero_division=zero_division,
            aggregation_method=aggregation_method,
            groups=groups,
            components=components,
        )
        self.beta = beta

    def _compute_metric_from_counts(self, counts: pl.DataFrame) -> pl.DataFrame:
        """Compute F-beta score from confusion counts."""
        b2 = self.beta**2
        return counts.select(
            pl
            .when((1 + b2) * pl.col("tp") + b2 * pl.col("fn") + pl.col("fp") == 0)
            .then(self.zero_division)
            .otherwise((1 + b2) * pl.col("tp") / ((1 + b2) * pl.col("tp") + b2 * pl.col("fn") + pl.col("fp")))
            .alias("value")
        )