1#[cfg(not(any(feature = "std", test)))]
9use crate::math::FloatExt;
10use crate::math::{f64_usize, u64_f64, usize_f64};
11
12#[inline]
20#[must_use]
21pub fn percentile_rank(sorted_data: &[f64], value: f64) -> f64 {
22 if sorted_data.is_empty() {
23 return 0.0;
24 }
25 let mut count_below = 0usize;
26 let mut count_equal = 0usize;
27 for &v in sorted_data {
28 if v < value {
29 count_below += 1;
30 } else if (v - value).abs() < f64::EPSILON {
31 count_equal += 1;
32 }
33 }
34 let n = usize_f64(sorted_data.len());
36 0.5f64.mul_add(usize_f64(count_equal), usize_f64(count_below)) / n * 100.0
37}
38
39#[derive(Clone, Copy, Debug)]
45pub struct IqrResult {
46 pub q1: f64,
48 pub median: f64,
50 pub q3: f64,
52 pub iqr: f64,
54 pub lower_fence: f64,
56 pub upper_fence: f64,
58}
59
60#[must_use]
64pub fn iqr(sorted_data: &[f64]) -> Option<IqrResult> {
65 let n = sorted_data.len();
66 if n < 4 {
67 return None;
68 }
69
70 let q1 = quantile_sorted(sorted_data, 0.25);
71 let median = quantile_sorted(sorted_data, 0.50);
72 let q3 = quantile_sorted(sorted_data, 0.75);
73 let iqr_val = q3 - q1;
74
75 Some(IqrResult {
76 q1,
77 median,
78 q3,
79 iqr: iqr_val,
80 lower_fence: 1.5f64.mul_add(-iqr_val, q1),
81 upper_fence: 1.5f64.mul_add(iqr_val, q3),
82 })
83}
84
85#[inline]
87#[must_use]
88pub fn quantile_sorted(sorted_data: &[f64], q: f64) -> f64 {
89 let n = sorted_data.len();
90 if n == 0 {
91 return 0.0;
92 }
93 if n == 1 {
94 return sorted_data[0];
95 }
96 let q = q.clamp(0.0, 1.0);
97 let pos = q * usize_f64(n - 1);
98 let lower = f64_usize(pos.floor());
99 let upper = f64_usize(pos.ceil());
100 if lower == upper {
101 return sorted_data[lower];
102 }
103 let frac = pos - usize_f64(lower);
104 sorted_data[lower].mul_add(1.0 - frac, sorted_data[upper] * frac)
105}
106
107pub struct CovarianceMatrix<const D: usize> {
115 count: u64,
117 means: [f64; D],
119 co_moments: [[f64; D]; D],
121}
122
123impl<const D: usize> CovarianceMatrix<D> {
124 #[must_use]
126 pub const fn new() -> Self {
127 Self {
128 count: 0,
129 means: [0.0; D],
130 co_moments: [[0.0; D]; D],
131 }
132 }
133
134 pub fn observe(&mut self, values: &[f64; D]) {
136 self.count += 1;
137 let n = u64_f64(self.count);
138
139 let mut dx = [0.0; D];
140 for (i, d) in dx.iter_mut().enumerate() {
141 *d = values[i] - self.means[i];
142 }
143
144 for (i, mean) in self.means.iter_mut().enumerate() {
146 *mean += dx[i] / n;
147 }
148
149 for (i, (&val, &mean_i)) in values.iter().zip(self.means.iter()).enumerate() {
151 let dx2_i = val - mean_i;
152 for (j, dx_j) in dx.iter().enumerate().skip(i) {
153 self.co_moments[i][j] += dx_j * dx2_i;
154 }
155 }
156 }
157
158 #[must_use]
160 pub fn covariance(&self, i: usize, j: usize) -> f64 {
161 if self.count < 2 || i >= D || j >= D {
162 return 0.0;
163 }
164 let (r, c) = if i <= j { (i, j) } else { (j, i) };
165 self.co_moments[r][c] / u64_f64(self.count - 1)
166 }
167
168 #[must_use]
170 pub fn correlation(&self, i: usize, j: usize) -> f64 {
171 if i == j {
172 return 1.0;
173 }
174 let cov = self.covariance(i, j);
175 let var_i = self.covariance(i, i);
176 let var_j = self.covariance(j, j);
177 let denom = (var_i * var_j).sqrt();
178 if denom < f64::EPSILON {
179 return 0.0;
180 }
181 (cov / denom).clamp(-1.0, 1.0)
182 }
183
184 #[inline]
186 #[must_use]
187 pub fn variance(&self, i: usize) -> f64 {
188 self.covariance(i, i)
189 }
190
191 #[inline]
193 #[must_use]
194 pub const fn mean(&self, i: usize) -> f64 {
195 if i < D {
196 self.means[i]
197 } else {
198 0.0
199 }
200 }
201
202 #[inline]
204 #[must_use]
205 pub const fn count(&self) -> u64 {
206 self.count
207 }
208}
209
210impl<const D: usize> Default for CovarianceMatrix<D> {
211 fn default() -> Self {
212 Self::new()
213 }
214}
215
216#[derive(Clone, Debug)]
224pub struct StreamingStats {
225 count: u64,
226 mean: f64,
227 m2: f64,
228 m3: f64,
229 m4: f64,
230 min: f64,
231 max: f64,
232}
233
234impl StreamingStats {
235 #[must_use]
237 pub const fn new() -> Self {
238 Self {
239 count: 0,
240 mean: 0.0,
241 m2: 0.0,
242 m3: 0.0,
243 m4: 0.0,
244 min: f64::INFINITY,
245 max: f64::NEG_INFINITY,
246 }
247 }
248
249 pub fn observe(&mut self, value: f64) {
251 let n1 = self.count;
252 self.count += 1;
253 let n = u64_f64(self.count);
254
255 let delta = value - self.mean;
256 let delta_n = delta / n;
257 let delta_n2 = delta_n * delta_n;
258 let term1 = delta * delta_n * u64_f64(n1);
259
260 self.mean += delta_n;
261
262 self.m4 += (4.0 * delta_n).mul_add(
264 -self.m3,
265 (term1 * delta_n2).mul_add(n.mul_add(n, -(3.0 * n)) + 3.0, 6.0 * delta_n2 * self.m2),
266 );
267 self.m3 += (term1 * delta_n).mul_add(n - 2.0, -(3.0 * delta_n * self.m2));
268 self.m2 += term1;
269
270 self.min = alice_det_math::min64(self.min, value);
271 self.max = alice_det_math::max64(self.max, value);
272 }
273
274 #[inline]
276 #[must_use]
277 pub const fn count(&self) -> u64 {
278 self.count
279 }
280
281 #[inline]
283 #[must_use]
284 pub const fn mean(&self) -> f64 {
285 self.mean
286 }
287
288 #[must_use]
290 pub fn variance(&self) -> f64 {
291 if self.count < 2 {
292 return 0.0;
293 }
294 self.m2 / u64_f64(self.count)
295 }
296
297 #[must_use]
299 pub fn sample_variance(&self) -> f64 {
300 if self.count < 2 {
301 return 0.0;
302 }
303 self.m2 / u64_f64(self.count - 1)
304 }
305
306 #[inline]
308 #[must_use]
309 pub fn std_dev(&self) -> f64 {
310 self.variance().sqrt()
311 }
312
313 #[must_use]
315 pub fn skewness(&self) -> f64 {
316 if self.count < 3 || self.m2 < f64::EPSILON {
317 return 0.0;
318 }
319 let n = u64_f64(self.count);
320 n.sqrt() * self.m3 / (self.m2 * self.m2.sqrt())
322 }
323
324 #[must_use]
326 pub fn kurtosis(&self) -> f64 {
327 if self.count < 4 || self.m2 < f64::EPSILON {
328 return 0.0;
329 }
330 let n = u64_f64(self.count);
331 n * self.m4 / (self.m2 * self.m2) - 3.0
332 }
333
334 #[inline]
336 #[must_use]
337 pub const fn min(&self) -> f64 {
338 self.min
339 }
340
341 #[inline]
343 #[must_use]
344 pub const fn max(&self) -> f64 {
345 self.max
346 }
347
348 pub const fn reset(&mut self) {
350 self.count = 0;
351 self.mean = 0.0;
352 self.m2 = 0.0;
353 self.m3 = 0.0;
354 self.m4 = 0.0;
355 self.min = f64::INFINITY;
356 self.max = f64::NEG_INFINITY;
357 }
358}
359
360impl Default for StreamingStats {
361 fn default() -> Self {
362 Self::new()
363 }
364}
365
366#[cfg(test)]
371#[allow(clippy::float_cmp)]
372mod tests {
373 use super::*;
374
375 #[test]
376 fn percentile_rank_basic() {
377 let data = [1.0, 2.0, 3.0, 4.0, 5.0];
378 let rank = percentile_rank(&data, 3.0);
379 assert!((rank - 50.0).abs() < 1e-6);
381 }
382
383 #[test]
384 fn percentile_rank_empty() {
385 assert!((percentile_rank(&[], 5.0) - 0.0).abs() < 1e-6);
386 }
387
388 #[test]
389 fn percentile_rank_below_all() {
390 let data = [10.0, 20.0, 30.0];
391 let rank = percentile_rank(&data, 1.0);
392 assert!((rank - 0.0).abs() < 1e-6);
393 }
394
395 #[test]
396 fn percentile_rank_above_all() {
397 let data = [10.0, 20.0, 30.0];
398 let rank = percentile_rank(&data, 50.0);
399 assert!((rank - 100.0).abs() < 1e-6);
400 }
401
402 #[test]
403 fn iqr_basic() {
404 let data = [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0];
406 let result = iqr(&data).unwrap();
407 assert!((result.q1 - 2.75).abs() < 1e-6);
409 assert!((result.median - 4.5).abs() < 1e-6);
410 assert!((result.q3 - 6.25).abs() < 1e-6);
411 assert!((result.iqr - 3.5).abs() < 1e-6);
412 }
413
414 #[test]
415 fn iqr_too_small() {
416 let data = [1.0, 2.0, 3.0];
417 assert!(iqr(&data).is_none());
418 }
419
420 #[test]
421 fn iqr_fences() {
422 let data = [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0];
423 let result = iqr(&data).unwrap();
424 assert!(result.lower_fence < result.q1);
425 assert!(result.upper_fence > result.q3);
426 }
427
428 #[test]
429 fn quantile_sorted_edges() {
430 let data = [10.0, 20.0, 30.0];
431 assert!((quantile_sorted(&data, 0.0) - 10.0).abs() < 1e-6);
432 assert!((quantile_sorted(&data, 1.0) - 30.0).abs() < 1e-6);
433 assert!((quantile_sorted(&data, 0.5) - 20.0).abs() < 1e-6);
434 }
435
436 #[test]
437 fn covariance_matrix_basic() {
438 let mut cov = CovarianceMatrix::<2>::new();
439 for i in 0..100 {
441 let v = f64::from(i);
442 cov.observe(&[v, v]);
443 }
444 let r = cov.correlation(0, 1);
445 assert!((r - 1.0).abs() < 1e-6);
446 }
447
448 #[test]
449 fn covariance_matrix_negative_correlation() {
450 let mut cov = CovarianceMatrix::<2>::new();
451 for i in 0..100 {
453 let v = f64::from(i);
454 cov.observe(&[v, -v]);
455 }
456 let r = cov.correlation(0, 1);
457 assert!((r - (-1.0)).abs() < 1e-6);
458 }
459
460 #[test]
461 fn covariance_matrix_self_correlation() {
462 let cov = CovarianceMatrix::<3>::new();
463 assert!((cov.correlation(0, 0) - 1.0).abs() < 1e-6);
465 }
466
467 #[test]
468 fn covariance_matrix_variance() {
469 let mut cov = CovarianceMatrix::<2>::new();
470 for i in 0..100 {
472 cov.observe(&[f64::from(i), 0.0]);
473 }
474 let var = cov.variance(0);
475 assert!(var > 800.0 && var < 870.0, "var = {var}");
478 }
479
480 #[test]
481 fn streaming_stats_basic() {
482 let mut s = StreamingStats::new();
483 for v in [2.0, 4.0, 4.0, 4.0, 5.0, 5.0, 7.0, 9.0] {
484 s.observe(v);
485 }
486 assert_eq!(s.count(), 8);
487 assert!((s.mean() - 5.0).abs() < 1e-6);
488 assert!((s.min() - 2.0).abs() < 1e-6);
489 assert!((s.max() - 9.0).abs() < 1e-6);
490 }
491
492 #[test]
493 fn streaming_stats_variance() {
494 let mut s = StreamingStats::new();
495 for v in [2.0, 4.0, 4.0, 4.0, 5.0, 5.0, 7.0, 9.0] {
496 s.observe(v);
497 }
498 assert!((s.variance() - 4.0).abs() < 1e-6);
500 }
501
502 #[test]
503 fn streaming_stats_empty() {
504 let s = StreamingStats::new();
505 assert_eq!(s.count(), 0);
506 assert!((s.mean() - 0.0).abs() < 1e-6);
507 assert!((s.variance() - 0.0).abs() < 1e-6);
508 assert!((s.skewness() - 0.0).abs() < 1e-6);
509 }
510
511 #[test]
512 fn streaming_stats_reset() {
513 let mut s = StreamingStats::new();
514 s.observe(10.0);
515 s.observe(20.0);
516 s.reset();
517 assert_eq!(s.count(), 0);
518 }
519
520 #[test]
521 fn streaming_stats_skewness_symmetric() {
522 let mut s = StreamingStats::new();
523 for v in [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0] {
525 s.observe(v);
526 }
527 assert!(s.skewness().abs() < 0.1);
528 }
529
530 #[test]
531 fn streaming_stats_kurtosis_uniform() {
532 let mut s = StreamingStats::new();
533 for i in 0..1000 {
535 s.observe(f64::from(i));
536 }
537 assert!(s.kurtosis() < 0.0);
538 }
539
540 #[test]
541 fn streaming_stats_default() {
542 let s = StreamingStats::default();
543 assert_eq!(s.count(), 0);
544 }
545
546 #[test]
547 fn covariance_matrix_default() {
548 let cov = CovarianceMatrix::<4>::default();
549 assert_eq!(cov.count(), 0);
550 }
551}