Source code for sorbetto.tile.value_tile

import math

import numpy as np

from sorbetto.flavor.value_flavor import ValueFlavor
from sorbetto.geometry.line import Line
from sorbetto.geometry.pencil_of_lines import PencilOfLines
from sorbetto.parameterization.abstract_parameterization import AbstractParameterization
from sorbetto.parameterization.parameterization_default import ParameterizationDefault
from sorbetto.performance.two_class_classification_performance import (
    TwoClassClassificationPerformance,
)
from sorbetto.tile.numeric_tile import NumericTile


def __vut_default_param(ptn, pfp, pfn, ptp):
    def x_log_x(x):
        if x == 0:
            return 0.0
        else:
            return x * np.log(x)

    if ptn + pfp == 0.0:
        return math.nan  # The code below does not compute the right value, perhaps should we use the limit ?
    if pfn + ptp == 0.0:
        return math.nan  # The code below does not compute the right value, perhaps should we use the limit ?
    if ptn + pfn == 0.0:
        return math.nan  # The code below does not compute the right value, perhaps should we use the limit ?
    if ptp + pfp == 0.0:
        return math.nan  # The code below does not compute the right value, perhaps should we use the limit ?

    same_TN_TP = math.isclose(ptn, ptp)
    same_FN_FP = math.isclose(pfn, pfp)
    if same_TN_TP and same_FN_FP:
        return ptn + ptp
    elif same_TN_TP:
        return ptn / (pfn - pfp) * (np.log(ptn + pfn) - np.log(ptn + pfp))
    elif same_FN_FP:
        return 1.0 - pfn / (ptp - ptn) * (np.log(ptp + pfn) - np.log(ptn + pfn))
    else:
        # The analytical solution implemented here is due to Anthony Cioppa; many thanks to him.
        num = (
            (pfn - ptp) * x_log_x(pfn + ptp)
            + (ptn - pfn) * x_log_x(ptn + pfn)
            + (ptp - pfp) * x_log_x(pfp + ptp)
            + (pfp - ptn) * x_log_x(ptn + pfp)
        )
        den = (ptp - pfn) * (pfn - pfp)
        return 0.5 - 0.5 * num / den


[docs] class ValueTile(NumericTile): def __init__( self, parameterization: AbstractParameterization, flavor: ValueFlavor, name: str = "Value Tile", resolution: int = 1001, disable_colorbar: bool = False, ): super().__init__( parameterization=parameterization, flavor=flavor, name=name, resolution=resolution, disable_colorbar=disable_colorbar, ) self._performance = self.flavor.performance @property def flavor(self) -> ValueFlavor: return super().flavor # type: ignore @property def performance(self) -> TwoClassClassificationPerformance: return self._performance @performance.setter def performance(self, value: TwoClassClassificationPerformance): self._performance = value
[docs] def getVUT(self) -> float: """ Computes the volume See :cite:t:`Pierard2024TheTile-arxiv`, Section 3.1. (with default parameterization) """ if isinstance(self.parameterization, ParameterizationDefault): return __vut_default_param( self._performance.ptn, self._performance.pfp, self._performance.pfn, self._performance.ptp, ) else: raise NotImplementedError( "VUT is not implemented for other parameterization for now." )
[docs] def getLineForValue(self, value) -> Line: if not isinstance(self.parameterization, ParameterizationDefault): raise NotImplementedError( "VUT is not implemented for other parameterization for now." ) ptn = self._performance.ptn pfp = self._performance.pfp pfn = self._performance.pfn ptp = self._performance.ptp # The value taken by the ranking scores is v if and only if # ( itn ptn + itp ptp ) / ( itn ptn + ifp pfp + ifn pfn + itp ptp ) = v # <=> ( itn ptn + itp ptp ) - v ( itn ptn + ifp pfp + ifn pfn + itp ptp ) = 0 # <=> itn ptn(1-v) + ifp pfp(0-v) + ifn pfn(0-v) + itp ptp(1-v) = 0 # <=> (1-a) ptn(1-v) + (1-b) pfp(0-v) + b pfn(0-v) + a ptp(1-v) = 0 # <=> a [ (ptp-ptn) (1-v) ] + b [ (pfp-pfn) v ] + [ ptn(1-v) + pfp(0-v) ] = 0 Ka = (ptp - ptn) * (1.0 - value) Kb = (pfp - pfn) * value K = ptn * (1.0 - value) + pfp * (0.0 - value) name = "Line where the rankings scores take the value {:g} for the performance {}".format( value, self._performance ) return Line(Ka, Kb, K, name)
[docs] def getPencil(self) -> PencilOfLines: if not isinstance(self.parameterization, ParameterizationDefault): raise NotImplementedError( "VUT is not implemented for other parameterization for now." ) line_1 = self.getLineForValue(0.0) line_2 = self.getLineForValue(1.0) pencil = PencilOfLines( line_1, line_2, "pencil for performance {}".format(self._performance) ) return pencil
[docs] def getExplanation(self) -> str: return "Explanation for this tile is not implemented yet"