use super::{quartiles, sorted_finite, StatsError};
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum BinRule {
Count(usize),
Width(f64),
Sturges,
FreedmanDiaconis,
Scott,
}
#[derive(Debug, Clone, PartialEq)]
pub struct Histogram {
pub edges: Vec<f64>,
pub counts: Vec<usize>,
pub density: Vec<f64>,
}
impl Histogram {
pub fn centers(&self) -> Vec<f64> {
self.edges.windows(2).map(|w| (w[0] + w[1]) / 2.0).collect()
}
pub fn bin_width(&self) -> f64 {
if self.edges.len() < 2 {
0.0
} else {
self.edges[1] - self.edges[0]
}
}
}
pub fn histogram(data: &[f64], rule: BinRule) -> Result<Histogram, StatsError> {
let sorted = sorted_finite(data);
if sorted.is_empty() {
return Err(StatsError::EmptyInput);
}
let n = sorted.len();
let min = sorted[0];
let max = sorted[n - 1];
if max <= min {
return Err(StatsError::ZeroVariance);
}
let range = max - min;
let nf = n as f64;
let bins = match rule {
BinRule::Count(c) => {
if c == 0 {
return Err(StatsError::InvalidBandwidth);
}
c
}
BinRule::Width(w) => {
if !(w.is_finite() && w > 0.0) {
return Err(StatsError::InvalidBandwidth);
}
(range / w).ceil().max(1.0) as usize
}
BinRule::Sturges => (nf.log2().ceil() as usize) + 1,
BinRule::FreedmanDiaconis => {
let q = quartiles(&sorted)?;
let w = 2.0 * q.iqr * nf.powf(-1.0 / 3.0);
if w > 0.0 {
(range / w).ceil().max(1.0) as usize
} else {
(nf.log2().ceil() as usize) + 1 }
}
BinRule::Scott => {
let mean = sorted.iter().sum::<f64>() / nf;
let std = (sorted.iter().map(|x| (x - mean).powi(2)).sum::<f64>()
/ (nf - 1.0).max(1.0))
.sqrt();
let w = 3.49 * std * nf.powf(-1.0 / 3.0);
if w > 0.0 {
(range / w).ceil().max(1.0) as usize
} else {
(nf.log2().ceil() as usize) + 1
}
}
}
.max(1);
let width = range / bins as f64;
let edges: Vec<f64> = (0..=bins).map(|i| min + width * i as f64).collect();
let mut counts = vec![0usize; bins];
for &v in &sorted {
let mut idx = ((v - min) / width).floor() as usize;
if idx >= bins {
idx = bins - 1; }
counts[idx] += 1;
}
let density = counts.iter().map(|&c| c as f64 / (nf * width)).collect();
Ok(Histogram {
edges,
counts,
density,
})
}