from dataclasses import dataclass
from io import StringIO
from typing import List
from typing import Tuple
from typing import Union
from rich import box
from rich.console import Console
from rich.table import Table
from rich.text import Text
__all__ = ["TestResult", "make_testvars_table", "smartround"]
[docs]@dataclass
class TestResult:
"""Base container for test result data and subsequent representation methods
Attributes
----------
statistic : `int` or `float`
Statistic of the test
p : float
p-value of the test
"""
statistic: Union[int, float]
p: float
def _results_text(self, stat_varname="statistic") -> Text:
"""Returns Rich `Text` of the statistic and p-value
Parameters
----------
stat_varname : `str`, default `"statistic"`
Name describing the statistic
Returns
-------
`Text`
Multi-line Rich `Text` variable list
"""
return vars_list((stat_varname, self.statistic), ("p-value", self.p))
def __rich_console__(self, console, options):
raise NotImplementedError(
f"No Rich representation provided for {self.__class__.__name__}"
)
def __str__(self):
"""Mocks stdout file as the stdout of a Rich `Console` to get `str` equivalent"""
buf = StringIO()
console = Console(file=buf, force_jupyter=False)
console.print(self)
return buf.getvalue()
def make_testvars_table(*columns) -> Table:
table = Table(box=box.SQUARE)
table.add_column(columns[0], justify="left")
for col in columns[1:]:
table.add_column(col, justify="right")
return table
[docs]def smartround(num: Union[int, float], ndigits=1) -> Union[int, float]:
"""Round number only if it's a float"""
if isinstance(num, int):
return int(num)
else:
return round(num, ndigits)
# ------------------------------------------------------------------------------
# Helpers
def vars_list(*varname_value_pairs: List[Tuple[str, Union[int, float]]]) -> Text:
varnames, values = zip(*varname_value_pairs)
varname_maxlen = max(len(varname) for varname in varnames)
f_varnames = [pad_right(varname, varname_maxlen) for varname in varnames]
f_values = align_nums(values)
f_varname_value_pairs = [
f_varname + " " + f_value for f_varname, f_value in zip(f_varnames, f_values)
]
return Text("\n".join(f_varname_value_pairs))
def align_nums(nums):
f_nums = [str(smartround(num, 3)) for num in nums]
dot_positions = []
for f_num in f_nums:
pos = f_num.find(".")
if pos == -1:
pos = len(f_num)
dot_positions.append(pos)
maxpos = max(dot_positions)
f_aligned_nums = [
" " * (maxpos - pos) + f_num for f_num, pos in zip(f_nums, dot_positions)
]
return f_aligned_nums
def pad_right(string, maxlen):
nspaces = maxlen - len(string)
f_string = string + " " * nspaces
return f_string