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}