mod kernel;
use kernel::{count_to_f64, sample_variance};
const TWO_PI: f64 = std::f64::consts::TAU;
#[derive(Debug, Clone, PartialEq)]
pub struct GaussianKde {
data: Vec<f64>,
variance: f64,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum DensityError {
EmptyInput,
SinglePoint,
ZeroVariance,
NonFinite,
}
impl std::fmt::Display for DensityError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::EmptyInput => write!(f, "sample has no observations"),
Self::SinglePoint => write!(f, "sample needs at least two observations"),
Self::ZeroVariance => write!(f, "sample has zero variance"),
Self::NonFinite => write!(f, "sample contains a non-finite value"),
}
}
}
impl std::error::Error for DensityError {}
impl GaussianKde {
#[must_use]
pub const fn variance(&self) -> f64 {
self.variance
}
#[must_use]
pub fn bandwidth(&self) -> f64 {
self.variance.sqrt()
}
#[must_use]
pub fn density_at(&self, x: f64) -> f64 {
let n = count_to_f64(self.data.len());
let norm = 1.0 / (TWO_PI * self.variance).sqrt();
let inv_two_var = 1.0 / (2.0 * self.variance);
let sum: f64 = self
.data
.iter()
.map(|&xi| {
let d = x - xi;
(-d * d * inv_two_var).exp()
})
.sum();
norm * sum / n
}
#[must_use]
pub fn density(&self, xs: &[f64]) -> Vec<f64> {
xs.iter().map(|&x| self.density_at(x)).collect()
}
}
pub fn gaussian_kde(data: &[f64]) -> Result<GaussianKde, DensityError> {
if data.is_empty() {
return Err(DensityError::EmptyInput);
}
if data.iter().any(|v| !v.is_finite()) {
return Err(DensityError::NonFinite);
}
if data.len() < 2 {
return Err(DensityError::SinglePoint);
}
let var = sample_variance(data);
if var <= 0.0 {
return Err(DensityError::ZeroVariance);
}
let n = count_to_f64(data.len());
let factor = n.powf(-1.0 / 5.0);
let variance = factor * factor * var;
Ok(GaussianKde {
data: data.to_vec(),
variance,
})
}
#[cfg(test)]
#[path = "tests.rs"]
mod tests;