plotters-statistical 0.1.0

Statistical chart primitives (box, violin, ROC, PR, regularization-path, residual) as native plotters series
Documentation
//! Empirical cumulative distribution function (ECDF) and its DKW confidence band.

use super::{sorted_finite, StatsError};

/// An ECDF as step data: for each distinct sorted value `x[i]`, `p[i]` is the
/// proportion of the sample less than or equal to it.
#[derive(Debug, Clone, PartialEq)]
pub struct Ecdf {
    /// Distinct sample values, ascending.
    pub x: Vec<f64>,
    /// Cumulative proportion (`<= x[i]`), in `(0, 1]`.
    pub p: Vec<f64>,
    /// Sample size (finite values only) — needed for the DKW band width.
    pub n: usize,
}

/// Compute the ECDF of `data`. Non-finite values are ignored. Returns
/// [`StatsError::EmptyInput`] if no finite values remain.
///
/// ```
/// # use plotters_statistical::stats::ecdf;
/// let e = ecdf(&[3.0, 1.0, 2.0, 2.0]).unwrap();
/// assert_eq!(e.x, vec![1.0, 2.0, 3.0]);
/// assert_eq!(e.p, vec![0.25, 0.75, 1.0]);
/// ```
pub fn ecdf(data: &[f64]) -> Result<Ecdf, StatsError> {
    let sorted = sorted_finite(data);
    if sorted.is_empty() {
        return Err(StatsError::EmptyInput);
    }
    let n = sorted.len();
    let mut x = Vec::new();
    let mut p = Vec::new();
    let mut i = 0;
    while i < n {
        let v = sorted[i];
        while i < n && sorted[i] == v {
            i += 1;
        }
        x.push(v);
        p.push(i as f64 / n as f64);
    }
    Ok(Ecdf { x, p, n })
}

/// Dvoretzky–Kiefer–Wolfowitz band half-width for a sample of size `n` at
/// confidence `1 - alpha`: `epsilon = sqrt(ln(2/alpha) / (2n))`. The true CDF
/// lies within `p ± epsilon` (clamped to `[0, 1]`) with probability `1 - alpha`.
pub fn dkw_epsilon(n: usize, alpha: f64) -> f64 {
    if n == 0 {
        return f64::INFINITY;
    }
    ((2.0 / alpha).ln() / (2.0 * n as f64)).sqrt()
}