Source code for compml.frequentist.tests.non_parametrics.quade

"""Quade test."""

from typing import Any

import numpy as np
from scipy.stats import f, rankdata

from compml.evaluation_scores import EvaluationScores
from compml.frequentist.tests.base import BaseFrequentistTest


[docs] class QuadeTest(BaseFrequentistTest[EvaluationScores]): """Quade test for repeated measurements. The null hypothesis is that the mean scores are equal across all algorithms. """ def __init__(self, alpha: float = 0.05, minimize: bool = False): """Initialize the Quade test. Parameters ---------- alpha : float Significance level. minimize : bool Whether to minimize the metric (lower scores are better). """ super().__init__(alpha=alpha) self.__validate_arguments(minimize) self.minimize = minimize
[docs] def compute_statistic(self, scores: EvaluationScores) -> float: """Compute the Quade test statistic. Parameters ---------- scores : EvaluationScores Evaluation scores container. Returns ------- float Quade test statistic. """ x = scores.compute_means() n, k = x.shape ranges = x.max(axis=1) - x.min(axis=1) rank_q = rankdata(ranges, method="average") if self.minimize: ranks = np.apply_along_axis(rankdata, 1, x, method="average") else: ranks = np.apply_along_axis(rankdata, 1, x, method="average") s_mat = rank_q[:, np.newaxis] * (ranks - (k + 1) / 2.0) a_val = float(np.sum(s_mat**2)) col_sums = s_mat.sum(axis=0) b_val = float(np.sum(col_sums**2) / n) if abs(a_val - b_val) < 1e-15: return 0.0 return (n - 1) * b_val / (a_val - b_val)
[docs] def compute_p_value(self, scores: EvaluationScores, statistic: float) -> float: """Compute p-value from the Quade test statistic. Parameters ---------- scores : EvaluationScores Evaluation scores container. statistic : float Quade test statistic. Returns ------- float p-value computed from the Quade test statistic. """ df1, df2 = self.__degrees_of_freedom(scores) return float(f.sf(statistic, df1, df2))
[docs] def compute_c_value(self, scores: EvaluationScores) -> float: """Compute critical value for the Quade test statistic. Parameters ---------- scores : EvaluationScores Evaluation scores container. Returns ------- float Critical value at ``1 - alpha`` (upper-tail). """ df1, df2 = self.__degrees_of_freedom(scores) return float(f.ppf(1.0 - self.alpha, df1, df2))
def _validate_scores(self, scores: EvaluationScores): """Validate that the input scores meet the test assumptions. Parameters ---------- scores : EvaluationScores Scores to validate. Raises ------ ValueError If score structure is not compatible with this test. """ if scores.n_algorithms < 2: raise ValueError("Quade test requires at least two algorithms") if scores.n_datasets < 2: raise ValueError("Quade test requires at least two datasets") def _get_metadata(self) -> dict[str, Any]: """Return procedure metadata. Returns ------- dict Metadata including ranking convention. """ return {"minimize": self.minimize} def __degrees_of_freedom(self, scores: EvaluationScores) -> tuple[int, int]: """Degrees of freedom for the Quade test. Parameters ---------- scores : EvaluationScores Evaluation scores container. Returns ------- tuple[int, int] Degrees of freedom for the Quade test. """ k = scores.n_algorithms n = scores.n_datasets return k - 1, (k - 1) * (n - 1) def __validate_arguments(self, minimize: bool): """Validate initialization arguments. Parameters ---------- minimize : bool Whether to minimize the metric (lower scores are better). """ if not isinstance(minimize, bool): raise ValueError("minimize must be a boolean")