Source code for sorbetto.core.importance

import math

import numpy as np


[docs] class Importance: """This class encodes some application-specific preferences. Currently, it is a random variable, called importance, that gives a positive value to each element of the sample space: tn (for true negative), fp (for false positive), fn (for false negative), and tp (for true positive). See :cite:t:`Pierard2025Foundations` for more information on this topic. """ tol = 1e-10 def __init__( self, itn: float | int, ifp: float | int, ifn: float | int, itp: float | int, name: str = "I", ): assert isinstance(itn, (float, int)) assert isinstance(ifp, (float, int)) assert isinstance(ifn, (float, int)) assert isinstance(itp, (float, int)) if itn < 0.0 or ifp < 0.0 or ifn < 0.0 or itp < 0.0: raise ValueError( f"Importance values must be non-negative. Received [TN:{itn}, FP:{ifp}, FN:{ifn}, TP:{itp}]" ) if math.isclose(itn + ifp + ifn + itp, 0.0, abs_tol=self.tol): raise ValueError( f"At least one importance value must be positive. Received [TN:{itn}, FP:{ifp}, FN:{ifn}, TP:{itp}]" ) self._itn = float(itn) self._ifp = float(ifp) self._ifn = float(ifn) self._itp = float(itp) self._name = name @property def itn(self) -> float: return self._itn @property def ifp(self) -> float: return self._ifp @property def ifn(self) -> float: return self._ifn @property def itp(self) -> float: return self._itp @property def name(self) -> str: return self._name def __eq__(self, other): if not isinstance(other, Importance): return False return ( # TODO: would math.fabs be better than abs? abs(self.itn - other.itn) <= self.tol and abs(self.ifp - other.ifp) <= self.tol and abs(self.ifn - other.ifn) <= self.tol and abs(self.itp - other.itp) <= self.tol ) def __str__(self): txt = f"[Importance] containing [TN:{self.itn}, FP:{self.ifp}, FN:{self.ifn}, TP:{self.itp}]" return txt
# TODO get even better typing there (output as tuple of single type, based on inputs) def _parse_importance( importance: Importance | list[Importance] | np.ndarray | None = None, itn: float | int | np.ndarray | None = None, ifp: float | int | np.ndarray | None = None, ifn: float | int | np.ndarray | None = None, itp: float | int | np.ndarray | None = None, ) -> tuple[ float | int | np.ndarray, float | int | np.ndarray, float | int | np.ndarray, float | int | np.ndarray, ]: if isinstance(importance, Importance): itn_ = importance.itn ifp_ = importance.ifp ifn_ = importance.ifn itp_ = importance.itp elif isinstance(importance, np.ndarray): assert importance.shape[-1] == 4 itn_ = importance[..., 0] ifp_ = importance[..., 1] ifn_ = importance[..., 2] itp_ = importance[..., 3] elif isinstance(importance, list): itn_ = np.array([imp.itn for imp in importance]) ifp_ = np.array([imp.ifp for imp in importance]) ifn_ = np.array([imp.ifn for imp in importance]) itp_ = np.array([imp.itp for imp in importance]) else: if (itn is None) or (ifp is None) or (ifn is None) or (itp is None): raise ValueError( "Either importance or all itn, ifp, ifn, itp must be provided." ) itn_, ifp_, ifn_, itp_ = itn, ifp, ifn, itp return itn_, ifp_, ifn_, itp_