1use std::collections::HashMap;
14
15#[inline]
17fn win_start(i: usize, window: usize) -> usize {
18 (i + 1).saturating_sub(window)
19}
20
21fn per_window(
24 data: &[f64],
25 window: usize,
26 min_periods: usize,
27 mut f: impl FnMut(&[f64]) -> f64,
28) -> Vec<f64> {
29 let n = data.len();
30 let mut out = vec![f64::NAN; n];
31 let mut buf: Vec<f64> = Vec::with_capacity(window.min(n));
32 for i in 0..n {
33 buf.clear();
34 for &x in &data[win_start(i, window)..=i] {
35 if !x.is_nan() {
36 buf.push(x);
37 }
38 }
39 if buf.len() >= min_periods.max(1) {
40 out[i] = f(&buf);
41 }
42 }
43 out
44}
45
46pub fn count(
54 data: &[f64],
55 window: usize,
56 min_periods: usize,
57 center: bool,
58) -> Vec<f64> {
59 let n = data.len();
60 let fwd = if center { window - window / 2 - 1 } else { 0 };
61 let back = window.saturating_sub(fwd + 1);
62 (0..n)
63 .map(|i| {
64 let end = std::cmp::min(i + fwd, n - 1);
65 let start = if window == usize::MAX { 0 } else { i.saturating_sub(back) };
66 let covered = end - start + 1;
67 if covered < min_periods.max(1) {
68 return f64::NAN;
69 }
70 data[start..=end].iter().filter(|x| !x.is_nan()).count() as f64
71 })
72 .collect()
73}
74
75fn sliding<T: Copy>(
77 data: &[f64],
78 window: usize,
79 mut update: impl FnMut(f64, bool) -> T,
80) -> Vec<T> {
81 (0..data.len())
82 .map(|i| {
83 if window != usize::MAX && i >= window {
84 update(data[i - window], false);
85 }
86 update(data[i], true)
87 })
88 .collect()
89}
90
91pub fn sum(data: &[f64], window: usize, min_periods: usize) -> Vec<f64> {
93 let mut s = 0.0f64;
94 let mut nobs = 0usize;
95 sliding(data, window, |x, entering| {
96 if !x.is_nan() {
97 if entering {
98 s += x;
99 nobs += 1;
100 } else {
101 s -= x;
102 nobs -= 1;
103 }
104 }
105 (s, nobs)
106 })
107 .into_iter()
108 .map(|(s, c)| if c >= min_periods.max(1) { s } else { f64::NAN })
109 .collect()
110}
111
112pub fn mean(data: &[f64], window: usize, min_periods: usize) -> Vec<f64> {
114 let mut s = 0.0f64;
115 let mut nobs = 0usize;
116 sliding(data, window, |x, entering| {
117 if !x.is_nan() {
118 if entering {
119 s += x;
120 nobs += 1;
121 } else {
122 s -= x;
123 nobs -= 1;
124 }
125 }
126 (s, nobs)
127 })
128 .into_iter()
129 .map(|(s, c)| {
130 if c >= min_periods.max(1) {
131 s / c as f64
132 } else {
133 f64::NAN
134 }
135 })
136 .collect()
137}
138
139fn extremum(data: &[f64], window: usize, min_periods: usize, is_min: bool) -> Vec<f64> {
142 let n = data.len();
143 let mut out = vec![f64::NAN; n];
144 let mut deque: std::collections::VecDeque<usize> = std::collections::VecDeque::new();
145 let better = |a: f64, b: f64| if is_min { a <= b } else { a >= b };
146 let mut nobs = 0usize;
147 for i in 0..n {
148 if window != usize::MAX && i >= window && !data[i - window].is_nan() {
149 nobs -= 1;
150 }
151 if let Some(&front) = deque.front() {
152 if window != usize::MAX && front + window <= i {
153 deque.pop_front();
154 }
155 }
156 if !data[i].is_nan() {
157 nobs += 1;
158 while let Some(&back) = deque.back() {
159 if better(data[i], data[back]) {
160 deque.pop_back();
161 } else {
162 break;
163 }
164 }
165 deque.push_back(i);
166 }
167 if nobs >= min_periods.max(1) {
168 if let Some(&front) = deque.front() {
169 out[i] = data[front];
170 }
171 }
172 }
173 out
174}
175
176pub fn min(data: &[f64], window: usize, min_periods: usize) -> Vec<f64> {
178 extremum(data, window, min_periods, true)
179}
180
181pub fn max(data: &[f64], window: usize, min_periods: usize) -> Vec<f64> {
183 extremum(data, window, min_periods, false)
184}
185
186fn buf_var(b: &[f64], ddof: usize) -> f64 {
189 if b.len() <= ddof {
190 return f64::NAN;
191 }
192 let m = b.iter().sum::<f64>() / b.len() as f64;
193 b.iter().map(|x| (x - m) * (x - m)).sum::<f64>() / (b.len() - ddof) as f64
194}
195
196pub fn var(data: &[f64], window: usize, min_periods: usize, ddof: usize) -> Vec<f64> {
199 per_window(data, window, min_periods, |b| buf_var(b, ddof))
200}
201
202pub fn std(data: &[f64], window: usize, min_periods: usize, ddof: usize) -> Vec<f64> {
204 per_window(data, window, min_periods, |b| buf_var(b, ddof).sqrt())
205}
206
207pub fn sem(data: &[f64], window: usize, min_periods: usize, ddof: usize) -> Vec<f64> {
209 per_window(data, window, min_periods, |b| {
210 (buf_var(b, ddof) / b.len() as f64).sqrt()
211 })
212}
213
214pub fn dev(data: &[f64], window: usize, min_periods: usize) -> Vec<f64> {
218 per_window(data, window, min_periods, |b| {
219 let m = b.iter().sum::<f64>() / b.len() as f64;
220 b.iter().map(|x| (x - m).abs()).sum::<f64>() / b.len() as f64
221 })
222}
223
224pub fn mode(data: &[f64], window: usize, min_periods: usize) -> Vec<f64> {
227 per_sorted_window(data, window, min_periods, |sorted, _| {
228 let (mut best_val, mut best_len) = (sorted[0], 1usize);
232 let (mut cur_val, mut cur_len) = (sorted[0], 1usize);
233 for &x in &sorted[1..] {
234 if x == cur_val {
235 cur_len += 1;
236 } else {
237 cur_val = x;
238 cur_len = 1;
239 }
240 if cur_len > best_len {
241 best_len = cur_len;
242 best_val = cur_val;
243 }
244 }
245 best_val
246 })
247}
248
249pub fn skew(data: &[f64], window: usize, min_periods: usize) -> Vec<f64> {
251 per_window(data, window, min_periods, |b| {
252 let n = b.len() as f64;
253 if b.len() < 3 {
254 return f64::NAN;
255 }
256 let m = b.iter().sum::<f64>() / n;
257 let m2 = b.iter().map(|x| (x - m).powi(2)).sum::<f64>() / n;
258 let m3 = b.iter().map(|x| (x - m).powi(3)).sum::<f64>() / n;
259 if m2 <= 0.0 {
260 return f64::NAN;
261 }
262 (n * (n - 1.0)).sqrt() / (n - 2.0) * m3 / m2.powf(1.5)
263 })
264}
265
266pub fn kurt(data: &[f64], window: usize, min_periods: usize) -> Vec<f64> {
268 per_window(data, window, min_periods, |b| {
269 let n = b.len() as f64;
270 if b.len() < 4 {
271 return f64::NAN;
272 }
273 let m = b.iter().sum::<f64>() / n;
274 let m2 = b.iter().map(|x| (x - m).powi(2)).sum::<f64>() / n;
275 let m4 = b.iter().map(|x| (x - m).powi(4)).sum::<f64>() / n;
276 if m2 <= 0.0 {
277 return f64::NAN;
278 }
279 ((n + 1.0) * m4 / (m2 * m2) - 3.0 * (n - 1.0)) * (n - 1.0)
280 / ((n - 2.0) * (n - 3.0))
281 })
282}
283
284fn sorted_quantile(sorted: &[f64], q: f64, interpolation: &str) -> f64 {
288 let n = sorted.len();
289 let pos = q * (n - 1) as f64;
290 let (lo, hi) = (pos.floor() as usize, pos.ceil() as usize);
291 let frac = pos - lo as f64;
292 match interpolation {
293 "lower" => sorted[lo],
294 "higher" => sorted[hi],
295 "nearest" => sorted[if frac > 0.5 || (frac == 0.5 && lo % 2 == 1) { hi } else { lo }],
297 "midpoint" => (sorted[lo] + sorted[hi]) / 2.0,
298 _ => sorted[lo] + frac * (sorted[hi] - sorted[lo]),
300 }
301}
302
303fn per_sorted_window(
304 data: &[f64],
305 window: usize,
306 min_periods: usize,
307 mut f: impl FnMut(&[f64], f64) -> f64,
308) -> Vec<f64> {
309 let n = data.len();
310 let mut out = vec![f64::NAN; n];
311 let mut sorted: Vec<f64> = Vec::with_capacity(window.min(n));
313 for i in 0..n {
314 if window != usize::MAX && i >= window {
315 let leaving = data[i - window];
316 if !leaving.is_nan() {
317 let pos = sorted.partition_point(|&v| v < leaving);
318 sorted.remove(pos);
319 }
320 }
321 let x = data[i];
322 if !x.is_nan() {
323 let pos = sorted.partition_point(|&v| v < x);
324 sorted.insert(pos, x);
325 }
326 if sorted.len() >= min_periods.max(1) {
327 out[i] = f(&sorted, x);
328 }
329 }
330 out
331}
332
333pub fn median(data: &[f64], window: usize, min_periods: usize) -> Vec<f64> {
335 per_sorted_window(data, window, min_periods, |s, _| {
336 sorted_quantile(s, 0.5, "linear")
337 })
338}
339
340pub fn quantile(
342 data: &[f64],
343 window: usize,
344 min_periods: usize,
345 q: f64,
346 interpolation: &str,
347) -> Vec<f64> {
348 per_sorted_window(data, window, min_periods, |s, _| {
349 sorted_quantile(s, q, interpolation)
350 })
351}
352
353pub fn rank(
357 data: &[f64],
358 window: usize,
359 min_periods: usize,
360 method: &str,
361 ascending: bool,
362 pct: bool,
363) -> Vec<f64> {
364 per_sorted_window(data, window, min_periods, |s, x| {
365 if x.is_nan() {
366 return f64::NAN;
367 }
368 let below = s.partition_point(|&v| v < x);
369 let through = s.partition_point(|&v| v <= x);
370 let (lo, hi) = if ascending {
371 (below + 1, through)
372 } else {
373 (s.len() - through + 1, s.len() - below)
374 };
375 let r = match method {
376 "min" => lo as f64,
377 "max" => hi as f64,
378 _ => (lo + hi) as f64 / 2.0, };
380 if pct {
381 r / s.len() as f64
382 } else {
383 r
384 }
385 })
386}
387
388pub fn nunique(data: &[f64], window: usize, min_periods: usize) -> Vec<f64> {
390 let n = data.len();
391 let mut out = vec![f64::NAN; n];
392 let mut counts: HashMap<u64, usize> = HashMap::new();
393 let mut nobs = 0usize;
394 for i in 0..n {
395 if window != usize::MAX && i >= window {
396 let leaving = data[i - window];
397 if !leaving.is_nan() {
398 nobs -= 1;
399 let k = leaving.to_bits();
400 let c = counts.get_mut(&k).expect("leaving value was inserted");
401 *c -= 1;
402 if *c == 0 {
403 counts.remove(&k);
404 }
405 }
406 }
407 let x = data[i];
408 if !x.is_nan() {
409 nobs += 1;
410 *counts.entry(x.to_bits()).or_insert(0) += 1;
411 }
412 if nobs >= min_periods.max(1) {
413 out[i] = counts.len() as f64;
414 }
415 }
416 out
417}
418
419pub fn edge_positions(
423 valid: &[bool],
424 window: usize,
425 min_periods: usize,
426 last: bool,
427) -> Vec<Option<usize>> {
428 let n = valid.len();
429 let mut out = vec![None; n];
430 for i in 0..n {
431 let start = win_start(i, window);
432 let nobs = valid[start..=i].iter().filter(|&&v| v).count();
433 if nobs >= min_periods.max(1) {
434 out[i] = if last {
435 (start..=i).rev().find(|&k| valid[k])
436 } else {
437 (start..=i).find(|&k| valid[k])
438 };
439 }
440 }
441 out
442}
443
444fn pairwise(
447 x: &[f64],
448 y: &[f64],
449 window: usize,
450 min_periods: usize,
451 mut f: impl FnMut(&[f64], &[f64]) -> f64,
452) -> Vec<f64> {
453 let n = x.len();
454 let mut out = vec![f64::NAN; n];
455 let mut bx: Vec<f64> = Vec::new();
456 let mut by: Vec<f64> = Vec::new();
457 #[allow(clippy::needless_range_loop)] for i in 0..n {
459 bx.clear();
460 by.clear();
461 for k in win_start(i, window)..=i {
462 if !x[k].is_nan() && !y[k].is_nan() {
464 bx.push(x[k]);
465 by.push(y[k]);
466 }
467 }
468 if bx.len() >= min_periods.max(1) {
469 out[i] = f(&bx, &by);
470 }
471 }
472 out
473}
474
475pub fn cov(x: &[f64], y: &[f64], window: usize, min_periods: usize, ddof: usize) -> Vec<f64> {
477 pairwise(x, y, window, min_periods, |bx, by| {
478 if bx.len() <= ddof {
479 return f64::NAN;
480 }
481 let n = bx.len() as f64;
482 let (mx, my) = (
483 bx.iter().sum::<f64>() / n,
484 by.iter().sum::<f64>() / n,
485 );
486 bx.iter()
487 .zip(by)
488 .map(|(a, b)| (a - mx) * (b - my))
489 .sum::<f64>()
490 / (n - ddof as f64)
491 })
492}
493
494pub fn corr(x: &[f64], y: &[f64], window: usize, min_periods: usize) -> Vec<f64> {
496 pairwise(x, y, window, min_periods, |bx, by| {
497 let sx = buf_var(bx, 1).sqrt();
498 let sy = buf_var(by, 1).sqrt();
499 #[allow(clippy::neg_cmp_op_on_partial_ord)]
501 if !(sx > 0.0) || !(sy > 0.0) {
502 return f64::NAN;
503 }
504 let n = bx.len() as f64;
505 let (mx, my) = (
506 bx.iter().sum::<f64>() / n,
507 by.iter().sum::<f64>() / n,
508 );
509 let c = bx
510 .iter()
511 .zip(by)
512 .map(|(a, b)| (a - mx) * (b - my))
513 .sum::<f64>()
514 / (n - 1.0);
515 c / (sx * sy)
516 })
517}
518
519pub fn ewm_mean(
525 data: &[f64],
526 alpha: f64,
527 adjust: bool,
528 ignore_na: bool,
529 min_periods: usize,
530) -> Vec<f64> {
531 let old_wt_factor = 1.0 - alpha;
532 let new_wt = if adjust { 1.0 } else { alpha };
533 let mut avg = f64::NAN;
534 let mut old_wt = 1.0;
535 let mut nobs = 0usize;
536 data.iter()
537 .map(|&x| {
538 let is_obs = !x.is_nan();
539 nobs += is_obs as usize;
540 if !avg.is_nan() {
541 if is_obs || !ignore_na {
542 old_wt *= old_wt_factor;
543 if is_obs {
544 if avg != x {
545 avg = (old_wt * avg + new_wt * x) / (old_wt + new_wt);
546 }
547 if adjust {
548 old_wt += new_wt;
549 } else {
550 old_wt = 1.0;
551 }
552 }
553 }
554 } else if is_obs {
555 avg = x;
556 }
557 if nobs >= min_periods.max(1) {
558 avg
559 } else {
560 f64::NAN
561 }
562 })
563 .collect()
564}
565
566pub fn ewm_sum(data: &[f64], alpha: f64, ignore_na: bool, min_periods: usize) -> Vec<f64> {
569 let old_wt_factor = 1.0 - alpha;
570 let mut s = f64::NAN;
571 let mut nobs = 0usize;
572 data.iter()
573 .map(|&x| {
574 let is_obs = !x.is_nan();
575 nobs += is_obs as usize;
576 if !s.is_nan() {
577 if is_obs || !ignore_na {
578 s *= old_wt_factor;
579 if is_obs {
580 s += x;
581 }
582 }
583 } else if is_obs {
584 s = x;
585 }
586 if nobs >= min_periods.max(1) {
587 s
588 } else {
589 f64::NAN
590 }
591 })
592 .collect()
593}
594
595#[allow(clippy::too_many_arguments)]
598pub fn ewm_cov(
599 x: &[f64],
600 y: &[f64],
601 alpha: f64,
602 adjust: bool,
603 ignore_na: bool,
604 bias: bool,
605 min_periods: usize,
606) -> Vec<f64> {
607 let old_wt_factor = 1.0 - alpha;
608 let new_wt = if adjust { 1.0 } else { alpha };
609 let (mut mean_x, mut mean_y) = (f64::NAN, f64::NAN);
610 let mut cov = 0.0;
611 let (mut sum_wt, mut sum_wt2, mut old_wt) = (1.0, 1.0, 1.0);
612 let mut nobs = 0usize;
613 x.iter()
614 .zip(y)
615 .map(|(&xi, &yi)| {
616 let is_obs = !xi.is_nan() && !yi.is_nan();
617 nobs += is_obs as usize;
618 if !mean_x.is_nan() {
619 if is_obs || !ignore_na {
620 sum_wt *= old_wt_factor;
621 sum_wt2 *= old_wt_factor * old_wt_factor;
622 old_wt *= old_wt_factor;
623 if is_obs {
624 let (old_mean_x, old_mean_y) = (mean_x, mean_y);
625 if mean_x != xi {
626 mean_x = (old_wt * old_mean_x + new_wt * xi) / (old_wt + new_wt);
627 }
628 if mean_y != yi {
629 mean_y = (old_wt * old_mean_y + new_wt * yi) / (old_wt + new_wt);
630 }
631 cov = (old_wt
632 * (cov + (old_mean_x - mean_x) * (old_mean_y - mean_y))
633 + new_wt * (xi - mean_x) * (yi - mean_y))
634 / (old_wt + new_wt);
635 sum_wt += new_wt;
636 sum_wt2 += new_wt * new_wt;
637 old_wt += new_wt;
638 if !adjust {
639 sum_wt /= old_wt;
640 sum_wt2 /= old_wt * old_wt;
641 old_wt = 1.0;
642 }
643 }
644 }
645 } else if is_obs {
646 mean_x = xi;
647 mean_y = yi;
648 }
649 if nobs >= min_periods.max(1) {
650 if bias {
651 cov
652 } else {
653 let numerator = sum_wt * sum_wt;
654 let denominator = numerator - sum_wt2;
655 if denominator > 0.0 {
656 (numerator / denominator) * cov
657 } else {
658 f64::NAN
659 }
660 }
661 } else {
662 f64::NAN
663 }
664 })
665 .collect()
666}
667
668pub fn ewm_var(
670 data: &[f64],
671 alpha: f64,
672 adjust: bool,
673 ignore_na: bool,
674 bias: bool,
675 min_periods: usize,
676) -> Vec<f64> {
677 ewm_cov(data, data, alpha, adjust, ignore_na, bias, min_periods)
678}
679
680pub fn ewm_corr(
683 x: &[f64],
684 y: &[f64],
685 alpha: f64,
686 adjust: bool,
687 ignore_na: bool,
688 min_periods: usize,
689) -> Vec<f64> {
690 let cv = ewm_cov(x, y, alpha, adjust, ignore_na, true, min_periods);
691 let n = x.len();
694 let (mut xp, mut yp) = (vec![f64::NAN; n], vec![f64::NAN; n]);
695 for i in 0..n {
696 if !x[i].is_nan() && !y[i].is_nan() {
697 xp[i] = x[i];
698 yp[i] = y[i];
699 }
700 }
701 let vx = ewm_cov(&xp, &xp, alpha, adjust, ignore_na, true, min_periods);
702 let vy = ewm_cov(&yp, &yp, alpha, adjust, ignore_na, true, min_periods);
703 cv.iter()
704 .zip(vx.iter().zip(&vy))
705 .map(|(&c, (&a, &b))| {
706 let d = (a * b).sqrt();
707 if d > 0.0 {
708 c / d
709 } else {
710 f64::NAN
711 }
712 })
713 .collect()
714}