Source code for hypotestx.core.result

"""
HypoResult class for standardized test results
"""

from typing import Any, Dict, Optional, Union

from ..utils.formatting import format_p_value


[docs] class HypoResult: """Standardized result object for hypothesis tests""" def __init__( self, test_name: str, statistic: float, p_value: float, effect_size: Optional[float] = None, effect_size_name: Optional[str] = None, confidence_interval: Optional[tuple] = None, degrees_of_freedom: Optional[Union[int, tuple]] = None, sample_sizes: Optional[Union[int, tuple]] = None, assumptions_met: Optional[Dict[str, bool]] = None, interpretation: Optional[str] = None, data_summary: Optional[Dict[str, Any]] = None, alpha: float = 0.05, alternative: str = "two-sided", **kwargs, ): self.test_name = test_name self.statistic = statistic self.p_value = p_value self.effect_size = effect_size self.effect_size_name = effect_size_name self.confidence_interval = confidence_interval self.degrees_of_freedom = degrees_of_freedom self.sample_sizes = sample_sizes self.assumptions_met = assumptions_met or {} self.interpretation = interpretation self.data_summary = data_summary or {} self.alpha = alpha self.alternative = alternative self.extra_info = kwargs # Routing metadata (populated by analyze()) self.routing_confidence: float = 1.0 self.routing_source: str = "llm" @property def is_significant(self) -> bool: """Check if result is statistically significant""" return self.p_value < self.alpha @property def effect_magnitude(self) -> str: """Interpret effect size magnitude using Cohen's conventions for the appropriate measure.""" if self.effect_size is None: return "not calculated" es = abs(self.effect_size) name = (self.effect_size_name or "").lower() # Cohen's d (t-tests) if "cohen" in name or name == "d": if es < 0.2: return "negligible" if es < 0.5: return "small" if es < 0.8: return "medium" return "large" # Pearson / Spearman r, rank-biserial r, point-biserial r if name in ("r", "rank-biserial r", "point-biserial r", "spearman rho"): if es < 0.10: return "negligible" if es < 0.30: return "small" if es < 0.50: return "medium" return "large" # Cramer's V / phi (chi-square family) if name in ("cramer's v", "phi", "cohens w", "cohen's w", "w"): if es < 0.10: return "negligible" if es < 0.30: return "small" if es < 0.50: return "medium" return "large" # Eta-squared / partial eta-squared (ANOVA / Kruskal-Wallis) if "eta" in name: if es < 0.01: return "negligible" if es < 0.06: return "small" if es < 0.14: return "medium" return "large" # Omega-squared if "omega" in name: if es < 0.01: return "negligible" if es < 0.06: return "small" if es < 0.14: return "medium" return "large" # Odds ratio — not on a linear scale; give qualitative label if "odds" in name: if es < 1.5: return "negligible" if es < 2.5: return "small" if es < 4.0: return "medium" return "large" return "unknown scale"
[docs] def summary(self, verbose: bool = False) -> str: """Human-readable summary of results""" lines = [] # Header header = f"[ {self.test_name} ]" lines.append(header) lines.append("=" * len(header)) # Main results significance = "SIGNIFICANT" if self.is_significant else "Not significant" lines.append(f"Result: {significance} (alpha = {self.alpha})") lines.append(f"Test statistic: {self.statistic:.4f}") lines.append(f"p-value: {format_p_value(self.p_value)}") if self.degrees_of_freedom is not None: if isinstance(self.degrees_of_freedom, tuple): df_str = f"({', '.join(map(str, self.degrees_of_freedom))})" else: df_str = str(self.degrees_of_freedom) lines.append(f"Degrees of freedom: {df_str}") # Effect size if self.effect_size is not None: lines.append( f"{self.effect_size_name}: {self.effect_size:.4f} ({self.effect_magnitude})" ) # Confidence interval if self.confidence_interval is not None: ci_level = int((1 - self.alpha) * 100) ci_str = f"[{self.confidence_interval[0]:.4f}, {self.confidence_interval[1]:.4f}]" lines.append(f"{ci_level}% Confidence Interval: {ci_str}") if verbose: # Sample sizes if self.sample_sizes is not None: if isinstance(self.sample_sizes, tuple): lines.append(f"Sample sizes: {self.sample_sizes}") else: lines.append(f"Sample size: {self.sample_sizes}") # Assumptions if self.assumptions_met: lines.append("\nAssumption Checks:") for assumption, met in self.assumptions_met.items(): status = "Met" if met else "Violated" lines.append(f" {assumption}: {status}") # Data summary if self.data_summary: lines.append("\nData Summary:") for key, value in self.data_summary.items(): if isinstance(value, float): lines.append(f" {key}: {value:.4f}") else: lines.append(f" {key}: {value}") # Interpretation if self.interpretation: lines.append(f"\nInterpretation:\n{self.interpretation}") # Routing fallback warning if getattr(self, "routing_source", "llm") == "fallback": lines.append( "\n⚠ Routed via regex fallback (confidence=60%). " "Verify the correct test was selected." ) return "\n".join(lines)
[docs] def to_dict(self) -> Dict[str, Any]: """Convert result to dictionary""" result_dict = { "test_name": self.test_name, "statistic": self.statistic, "p_value": self.p_value, "is_significant": self.is_significant, "alpha": self.alpha, "alternative": self.alternative, } if self.effect_size is not None: result_dict["effect_size"] = self.effect_size result_dict["effect_size_name"] = self.effect_size_name result_dict["effect_magnitude"] = self.effect_magnitude if self.confidence_interval is not None: result_dict["confidence_interval"] = self.confidence_interval if self.degrees_of_freedom is not None: result_dict["degrees_of_freedom"] = self.degrees_of_freedom if self.sample_sizes is not None: result_dict["sample_sizes"] = self.sample_sizes result_dict["assumptions_met"] = self.assumptions_met result_dict["data_summary"] = self.data_summary result_dict.update(self.extra_info) return result_dict
def __str__(self) -> str: """String representation""" return self.summary() def __repr__(self) -> str: """Detailed representation""" return f"HypoResult(test='{self.test_name}', statistic={self.statistic:.4f}, p_value={self.p_value:.6f})" # noqa: E501
[docs] def plot(self, kind: str = "auto"): """ Visualise this result. Delegates to :func:`hypotestx.explore.visualize.plot_result`. Parameters ---------- kind : str ``"auto"`` (default), ``"p_value"``, ``"bar"``, ``"box"``. Returns ------- matplotlib.figure.Figure Raises ------ ImportError If matplotlib is not installed. Install with ``pip install matplotlib`` or ``pip install hypotestx[visualization]``. """ from ..explore.visualize import plot_result return plot_result(self, kind=kind)