percentile function

double? percentile(
  1. Iterable<num> values,
  2. double p
)

Returns the pth percentile of values using linear interpolation.

p is a fraction in 0.01.0 (e.g. 0.5 for the median). Returns null if p is out of range or values is empty. Does not mutate the caller's input.

Example:

percentile([1, 2, 3, 4], 0.5); // 2.5

Implementation

double? percentile(Iterable<num> values, double p) {
  if (p < 0 || p > 1) return null;
  // ignore: saropa_lints/avoid_large_list_copy -- needs an independent copy to sort without mutating the caller's input
  final List<double> list = values.map((num n) => n.toDouble()).toList()..sort();
  if (list.isEmpty) return null;
  if (list.length == 1) return list[0];
  // Linear-interpolation method (R's "type 7"): the percentile maps to a
  // fractional rank p*(n-1), so p=0 hits the first element and p=1 the last.
  final double index = p * (list.length - 1);
  // Bracket that fractional rank with its floor (i) and next (j) integer ranks;
  // both are clamped so the top of the range (i == n-1) does not read past end.
  final int i = index.floor().clamp(0, list.length - 1);
  final int j = (i + 1).clamp(0, list.length - 1);
  // t is the fractional offset between ranks i and j; blend them proportionally.
  final double t = index - i;
  return list[i] + t * (list[j] - list[i]);
}