Source code for anomsmith.primitives.thresholding

"""Thresholding primitives for converting scores to labels."""

from dataclasses import dataclass
from typing import Literal

import numpy as np

from anomsmith.objects.views import LabelView, ScoreView


[docs] @dataclass(frozen=True) class ThresholdRule: """Rule for thresholding anomaly scores. Attributes: method: 'absolute' (use value directly) or 'quantile' (use quantile) value: Threshold value (absolute) or quantile (0-1) quantile: If method is 'quantile', this is the quantile to use """ method: Literal["absolute", "quantile"] value: float quantile: float | None = None def __post_init__(self) -> None: """Validate inputs after initialization.""" if self.method == "quantile": if self.quantile is None: raise ValueError("quantile must be provided when method is 'quantile'") if not 0 <= self.quantile <= 1: raise ValueError(f"quantile must be in [0, 1], got {self.quantile}") elif self.method == "absolute": if self.quantile is not None: raise ValueError( "quantile should not be provided when method is 'absolute'" ) else: raise ValueError( f"method must be 'absolute' or 'quantile', got {self.method}" )
[docs] def apply_threshold(score_view: ScoreView, rule: ThresholdRule) -> LabelView: """Apply threshold rule to scores to produce binary labels. Args: score_view: ScoreView with anomaly scores rule: ThresholdRule to apply Returns: LabelView with binary labels (1 = anomaly, 0 = normal) """ scores = score_view.scores if rule.method == "absolute": threshold = rule.value elif rule.method == "quantile": if rule.quantile is None: raise ValueError("quantile must be provided when method is 'quantile'") threshold = np.quantile(scores, rule.quantile) else: raise ValueError(f"Unknown method: {rule.method}") labels = (scores >= threshold).astype(int) return LabelView(index=score_view.index, labels=labels)