plotters_statistical/stats/ecdf.rs
1//! Empirical cumulative distribution function (ECDF) and its DKW confidence band.
2
3use super::{sorted_finite, StatsError};
4
5/// An ECDF as step data: for each distinct sorted value `x[i]`, `p[i]` is the
6/// proportion of the sample less than or equal to it.
7#[derive(Debug, Clone, PartialEq)]
8pub struct Ecdf {
9 /// Distinct sample values, ascending.
10 pub x: Vec<f64>,
11 /// Cumulative proportion (`<= x[i]`), in `(0, 1]`.
12 pub p: Vec<f64>,
13 /// Sample size (finite values only) — needed for the DKW band width.
14 pub n: usize,
15}
16
17/// Compute the ECDF of `data`. Non-finite values are ignored. Returns
18/// [`StatsError::EmptyInput`] if no finite values remain.
19///
20/// ```
21/// # use plotters_statistical::stats::ecdf;
22/// let e = ecdf(&[3.0, 1.0, 2.0, 2.0]).unwrap();
23/// assert_eq!(e.x, vec![1.0, 2.0, 3.0]);
24/// assert_eq!(e.p, vec![0.25, 0.75, 1.0]);
25/// ```
26pub fn ecdf(data: &[f64]) -> Result<Ecdf, StatsError> {
27 let sorted = sorted_finite(data);
28 if sorted.is_empty() {
29 return Err(StatsError::EmptyInput);
30 }
31 let n = sorted.len();
32 let mut x = Vec::new();
33 let mut p = Vec::new();
34 let mut i = 0;
35 while i < n {
36 let v = sorted[i];
37 while i < n && sorted[i] == v {
38 i += 1;
39 }
40 x.push(v);
41 p.push(i as f64 / n as f64);
42 }
43 Ok(Ecdf { x, p, n })
44}
45
46/// Dvoretzky–Kiefer–Wolfowitz band half-width for a sample of size `n` at
47/// confidence `1 - alpha`: `epsilon = sqrt(ln(2/alpha) / (2n))`. The true CDF
48/// lies within `p ± epsilon` (clamped to `[0, 1]`) with probability `1 - alpha`.
49pub fn dkw_epsilon(n: usize, alpha: f64) -> f64 {
50 if n == 0 {
51 return f64::INFINITY;
52 }
53 ((2.0 / alpha).ln() / (2.0 * n as f64)).sqrt()
54}