plotters_statistical/stats/
kde.rs1use super::{sorted_finite, StatsError};
4use std::f64::consts::PI;
5
6#[derive(Debug, Clone, PartialEq)]
9pub struct KdeCurve {
10 pub xs: Vec<f64>,
12 pub density: Vec<f64>,
14 pub bandwidth: f64,
16}
17
18impl KdeCurve {
19 pub fn max_density(&self) -> f64 {
22 self.density.iter().copied().fold(0.0_f64, f64::max)
23 }
24}
25
26pub fn silverman_bandwidth(data: &[f64]) -> Result<f64, StatsError> {
34 let sorted = sorted_finite(data);
35 let n = sorted.len();
36 if n < 2 {
37 return Err(StatsError::InvalidBandwidth);
38 }
39 let nf = n as f64;
40 let mean = sorted.iter().sum::<f64>() / nf;
41 let var = sorted.iter().map(|x| (x - mean).powi(2)).sum::<f64>() / (nf - 1.0);
42 let std = var.sqrt();
43
44 let q1 = super::percentile_sorted(&sorted, 0.25);
46 let q3 = super::percentile_sorted(&sorted, 0.75);
47 let iqr = q3 - q1;
48
49 let spread = if iqr > 0.0 { std.min(iqr / 1.34) } else { std };
50 let h = 0.9 * spread * nf.powf(-1.0 / 5.0);
51 if h.is_finite() && h > 0.0 {
52 Ok(h)
53 } else {
54 Err(StatsError::InvalidBandwidth)
55 }
56}
57
58fn gaussian_kernel(u: f64) -> f64 {
59 (-0.5 * u * u).exp() / (2.0 * PI).sqrt()
60}
61
62pub fn kde_curve(
75 data: &[f64],
76 bandwidth: Option<f64>,
77 n_points: usize,
78 cut: f64,
79) -> Result<KdeCurve, StatsError> {
80 let sorted = sorted_finite(data);
81 if sorted.is_empty() {
82 return Err(StatsError::EmptyInput);
83 }
84 let h = match bandwidth {
85 Some(b) if b.is_finite() && b > 0.0 => b,
86 Some(_) => return Err(StatsError::InvalidBandwidth),
87 None => silverman_bandwidth(&sorted)?,
88 };
89
90 let n_points = n_points.max(2);
91 let lo = sorted[0] - cut * h;
92 let hi = sorted[sorted.len() - 1] + cut * h;
93 let step = (hi - lo) / (n_points as f64 - 1.0);
94
95 let nf = sorted.len() as f64;
96 let mut xs = Vec::with_capacity(n_points);
97 let mut density = Vec::with_capacity(n_points);
98 for i in 0..n_points {
99 let x = lo + step * i as f64;
100 let d = sorted
101 .iter()
102 .map(|&xi| gaussian_kernel((x - xi) / h))
103 .sum::<f64>()
104 / (nf * h);
105 xs.push(x);
106 density.push(d);
107 }
108
109 Ok(KdeCurve {
110 xs,
111 density,
112 bandwidth: h,
113 })
114}