import math from typing import Sequence from .math_round_float import round_float from .stats_percentile_types import PercentileMethod def _finite(value: object) -> bool: return isinstance(value, (int, float)) and not isinstance(value, bool) and math.isfinite(value) def percentile(values: Sequence[float], p: float, method: PercentileMethod, decimals: int) -> float: """The p-th percentile of ``values`` by a named method. There is no single "95th percentile": the caller names the definition, so a dashboard and a billing job cannot quietly disagree. """ if isinstance(values, (str, bytes)) or not isinstance(values, (list, tuple)): raise TypeError("values must be a list of numbers") if len(values) == 0: raise ValueError("values must not be empty") for v in values: if not _finite(v): raise TypeError("values must be finite numbers, received %r" % (v,)) if not _finite(p) or p < 0 or p > 100: raise ValueError("p must be between 0 and 100, received %r" % (p,)) # Checked up front as well as in round_float (same wording), so a bad # decimals is reported before the method is looked at, as in 1.x. if isinstance(decimals, bool) or not isinstance(decimals, int) or decimals < 0 or decimals > 12: raise ValueError("decimals must be a whole number from 0 to 12, received %r" % (decimals,)) # float() first so integer inputs follow exactly the binary64 path the # other languages take. ordered = sorted(float(v) for v in values) n = len(ordered) p = float(p) if method == "nearest-rank": # The smallest value with at least p% of the sample at or below it. rank = math.ceil((p * n) / 100.0) if rank < 1: rank = 1 result = ordered[rank - 1] elif method == "linear": # Hyndman & Fan type 7: zero-based position (n - 1) * p / 100. h = ((n - 1) * p) / 100.0 lo = math.floor(h) if lo >= n - 1: result = ordered[n - 1] else: result = ordered[lo] + (h - lo) * (ordered[lo + 1] - ordered[lo]) else: raise ValueError('unknown percentile method "%s"' % (method,)) # math.round-float rounds on the exact value of the double: 2.675 gives 2.67. return round_float(result, decimals)