malevich 0.7.0

Terminal plotting: a small grammar of marks, honest axes, millions of points
Documentation
//! Rolling windows: trailing reduces with the shared reducer vocabulary.

/// A trailing window of `size` values, reduced at every position.
///
/// The first positions reduce partial windows (no warm-up gap in the chart), gaps
/// (`NaN`) are excluded from each window's reduction, and a window with nothing
/// finite reduces to a gap. Reducer names match [`super::Agg`].
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Window {
    size: usize,
}

impl Window {
    /// A window of `size` trailing values.
    ///
    /// # Panics
    ///
    /// Panics if `size` is zero.
    pub fn new(size: usize) -> Window {
        assert!(size > 0, "Window::new requires a non-zero size");
        Window { size }
    }

    /// The rolling mean.
    pub fn mean(&self, values: &[f64]) -> Vec<f64> {
        self.reduce(values, |window| {
            let finite: Vec<f64> = window.iter().copied().filter(|v| v.is_finite()).collect();
            if finite.is_empty() {
                f64::NAN
            } else {
                finite.iter().sum::<f64>() / finite.len() as f64
            }
        })
    }

    /// The rolling sum (0 when nothing is finite).
    pub fn sum(&self, values: &[f64]) -> Vec<f64> {
        self.reduce(values, |window| {
            window.iter().copied().filter(|v| v.is_finite()).sum()
        })
    }

    /// The rolling minimum.
    pub fn min(&self, values: &[f64]) -> Vec<f64> {
        self.reduce(values, |window| {
            let min = window
                .iter()
                .copied()
                .filter(|v| v.is_finite())
                .fold(f64::INFINITY, f64::min);
            if min.is_finite() { min } else { f64::NAN }
        })
    }

    /// The rolling maximum.
    pub fn max(&self, values: &[f64]) -> Vec<f64> {
        self.reduce(values, |window| {
            let max = window
                .iter()
                .copied()
                .filter(|v| v.is_finite())
                .fold(f64::NEG_INFINITY, f64::max);
            if max.is_finite() { max } else { f64::NAN }
        })
    }

    fn reduce(&self, values: &[f64], reducer: impl Fn(&[f64]) -> f64) -> Vec<f64> {
        (0..values.len())
            .map(|end| {
                let start = (end + 1).saturating_sub(self.size);
                reducer(&values[start..=end])
            })
            .collect()
    }
}

#[cfg(test)]
#[path = "tests/window_tests.rs"]
mod tests;