Source code for cc_tk.relationship.significance.base

"""Evaluate the significance of the relationship between 2 variables.

Usually this consists in evaluating the relationship between a feature and
the target variable.
"""

from abc import ABC, abstractmethod
from enum import Enum, unique

import pandas as pd
from pydantic import BaseModel, ConfigDict, validate_call

from cc_tk.relationship.schema import (
    SeriesType,
    check_input_index,
    check_input_types,
)


[docs] class Constants: """Constants for the relationship functions.""" WEAK_THRESHOLD = 0.1 STRONG_THRESHOLD = 0.05
[docs] class VariableType(str, Enum): """Defines the type of a variable.""" NUMERIC = "numeric" CATEGORICAL = "categorical"
[docs] class SignificanceType(str, Enum): """Defines the type of significance to compute.""" STATISTICAL = "statistical" PREDICTIVENESS = "predictiveness"
[docs] @unique class SignificanceEnum(str, Enum): """Defines the significance levels.""" WEAK_VALUE = "weak" MEDIUM_VALUE = "medium" STRONG_VALUE = "strong"
[docs] class SignificanceOutput(BaseModel): """Output of the significance functions.""" pvalue: float influence: pd.Series statistic: float message: str = "" model_config = ConfigDict(arbitrary_types_allowed=True) @property def significance(self) -> SignificanceEnum: """Computing significativity based on pvalue.""" significance = SignificanceEnum.WEAK_VALUE if self.pvalue < Constants.WEAK_THRESHOLD: significance = SignificanceEnum.MEDIUM_VALUE if self.pvalue < Constants.STRONG_THRESHOLD: significance = SignificanceEnum.STRONG_VALUE return significance
[docs] def to_dataframe(self) -> pd.DataFrame: """Convert the output to a dataframe.""" return pd.DataFrame( { "influence": self.influence, "pvalue": self.pvalue, "statistic": self.statistic, "message": self.message, "significance": self.significance.value, } )
[docs] class SignificanceNumericNumeric(ABC): """Base class for the numeric/numeric significance.""" @check_input_types( ("numeric_values_1", SeriesType.NUMERIC), ("numeric_values_2", SeriesType.NUMERIC), ) @check_input_index("numeric_values_1", "numeric_values_2") @validate_call(config={"arbitrary_types_allowed": True}) def __call__( self, numeric_values_1: pd.Series, numeric_values_2: pd.Series ) -> SignificanceOutput: """Numeric/numeric significance with input checks. Parameters ---------- numeric_values_1 : pd.Series First numeric values numeric_values_2 : pd.Series Second numeric values Returns ------- SignificanceOutput Output of the significance function """ return self._evaluate(numeric_values_1, numeric_values_2) @abstractmethod def _evaluate( self, numeric_values_1: pd.Series, numeric_values_2: pd.Series ) -> SignificanceOutput: """Actual significance computation.""" pass
[docs] class SignificanceNumericCategorical(ABC): """Base class for the numeric/categorical significance.""" @check_input_types( ("numeric_values", SeriesType.NUMERIC), ("categorical_values", SeriesType.CATEGORICAL), ) @check_input_index("numeric_values", "categorical_values") @validate_call(config={"arbitrary_types_allowed": True}) def __call__( self, numeric_values: pd.Series, categorical_values: pd.Series ) -> SignificanceOutput: """Numeric/categorical significance with input checks. Parameters ---------- numeric_values : pd.Series Numeric values categorical_values : pd.Series Categorical values Returns ------- SignificanceOutput Output of the significance function """ return self._evaluate(numeric_values, categorical_values) @abstractmethod def _evaluate( self, numeric_values: pd.Series, categorical_values: pd.Series ) -> SignificanceOutput: """Actual significance computation.""" pass
[docs] class SignificanceCategoricalNumeric(ABC): """Base class for the categorical/numeric significance.""" @check_input_types( ("categorical_values", SeriesType.CATEGORICAL), ("numeric_values", SeriesType.NUMERIC), ) @check_input_index("categorical_values", "numeric_values") @validate_call(config={"arbitrary_types_allowed": True}) def __call__( self, categorical_values: pd.Series, numeric_values: pd.Series ) -> SignificanceOutput: """Categorical/numeric significance with input checks. Parameters ---------- categorical_values : pd.Series Categorical values numeric_values : pd.Series Numeric values Returns ------- SignificanceOutput Output of the significance function """ return self._evaluate(categorical_values, numeric_values) @abstractmethod def _evaluate( self, categorical_values: pd.Series, numeric_values: pd.Series ) -> SignificanceOutput: """Actual significance computation.""" pass
[docs] class SignificanceCategoricalCategorical(ABC): """Base class for the categorical/categorical significance.""" @check_input_types( ("categorical_values_1", SeriesType.CATEGORICAL), ("categorical_values_2", SeriesType.CATEGORICAL), ) @check_input_index("categorical_values_1", "categorical_values_2") @validate_call(config={"arbitrary_types_allowed": True}) def __call__( self, categorical_values_1: pd.Series, categorical_values_2: pd.Series ) -> SignificanceOutput: """Categorical/categorical significance with input checks. Parameters ---------- categorical_values_1 : pd.Series First categorical values categorical_values_2 : pd.Series Second categorical values Returns ------- SignificanceOutput Output of the significance function """ return self._evaluate(categorical_values_1, categorical_values_2) @abstractmethod def _evaluate( self, categorical_values_1: pd.Series, categorical_values_2: pd.Series ) -> SignificanceOutput: """Actual significance computation.""" pass