1use crate::predict::ExtrapolatedPrediction;
2
3#[derive(Debug, Clone)]
5pub struct Neighbor {
6 pub distance: f64,
8 pub output: f64,
10 pub weight: f64,
13}
14
15#[derive(Debug, Clone)]
17pub struct Neighbors {
18 pub neighbors: Vec<Neighbor>,
19}
20
21impl Neighbors {
22 pub fn weighted_mean(&self) -> f64 {
26 if self.neighbors.is_empty() {
27 return f64::NAN;
28 }
29
30 let exact: Vec<&Neighbor> = self
32 .neighbors
33 .iter()
34 .filter(|n| n.distance == 0.0)
35 .collect();
36 if !exact.is_empty() {
37 let total_w: f64 = exact.iter().map(|n| n.weight).sum();
38 if total_w > 0.0 {
39 return exact.iter().map(|n| n.weight * n.output).sum::<f64>() / total_w;
40 }
41 return exact[0].output;
42 }
43
44 let mut weight_sum = 0.0;
45 let mut value_sum = 0.0;
46 for n in &self.neighbors {
47 let w = n.weight / n.distance;
48 weight_sum += w;
49 value_sum += w * n.output;
50 }
51 value_sum / weight_sum
52 }
53
54 pub fn gaussian_weighted_mean(&self, bandwidth: f64) -> f64 {
58 if self.neighbors.is_empty() {
59 return f64::NAN;
60 }
61
62 let exact: Vec<&Neighbor> = self
64 .neighbors
65 .iter()
66 .filter(|n| n.distance == 0.0)
67 .collect();
68 if !exact.is_empty() {
69 let total_w: f64 = exact.iter().map(|n| n.weight).sum();
70 if total_w > 0.0 {
71 return exact.iter().map(|n| n.weight * n.output).sum::<f64>() / total_w;
72 }
73 return exact[0].output;
74 }
75
76 let h2 = 2.0 * bandwidth * bandwidth;
77 let mut weight_sum = 0.0;
78 let mut value_sum = 0.0;
79 for n in &self.neighbors {
80 let w = (-n.distance * n.distance / h2).exp() * n.weight;
81 if w < 1e-15 {
82 break;
85 }
86 weight_sum += w;
87 value_sum += w * n.output;
88 }
89 if weight_sum > 0.0 {
90 value_sum / weight_sum
91 } else {
92 self.neighbors[0].output
94 }
95 }
96
97 pub fn extrapolate(&self) -> ExtrapolatedPrediction {
99 ExtrapolatedPrediction::from_neighbors(&self.neighbors)
100 }
101
102 pub fn class_votes(&self) -> Vec<(f64, f64)> {
105 if self.neighbors.is_empty() {
106 return Vec::new();
107 }
108
109 let mut counts: Vec<(f64, f64)> = Vec::new(); for n in &self.neighbors {
111 let w = if n.distance == 0.0 {
112 n.weight * 1e6 } else {
114 n.weight / n.distance
115 };
116 if let Some(entry) = counts
117 .iter_mut()
118 .find(|(v, _)| (*v - n.output).abs() < 1e-10)
119 {
120 entry.1 += w;
121 } else {
122 counts.push((n.output, w));
123 }
124 }
125
126 let total: f64 = counts.iter().map(|(_, w)| w).sum();
127 let n_classes = counts.len() as f64;
128 if total > 0.0 {
129 counts
130 .into_iter()
131 .map(|(class, w)| (class, w / total))
132 .collect()
133 } else {
134 counts
135 .into_iter()
136 .map(|(class, _)| (class, 1.0 / n_classes))
137 .collect()
138 }
139 }
140
141 pub fn sample(&self, rng_value: f64) -> Option<f64> {
143 if self.neighbors.is_empty() {
144 return None;
145 }
146 let idx = (rng_value * self.neighbors.len() as f64) as usize;
147 let idx = idx.min(self.neighbors.len() - 1);
148 Some(self.neighbors[idx].output)
149 }
150}