mod equal_frequency;
mod equal_width;
pub use equal_frequency::EqualFrequencyBinning;
pub use equal_width::EqualWidthBinning;
use crate::error::{DriftError, Result};
pub const DEFAULT_BIN_COUNT: usize = 10;
#[derive(Clone, Debug, PartialEq)]
pub enum BinDefinition {
Continuous {
edges: Vec<f64>,
},
Categorical {
categories: Vec<String>,
},
}
impl BinDefinition {
pub fn len(&self) -> usize {
match self {
BinDefinition::Continuous { edges } => edges.len().saturating_sub(1),
BinDefinition::Categorical { categories } => categories.len(),
}
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
}
#[derive(Clone, Debug)]
pub struct Histogram {
bins: BinDefinition,
counts: Vec<f64>,
total: f64,
}
impl Histogram {
pub fn new(bins: BinDefinition, counts: Vec<f64>) -> Result<Self> {
if counts.len() != bins.len() {
return Err(DriftError::BinCountMismatch {
reference: bins.len(),
live: counts.len(),
});
}
let total = counts.iter().sum();
Ok(Self {
bins,
counts,
total,
})
}
pub fn bins(&self) -> &BinDefinition {
&self.bins
}
pub fn counts(&self) -> &[f64] {
&self.counts
}
pub fn total(&self) -> f64 {
self.total
}
pub fn len(&self) -> usize {
self.counts.len()
}
pub fn is_empty(&self) -> bool {
self.counts.is_empty()
}
pub fn frequencies(&self) -> Vec<f64> {
if self.total <= 0.0 {
return vec![0.0; self.counts.len()];
}
self.counts.iter().map(|&c| c / self.total).collect()
}
}
pub(crate) fn ensure_finite(data: &[f64]) -> Result<()> {
for (index, &v) in data.iter().enumerate() {
if !v.is_finite() {
return Err(DriftError::NonFinite { index });
}
}
Ok(())
}
pub(crate) fn histogram_from_edges(edges: &[f64], data: &[f64]) -> Result<Histogram> {
ensure_finite(data)?;
let n_bins = edges.len().saturating_sub(1);
if n_bins == 0 {
return Err(DriftError::InvalidBinCount(n_bins));
}
let mut counts = vec![0.0f64; n_bins];
for &v in data {
let idx = if v <= edges[0] {
0
} else if v >= edges[n_bins] {
n_bins - 1
} else {
match edges.partition_point(|&e| e <= v) {
0 => 0,
p if p >= n_bins => n_bins - 1,
p => p - 1,
}
};
counts[idx] += 1.0;
}
Histogram::new(
BinDefinition::Continuous {
edges: edges.to_vec(),
},
counts,
)
}
pub trait ContinuousBinning {
fn fit_edges(&self, reference: &[f64]) -> Result<Vec<f64>>;
}