Skip to main content

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}