tea_rolling/features.rs
1use tea_core::prelude::*;
2
3/// Trait for rolling window operations on valid (non-None) elements.
4pub trait RollingValidFeature<T: IsNone>: Vec1View<T> {
5 /// Calculates the rolling sum of valid elements within a window.
6 ///
7 /// # Arguments
8 ///
9 /// * `window` - The size of the rolling window.
10 /// * `min_periods` - The minimum number of observations in window required to have a value.
11 /// * `out` - Optional output buffer.
12 ///
13 /// # Returns
14 ///
15 /// A vector containing the rolling sums.
16 ///
17 /// # See Also
18 ///
19 /// [`RollingFeature::ts_sum`]
20 #[no_out]
21 fn ts_vsum<O: Vec1<U>, U>(
22 &self,
23 window: usize,
24 min_periods: Option<usize>,
25 out: Option<O::UninitRefMut<'_>>,
26 ) -> O
27 where
28 T::Inner: Number,
29 f64: Cast<U>,
30 {
31 let min_periods = min_periods.unwrap_or(window / 2).min(window);
32 let mut sum = T::Inner::zero();
33 let mut n = 0;
34 self.rolling_apply(
35 window,
36 move |v_rm, v| {
37 if v.not_none() {
38 n += 1;
39 sum += v.unwrap();
40 }
41 let res = if n >= min_periods {
42 sum.f64().cast()
43 } else {
44 f64::NAN.cast()
45 };
46 if let Some(v_rm) = v_rm {
47 if v_rm.not_none() {
48 n -= 1;
49 sum -= v_rm.unwrap();
50 }
51 }
52 res
53 },
54 out,
55 )
56 }
57
58 /// Calculates the rolling mean of valid elements within a window.
59 ///
60 /// # Arguments
61 ///
62 /// * `window` - The size of the rolling window.
63 /// * `min_periods` - The minimum number of observations in window required to have a value.
64 /// * `out` - Optional output buffer.
65 ///
66 /// # Returns
67 ///
68 /// A vector containing the rolling means.
69 ///
70 /// # See Also
71 ///
72 /// [`RollingFeature::ts_mean`]
73 #[no_out]
74 fn ts_vmean<O: Vec1<U>, U>(
75 &self,
76 window: usize,
77 min_periods: Option<usize>,
78 out: Option<O::UninitRefMut<'_>>,
79 ) -> O
80 where
81 T::Inner: Number,
82 f64: Cast<U>,
83 {
84 let min_periods = min_periods.unwrap_or(window / 2).min(window);
85 let mut sum = 0.;
86 let mut n = 0;
87 self.rolling_apply(
88 window,
89 move |v_rm, v| {
90 if v.not_none() {
91 n += 1;
92 sum += v.unwrap().f64();
93 }
94 let res = if n >= min_periods {
95 (sum / n as f64).cast()
96 } else {
97 f64::NAN.cast()
98 };
99 if let Some(v_rm) = v_rm {
100 if v_rm.not_none() {
101 n -= 1;
102 sum -= v_rm.unwrap().f64();
103 }
104 }
105 res
106 },
107 out,
108 )
109 }
110
111 /// Calculates the exponentially weighted moving average of valid elements within a window.
112 ///
113 /// # Arguments
114 ///
115 /// * `window` - The size of the rolling window.
116 /// * `min_periods` - The minimum number of observations in window required to have a value.
117 /// * `out` - Optional output buffer.
118 ///
119 /// # Returns
120 ///
121 /// A vector containing the exponentially weighted moving averages.
122 ///
123 /// # See Also
124 ///
125 /// [`RollingFeature::ts_ewm`]
126 #[no_out]
127 fn ts_vewm<O: Vec1<U>, U>(
128 &self,
129 window: usize,
130 min_periods: Option<usize>,
131 out: Option<O::UninitRefMut<'_>>,
132 ) -> O
133 where
134 T::Inner: Number,
135 f64: Cast<U>,
136 {
137 let min_periods = min_periods.unwrap_or(window / 2).min(window);
138 // 错位相减核心公式:
139 // q_x(t) = 1 * new_element - alpha(q_x(t-1 without 1st element)) - 1st element * oma ^ (n-1)
140 let mut q_x = 0.; // 权重的分子部分 * 元素,使用错位相减法来计算
141 let alpha = 2. / window.f64();
142 let oma = 1. - alpha; // one minus alpha
143 let mut n = 0;
144 self.rolling_apply(
145 window,
146 move |v_rm, v| {
147 if v.not_none() {
148 n += 1;
149 q_x += v.unwrap().f64() - alpha * q_x.f64();
150 }
151 let res = if n >= min_periods {
152 q_x.f64() * alpha / (1. - oma.powi(n as i32))
153 } else {
154 f64::NAN
155 };
156 if let Some(v_rm) = v_rm {
157 if v_rm.not_none() {
158 n -= 1;
159 // 本应是window-1,不过本身window就要自然减一,调整一下顺序
160 q_x -= v_rm.unwrap().f64() * oma.powi(n as i32);
161 }
162 }
163 res.cast()
164 },
165 out,
166 )
167 }
168
169 /// Calculates the weighted moving average of valid elements within a window.
170 ///
171 /// # Arguments
172 ///
173 /// * `window` - The size of the rolling window.
174 /// * `min_periods` - The minimum number of observations in window required to have a value.
175 /// * `out` - Optional output buffer.
176 ///
177 /// # Returns
178 ///
179 /// A vector containing the weighted moving averages.
180 ///
181 /// # See Also
182 ///
183 /// [`RollingFeature::ts_wma`]
184 #[no_out]
185 fn ts_vwma<O: Vec1<U>, U>(
186 &self,
187 window: usize,
188 min_periods: Option<usize>,
189 out: Option<O::UninitRefMut<'_>>,
190 ) -> O
191 where
192 T::Inner: Number,
193 f64: Cast<U>,
194 {
195 let min_periods = min_periods.unwrap_or(window / 2).min(window);
196 let mut sum = 0.;
197 let mut sum_xt = 0.;
198 let mut n = 0;
199 self.rolling_apply(
200 window,
201 move |v_rm, v| {
202 if v.not_none() {
203 let v = v.unwrap().f64();
204 n += 1;
205 sum_xt += n.f64() * v; // 错位相减法, 忽略nan带来的系数和window不一致问题
206 sum += v;
207 }
208 let res = if n >= min_periods {
209 let divisor = (n * (n + 1)) >> 1;
210 sum_xt / divisor.f64()
211 } else {
212 f64::NAN
213 };
214 if let Some(v_rm) = v_rm {
215 if v_rm.not_none() {
216 n -= 1;
217 sum_xt -= sum;
218 sum -= v_rm.unwrap().f64();
219 }
220 }
221 res.cast()
222 },
223 out,
224 )
225 }
226
227 /// Calculates the rolling standard deviation of valid elements within a window.
228 ///
229 /// # Arguments
230 ///
231 /// * `window` - The size of the rolling window.
232 /// * `min_periods` - The minimum number of observations in window required to have a value.
233 /// * `out` - Optional output buffer.
234 ///
235 /// # Returns
236 ///
237 /// A vector containing the rolling standard deviations.
238 ///
239 /// # See Also
240 ///
241 /// [`RollingFeature::ts_std`]
242 #[no_out]
243 fn ts_vstd<O: Vec1<U>, U>(
244 &self,
245 window: usize,
246 min_periods: Option<usize>,
247 out: Option<O::UninitRefMut<'_>>,
248 ) -> O
249 where
250 T::Inner: Number,
251 f64: Cast<U>,
252 {
253 let min_periods = min_periods.unwrap_or(window / 2).min(window).max(2);
254 let mut sum = 0.;
255 let mut sum2 = 0.;
256 let mut n = 0;
257 self.rolling_apply(
258 window,
259 move |v_rm, v| {
260 if v.not_none() {
261 let v = v.unwrap().f64();
262 n += 1;
263 sum += v;
264 sum2 += v * v
265 }
266 let res = if n >= min_periods {
267 let n_f64 = n.f64();
268 let mut var = sum2 / n_f64;
269 let mean = sum / n_f64;
270 var -= mean.powi(2);
271 // variance should be greater than 0
272 if var > EPS {
273 (var * n_f64 / (n - 1).f64()).sqrt()
274 } else {
275 0.
276 }
277 } else {
278 f64::NAN
279 };
280 if let Some(v) = v_rm {
281 if v.not_none() {
282 let v = v.unwrap().f64();
283 n -= 1;
284 sum -= v;
285 sum2 -= v * v
286 }
287 }
288 res.cast()
289 },
290 out,
291 )
292 }
293
294 /// Calculates the rolling variance of valid elements within a window.
295 ///
296 /// # Arguments
297 ///
298 /// * `window` - The size of the rolling window.
299 /// * `min_periods` - The minimum number of observations in window required to have a value.
300 /// * `out` - Optional output buffer.
301 ///
302 /// # Returns
303 ///
304 /// A vector containing the rolling variances.
305 ///
306 /// # See Also
307 ///
308 /// [`RollingFeature::ts_var`]
309 #[no_out]
310 fn ts_vvar<O: Vec1<U>, U>(
311 &self,
312 window: usize,
313 min_periods: Option<usize>,
314 out: Option<O::UninitRefMut<'_>>,
315 ) -> O
316 where
317 T::Inner: Number,
318 f64: Cast<U>,
319 {
320 let min_periods = min_periods.unwrap_or(window / 2).min(window).max(2);
321 let mut sum = 0.;
322 let mut sum2 = 0.;
323 let mut n = 0;
324 self.rolling_apply(
325 window,
326 move |v_rm, v| {
327 if v.not_none() {
328 n += 1;
329 let v = v.unwrap().f64();
330 sum += v;
331 sum2 += v * v
332 }
333 let res = if n >= min_periods {
334 let n_f64 = n.f64();
335 let mut var = sum2 / n_f64;
336 let mean = sum / n_f64;
337 var -= mean.powi(2);
338 // variance should be greater than 0
339 if var > EPS {
340 var * n_f64 / (n - 1).f64()
341 } else {
342 0.
343 }
344 } else {
345 f64::NAN
346 };
347 if let Some(v) = v_rm {
348 if v.not_none() {
349 let v = v.unwrap().f64();
350 n -= 1;
351 sum -= v;
352 sum2 -= v * v
353 }
354 }
355 res.cast()
356 },
357 out,
358 )
359 }
360
361 /// Calculates the rolling skewness of valid elements within a window.
362 ///
363 /// # Arguments
364 ///
365 /// * `window` - The size of the rolling window.
366 /// * `min_periods` - The minimum number of observations in window required to have a value.
367 /// * `out` - Optional output buffer.
368 ///
369 /// # Returns
370 ///
371 /// A vector containing the rolling skewness values.
372 ///
373 /// # See Also
374 ///
375 /// [`RollingFeature::ts_skew`]
376 #[no_out]
377 fn ts_vskew<O: Vec1<U>, U>(
378 &self,
379 window: usize,
380 min_periods: Option<usize>,
381 out: Option<O::UninitRefMut<'_>>,
382 ) -> O
383 where
384 T::Inner: Number,
385 f64: Cast<U>,
386 {
387 let min_periods = min_periods.unwrap_or(window / 2).min(window).max(3);
388 let mut sum = 0.;
389 let mut sum2 = 0.;
390 let mut sum3 = 0.;
391 let mut n = 0;
392 self.rolling_apply(
393 window,
394 move |v_rm, v| {
395 if v.not_none() {
396 n += 1;
397 let v = v.unwrap().f64();
398 sum += v;
399 let v2 = v * v;
400 sum2 += v2;
401 sum3 += v2 * v;
402 }
403 let res = if n >= min_periods {
404 let n_f64 = n.f64();
405 let mut var = sum2 / n_f64;
406 let mut mean = sum / n_f64;
407 var -= mean.powi(2);
408 if var <= EPS {
409 // 标准差为0, 则偏度为0
410 0.
411 } else {
412 let std = var.sqrt(); // std
413 let res = sum3 / n_f64; // Ex^3
414 mean /= std; // mean / std
415 let adjust = (n * (n - 1)).f64().sqrt() / (n - 2).f64();
416 adjust * (res / std.powi(3) - 3. * mean - mean.powi(3))
417 }
418 } else {
419 f64::NAN
420 };
421 if let Some(v) = v_rm {
422 if v.not_none() {
423 let v = v.unwrap().f64();
424 n -= 1;
425 sum -= v;
426 let v2 = v * v;
427 sum2 -= v2;
428 sum3 -= v2 * v;
429 }
430 }
431 res.cast()
432 },
433 out,
434 )
435 }
436
437 /// Calculates the rolling kurtosis for the vector, handling None values.
438 ///
439 /// # Arguments
440 ///
441 /// * `window` - The size of the rolling window.
442 /// * `min_periods` - The minimum number of observations in window required to have a value.
443 /// * `out` - Optional output buffer to store the results.
444 ///
445 /// # Returns
446 ///
447 /// A vector containing the rolling kurtosis values.
448 ///
449 /// # See Also
450 ///
451 /// [`ts_kurt`](RollingFeature::ts_kurt) - The version of this function that doesn't handle None values.
452 #[no_out]
453 fn ts_vkurt<O: Vec1<U>, U>(
454 &self,
455 window: usize,
456 min_periods: Option<usize>,
457 out: Option<O::UninitRefMut<'_>>,
458 ) -> O
459 where
460 T::Inner: Number,
461 f64: Cast<U>,
462 {
463 let min_periods = min_periods.unwrap_or(window / 2).min(window).max(4);
464 let mut sum = 0.;
465 let mut sum2 = 0.;
466 let mut sum3 = 0.;
467 let mut sum4 = 0.;
468 let mut n = 0;
469 self.rolling_apply(
470 window,
471 move |v_rm, v| {
472 if v.not_none() {
473 n += 1;
474 let v = v.unwrap().f64();
475 sum += v;
476 let v2 = v * v;
477 sum2 += v2;
478 sum3 += v2 * v;
479 sum4 += v2 * v2;
480 }
481 let res = if n >= min_periods {
482 let n_f64 = n.f64();
483 let mut var = sum2 / n_f64;
484 let mean = sum / n_f64;
485 var -= mean.powi(2);
486 if var <= EPS {
487 // 标准差为0, 则峰度为0
488 0.
489 } else {
490 let n_f64 = n.f64();
491 let var2 = var * var; // var^2
492 let ex4 = sum4 / n_f64; // Ex^4
493 let ex3 = sum3 / n_f64; // Ex^3
494 let mean2_var = mean * mean / var; // (mean / std)^2
495 let out = (ex4 - 4. * mean * ex3) / var2
496 + 6. * mean2_var
497 + 3. * mean2_var.powi(2);
498 1. / ((n - 2) * (n - 3)).f64()
499 * ((n.pow(2) - 1).f64() * out - (3 * (n - 1).pow(2)).f64())
500 }
501 } else {
502 f64::NAN
503 };
504 if let Some(v) = v_rm {
505 if v.not_none() {
506 let v = v.unwrap().f64();
507 n -= 1;
508 sum -= v;
509 let v2 = v * v;
510 sum2 -= v2;
511 sum3 -= v2 * v;
512 sum4 -= v2 * v2;
513 }
514 }
515 res.cast()
516 },
517 out,
518 )
519 }
520}
521
522pub trait RollingFeature<T: Clone>: Vec1View<T> {
523 /// Calculates the rolling sum for the vector.
524 ///
525 /// # Arguments
526 ///
527 /// * `window` - The size of the rolling window.
528 /// * `min_periods` - The minimum number of observations in window required to have a value.
529 /// * `out` - Optional output buffer to store the results.
530 ///
531 /// # Returns
532 ///
533 /// A vector containing the rolling sum values.
534 ///
535 /// # See Also
536 ///
537 /// [`ts_vsum`](RollingValidFeature::ts_vsum) - The version of this function that handles None values.
538 #[no_out]
539 fn ts_sum<O: Vec1<U>, U>(
540 &self,
541 window: usize,
542 min_periods: Option<usize>,
543 out: Option<O::UninitRefMut<'_>>,
544 ) -> O
545 where
546 T: Number,
547 f64: Cast<U>,
548 {
549 let min_periods = min_periods.unwrap_or(window / 2).min(window);
550 let mut sum = T::zero();
551 let mut n = 0;
552 self.rolling_apply(
553 window,
554 move |v_rm, v| {
555 n += 1;
556 sum += v;
557 let res = if n >= min_periods {
558 sum.f64().cast()
559 } else {
560 f64::NAN.cast()
561 };
562 if let Some(v_rm) = v_rm {
563 n -= 1;
564 sum -= v_rm;
565 }
566 res
567 },
568 out,
569 )
570 }
571
572 /// Calculates the rolling mean for the vector.
573 ///
574 /// # Arguments
575 ///
576 /// * `window` - The size of the rolling window.
577 /// * `min_periods` - The minimum number of observations in window required to have a value.
578 /// * `out` - Optional output buffer to store the results.
579 ///
580 /// # Returns
581 ///
582 /// A vector containing the rolling mean values.
583 ///
584 /// # See Also
585 ///
586 /// [`ts_vmean`](RollingValidFeature::ts_vmean) - The version of this function that handles None values.
587 #[no_out]
588 fn ts_mean<O: Vec1<U>, U>(
589 &self,
590 window: usize,
591 min_periods: Option<usize>,
592 out: Option<O::UninitRefMut<'_>>,
593 ) -> O
594 where
595 T: Number,
596 f64: Cast<U>,
597 {
598 let min_periods = min_periods.unwrap_or(window / 2).min(window);
599 let mut sum = 0.;
600 let mut n = 0;
601 self.rolling_apply(
602 window,
603 move |v_rm, v| {
604 n += 1;
605 sum += v.f64();
606 let res = if n >= min_periods {
607 sum / n as f64
608 } else {
609 f64::NAN
610 };
611 if let Some(v_rm) = v_rm {
612 n -= 1;
613 sum -= v_rm.f64();
614 }
615 res.cast()
616 },
617 out,
618 )
619 }
620
621 /// Calculates the exponentially weighted moving average for the vector.
622 ///
623 /// # Arguments
624 ///
625 /// * `window` - The size of the rolling window.
626 /// * `min_periods` - The minimum number of observations in window required to have a value.
627 /// * `out` - Optional output buffer to store the results.
628 ///
629 /// # Returns
630 ///
631 /// A vector containing the exponentially weighted moving average values.
632 ///
633 /// # See Also
634 ///
635 /// [`ts_vewm`](RollingValidFeature::ts_vewm) - The version of this function that handles None values.
636 #[no_out]
637 fn ts_ewm<O: Vec1<U>, U>(
638 &self,
639 window: usize,
640 min_periods: Option<usize>,
641 out: Option<O::UninitRefMut<'_>>,
642 ) -> O
643 where
644 T: Number,
645 f64: Cast<U>,
646 {
647 let min_periods = min_periods.unwrap_or(window / 2).min(window);
648 // 错位相减核心公式:
649 // q_x(t) = 1 * new_element - alpha(q_x(t-1 without 1st element)) - 1st element * oma ^ (n-1)
650 let mut q_x = 0.; // 权重的分子部分 * 元素,使用错位相减法来计算
651 let alpha = 2. / window.f64();
652 let oma = 1. - alpha; // one minus alpha
653 let mut n = 0;
654 self.rolling_apply(
655 window,
656 move |v_rm, v| {
657 n += 1;
658 q_x += v.f64() - alpha * q_x.f64();
659 let res = if n >= min_periods {
660 q_x.f64() * alpha / (1. - oma.powi(n as i32))
661 } else {
662 f64::NAN
663 };
664 if let Some(v_rm) = v_rm {
665 n -= 1;
666 // 本应是window-1,不过本身window就要自然减一,调整一下顺序
667 q_x -= v_rm.f64() * oma.powi(n as i32);
668 }
669 res.cast()
670 },
671 out,
672 )
673 }
674
675 /// Calculates the weighted moving average for the vector.
676 ///
677 /// # Arguments
678 ///
679 /// * `window` - The size of the rolling window.
680 /// * `min_periods` - The minimum number of observations in window required to have a value.
681 /// * `out` - Optional output buffer to store the results.
682 ///
683 /// # Returns
684 ///
685 /// A vector containing the weighted moving average values.
686 ///
687 /// # See Also
688 ///
689 /// [`ts_vwma`](RollingValidFeature::ts_vwma) - The version of this function that handles None values.
690 #[no_out]
691 fn ts_wma<O: Vec1<U>, U>(
692 &self,
693 window: usize,
694 min_periods: Option<usize>,
695 out: Option<O::UninitRefMut<'_>>,
696 ) -> O
697 where
698 T: Number,
699 f64: Cast<U>,
700 {
701 let min_periods = min_periods.unwrap_or(window / 2).min(window);
702 let mut sum = 0.;
703 let mut sum_xt = 0.;
704 let mut n = 0;
705 self.rolling_apply(
706 window,
707 move |v_rm, v| {
708 n += 1;
709 let v = v.f64();
710 sum_xt += n.f64() * v; // 错位相减法, 忽略nan带来的系数和window不一致问题
711 sum += v;
712
713 let res = if n >= min_periods {
714 let divisor = (n * (n + 1)) >> 1;
715 sum_xt / divisor.f64()
716 } else {
717 f64::NAN
718 };
719 if let Some(v_rm) = v_rm {
720 n -= 1;
721 sum_xt -= sum;
722 sum -= v_rm.f64();
723 }
724 res.cast()
725 },
726 out,
727 )
728 }
729
730 /// Calculates the rolling standard deviation for the vector.
731 ///
732 /// # Arguments
733 ///
734 /// * `window` - The size of the rolling window.
735 /// * `min_periods` - The minimum number of observations in window required to have a value.
736 /// * `out` - Optional output buffer to store the results.
737 ///
738 /// # Returns
739 ///
740 /// A vector containing the rolling standard deviation values.
741 ///
742 /// # See Also
743 ///
744 /// [`ts_vstd`](RollingValidFeature::ts_vstd) - The version of this function that handles None values.
745 #[no_out]
746 fn ts_std<O: Vec1<U>, U>(
747 &self,
748 window: usize,
749 min_periods: Option<usize>,
750 out: Option<O::UninitRefMut<'_>>,
751 ) -> O
752 where
753 T: Number,
754 f64: Cast<U>,
755 {
756 let min_periods = min_periods.unwrap_or(window / 2).min(window).max(2);
757 let mut sum = 0.;
758 let mut sum2 = 0.;
759 let mut n = 0;
760 self.rolling_apply(
761 window,
762 move |v_rm, v| {
763 n += 1;
764 let v = v.f64();
765 sum += v;
766 sum2 += v * v;
767
768 let res = if n >= min_periods {
769 let n_f64 = n.f64();
770 let mut var = sum2 / n_f64;
771 let mean = sum / n_f64;
772 var -= mean.powi(2);
773 // variance should be greater than 0
774 if var > EPS {
775 (var * n_f64 / (n - 1).f64()).sqrt()
776 } else {
777 0.
778 }
779 } else {
780 f64::NAN
781 };
782 if let Some(v) = v_rm {
783 let v = v.f64();
784 n -= 1;
785 sum -= v;
786 sum2 -= v * v
787 }
788 res.cast()
789 },
790 out,
791 )
792 }
793
794 /// Calculates the rolling variance for the vector.
795 ///
796 /// # Arguments
797 ///
798 /// * `window` - The size of the rolling window.
799 /// * `min_periods` - The minimum number of observations in window required to have a value.
800 /// * `out` - Optional output buffer to store the results.
801 ///
802 /// # Returns
803 ///
804 /// A vector containing the rolling variance values.
805 ///
806 /// # See Also
807 ///
808 /// [`ts_vvar`](RollingValidFeature::ts_vvar) - The version of this function that handles None values.
809 #[no_out]
810 fn ts_var<O: Vec1<U>, U>(
811 &self,
812 window: usize,
813 min_periods: Option<usize>,
814 out: Option<O::UninitRefMut<'_>>,
815 ) -> O
816 where
817 T: Number,
818 f64: Cast<U>,
819 {
820 let min_periods = min_periods.unwrap_or(window / 2).min(window).max(2);
821 let mut sum = 0.;
822 let mut sum2 = 0.;
823 let mut n = 0;
824 self.rolling_apply(
825 window,
826 move |v_rm, v| {
827 n += 1;
828 let v = v.f64();
829 sum += v;
830 sum2 += v * v;
831
832 let res = if n >= min_periods {
833 let n_f64 = n.f64();
834 let mut var = sum2 / n_f64;
835 let mean = sum / n_f64;
836 var -= mean.powi(2);
837 // variance should be greater than 0
838 if var > EPS {
839 var * n_f64 / (n - 1).f64()
840 } else {
841 0.
842 }
843 } else {
844 f64::NAN
845 };
846 if let Some(v) = v_rm {
847 let v = v.f64();
848 n -= 1;
849 sum -= v;
850 sum2 -= v * v
851 }
852 res.cast()
853 },
854 out,
855 )
856 }
857
858 /// Calculates the rolling skewness for the vector.
859 ///
860 /// # Arguments
861 ///
862 /// * `window` - The size of the rolling window.
863 /// * `min_periods` - The minimum number of observations in window required to have a value.
864 /// * `out` - Optional output buffer to store the results.
865 ///
866 /// # Returns
867 ///
868 /// A vector containing the rolling skewness values.
869 ///
870 /// # See Also
871 ///
872 /// [`ts_vskew`](RollingValidFeature::ts_vskew) - The version of this function that handles None values.
873 #[no_out]
874 fn ts_skew<O: Vec1<U>, U>(
875 &self,
876 window: usize,
877 min_periods: Option<usize>,
878 out: Option<O::UninitRefMut<'_>>,
879 ) -> O
880 where
881 T: Number,
882 f64: Cast<U>,
883 {
884 let min_periods = min_periods.unwrap_or(window / 2).min(window).max(3);
885 let mut sum = 0.;
886 let mut sum2 = 0.;
887 let mut sum3 = 0.;
888 let mut n = 0;
889 self.rolling_apply(
890 window,
891 move |v_rm, v| {
892 n += 1;
893 let v = v.f64();
894 sum += v;
895 let v2 = v * v;
896 sum2 += v2;
897 sum3 += v2 * v;
898
899 let res = if n >= min_periods {
900 let n_f64 = n.f64();
901 let mut var = sum2 / n_f64;
902 let mut mean = sum / n_f64;
903 var -= mean.powi(2);
904 if var <= EPS {
905 // 标准差为0, 则偏度为0
906 0.
907 } else {
908 let std = var.sqrt(); // std
909 let res = sum3 / n_f64; // Ex^3
910 mean /= std; // mean / std
911 let adjust = (n * (n - 1)).f64().sqrt() / (n - 2).f64();
912 adjust * (res / std.powi(3) - 3. * mean - mean.powi(3))
913 }
914 } else {
915 f64::NAN
916 };
917 if let Some(v) = v_rm {
918 let v = v.f64();
919 n -= 1;
920 sum -= v;
921 let v2 = v * v;
922 sum2 -= v2;
923 sum3 -= v2 * v;
924 }
925 res.cast()
926 },
927 out,
928 )
929 }
930
931 /// Calculates the rolling kurtosis for the vector.
932 ///
933 /// # Arguments
934 ///
935 /// * `window` - The size of the rolling window.
936 /// * `min_periods` - The minimum number of observations in window required to have a value.
937 /// * `out` - Optional output buffer to store the results.
938 ///
939 /// # Returns
940 ///
941 /// A vector containing the rolling kurtosis values.
942 ///
943 /// # See Also
944 ///
945 /// [`ts_vkurt`](RollingValidFeature::ts_vkurt) - The version of this function that handles None values.
946 #[no_out]
947 fn ts_kurt<O: Vec1<U>, U>(
948 &self,
949 window: usize,
950 min_periods: Option<usize>,
951 out: Option<O::UninitRefMut<'_>>,
952 ) -> O
953 where
954 T: Number,
955 f64: Cast<U>,
956 {
957 // let window = window.min(self.len());
958 let min_periods = min_periods.unwrap_or(window / 2).min(window).max(4);
959 let mut sum = 0.;
960 let mut sum2 = 0.;
961 let mut sum3 = 0.;
962 let mut sum4 = 0.;
963 let mut n = 0;
964 self.rolling_apply(
965 window,
966 move |v_rm, v| {
967 n += 1;
968 let v = v.f64();
969 sum += v;
970 let v2 = v * v;
971 sum2 += v2;
972 sum3 += v2 * v;
973 sum4 += v2 * v2;
974
975 let res = if n >= min_periods {
976 let n_f64 = n.f64();
977 let mut var = sum2 / n_f64;
978 let mean = sum / n_f64;
979 var -= mean.powi(2);
980 if var <= EPS {
981 // 标准差为0, 则峰度为0
982 0.
983 } else {
984 let n_f64 = n.f64();
985 let var2 = var * var; // var^2
986 let ex4 = sum4 / n_f64; // Ex^4
987 let ex3 = sum3 / n_f64; // Ex^3
988 let mean2_var = mean * mean / var; // (mean / std)^2
989 let out = (ex4 - 4. * mean * ex3) / var2
990 + 6. * mean2_var
991 + 3. * mean2_var.powi(2);
992 1. / ((n - 2) * (n - 3)).f64()
993 * ((n.pow(2) - 1).f64() * out - (3 * (n - 1).pow(2)).f64())
994 }
995 } else {
996 f64::NAN
997 };
998 if let Some(v) = v_rm {
999 let v = v.f64();
1000 n -= 1;
1001 sum -= v;
1002 let v2 = v * v;
1003 sum2 -= v2;
1004 sum3 -= v2 * v;
1005 sum4 -= v2 * v2;
1006 }
1007 res.cast()
1008 },
1009 out,
1010 )
1011 }
1012}
1013
1014impl<T: Clone, I: Vec1View<T>> RollingFeature<T> for I {}
1015
1016impl<T: IsNone, I: Vec1View<T>> RollingValidFeature<T> for I {}
1017
1018#[cfg(test)]
1019mod tests {
1020 use tea_core::testing::*;
1021
1022 use super::*;
1023 #[test]
1024 fn test_ts_sum() {
1025 // test empty iter
1026 let data: Vec<i32> = vec![];
1027 let sum: Vec<f64> = data.ts_sum(3, Some(1));
1028 let sum2: Vec<f64> = data.ts_vsum(3, None);
1029 assert!(sum.is_empty());
1030 assert!(sum2.is_empty());
1031
1032 // test when window is greater than length
1033 let data = vec![1, 2, 3];
1034 let sum: Vec<f64> = data.ts_sum(5, Some(1));
1035 let sum2: Vec<f64> = data.ts_vsum(5, Some(1));
1036 assert_eq!(sum, vec![1., 3., 6.]);
1037 assert_eq!(sum2, vec![1., 3., 6.]);
1038
1039 // test sum
1040 let data = vec![1, 2, 3, 4, 5];
1041 let sum: Vec<f64> = data.ts_sum(3, Some(1));
1042 let sum2: Vec<f64> = data.ts_vsum(3, Some(1));
1043 assert_eq!(sum, vec![1., 3., 6., 9., 12.]);
1044 assert_eq!(sum2, vec![1., 3., 6., 9., 12.]);
1045 // test valid sum
1046 let sum2: Vec<Option<f64>> = data.opt().ts_vsum(3, Some(3));
1047 assert_eq!(sum2, vec![None, None, Some(6.), Some(9.), Some(12.)]);
1048
1049 let data = vec![Some(1.), Some(2.), None, Some(4.), Some(5.)];
1050 let sum: Vec<Option<i32>> = data.ts_vsum(3, Some(1));
1051 assert_eq!(sum, vec![Some(1), Some(3), Some(3), Some(6), Some(9)]);
1052 }
1053
1054 #[test]
1055 fn test_ts_mean() {
1056 let data = vec![1, 2, 3, 4, 5];
1057 let mean: Vec<_> = data.ts_mean(3, Some(1));
1058 assert_vec1d_equal_numeric(&mean, &vec![1., 1.5, 2., 3., 4.], None);
1059 let data = vec![1., f64::NAN, 3., 4., 5.];
1060 let out: Vec<_> = data.ts_mean(2, Some(1));
1061 assert_vec1d_equal_numeric(
1062 &out,
1063 &vec![1., f64::NAN, f64::NAN, f64::NAN, f64::NAN],
1064 None,
1065 );
1066 let out2: Vec<f64> = data.ts_vmean(2, Some(1));
1067 let out3: Vec<Option<f64>> = data.opt().ts_vmean(2, Some(1));
1068 let expect = vec![Some(1.), Some(1.), Some(3.), Some(3.5), Some(4.5)];
1069 assert_eq!(out2, vec![1., 1., 3., 3.5, 4.5]);
1070 assert_eq!(out3, expect);
1071
1072 let out: Vec<_> = data.opt().ts_vmean(2, Some(2));
1073 assert_vec1d_equal_numeric(&out, &vec![None, None, None, Some(3.5), Some(4.5)], None)
1074 }
1075}