tea_rolling/
reg.rs

1use tea_core::prelude::*;
2/// Trait for rolling window regression operations on valid (non-None) elements.
3pub trait RollingValidReg<T: IsNone>: Vec1View<T> {
4    /// Calculates the rolling regression (predicted value) for valid elements within a window.
5    ///
6    /// # Arguments
7    ///
8    /// * `window` - The size of the rolling window.
9    /// * `min_periods` - The minimum number of observations in window required to have a value.
10    /// * `out` - Optional output buffer to store the results.
11    ///
12    /// # Returns
13    ///
14    /// A vector containing the rolling regression predicted values.
15    #[no_out]
16    fn ts_vreg<O: Vec1<U>, U>(
17        &self,
18        window: usize,
19        min_periods: Option<usize>,
20        out: Option<O::UninitRefMut<'_>>,
21    ) -> O
22    where
23        T::Inner: Number,
24        f64: Cast<U>,
25    {
26        let min_periods = min_periods.unwrap_or(window / 2).min(window);
27        let mut sum = 0.;
28        let mut sum_xt = 0.;
29        let mut n = 0;
30        self.rolling_apply(
31            window,
32            move |v_rm, v| {
33                if v.not_none() {
34                    let v = v.unwrap().f64();
35                    n += 1;
36                    sum_xt += n.f64() * v; // 错位相减法, 忽略nan带来的系数和window不一致问题
37                    sum += v;
38                }
39                let res = if n >= min_periods {
40                    let n_f64 = n.f64();
41                    let nn_add_n = n.mul_add(n, n);
42                    let sum_t = (nn_add_n >> 1).f64(); // sum of time from 1 to window
43                    // denominator of slope
44                    let divisor = (n * nn_add_n * n.mul_add(2, 1)).f64() / 6. - sum_t.powi(2);
45                    let slope = (n_f64 * sum_xt - sum_t * sum) / divisor;
46                    let intercept = sum_t.mul_add(-slope, sum) / n_f64;
47                    slope.mul_add(n_f64, intercept)
48                } else {
49                    f64::NAN
50                };
51                if let Some(v_rm) = v_rm {
52                    if v_rm.not_none() {
53                        n -= 1;
54                        sum_xt -= sum;
55                        sum -= v_rm.unwrap().f64();
56                    }
57                }
58                res.cast()
59            },
60            out,
61        )
62    }
63
64    /// Calculates the rolling time series forecast for valid elements within a window.
65    ///
66    /// # Arguments
67    ///
68    /// * `window` - The size of the rolling window.
69    /// * `min_periods` - The minimum number of observations in window required to have a value.
70    /// * `out` - Optional output buffer to store the results.
71    ///
72    /// # Returns
73    ///
74    /// A vector containing the rolling time series forecast values.
75    #[no_out]
76    fn ts_vtsf<O: Vec1<U>, U>(
77        &self,
78        window: usize,
79        min_periods: Option<usize>,
80        out: Option<O::UninitRefMut<'_>>,
81    ) -> O
82    where
83        T::Inner: Number,
84        f64: Cast<U>,
85    {
86        let min_periods = min_periods.unwrap_or(window / 2).min(window);
87        let mut sum = 0.;
88        let mut sum_xt = 0.;
89        let mut n = 0;
90        self.rolling_apply(
91            window,
92            move |v_rm, v| {
93                if v.not_none() {
94                    let v = v.unwrap().f64();
95                    n += 1;
96                    sum_xt += n.f64() * v; // 错位相减法, 忽略nan带来的系数和window不一致问题
97                    sum += v;
98                }
99                let res = if n >= min_periods {
100                    let n_f64 = n.f64();
101                    let nn_add_n = n.mul_add(n, n);
102                    let sum_t = (nn_add_n >> 1).f64(); // sum of time from 1 to window
103                    // denominator of slope
104                    let divisor = (n * nn_add_n * n.mul_add(2, 1)).f64() / 6. - sum_t.powi(2);
105                    let slope = (n_f64 * sum_xt - sum_t * sum) / divisor;
106                    let intercept = sum_t.mul_add(-slope, sum) / n_f64;
107                    slope.mul_add((n + 1).f64(), intercept)
108                } else {
109                    f64::NAN
110                };
111                if let Some(v_rm) = v_rm {
112                    if v_rm.not_none() {
113                        n -= 1;
114                        sum_xt -= sum;
115                        sum -= v_rm.unwrap().f64();
116                    }
117                }
118                res.cast()
119            },
120            out,
121        )
122    }
123
124    /// Calculates the rolling regression slope for valid elements within a window.
125    ///
126    /// # Arguments
127    ///
128    /// * `window` - The size of the rolling window.
129    /// * `min_periods` - The minimum number of observations in window required to have a value.
130    /// * `out` - Optional output buffer to store the results.
131    ///
132    /// # Returns
133    ///
134    /// A vector containing the rolling regression slope values.
135    #[no_out]
136    fn ts_vreg_slope<O: Vec1<U>, U>(
137        &self,
138        window: usize,
139        min_periods: Option<usize>,
140        out: Option<O::UninitRefMut<'_>>,
141    ) -> O
142    where
143        T::Inner: Number,
144        f64: Cast<U>,
145    {
146        let min_periods = min_periods.unwrap_or(window / 2).min(window);
147        let mut sum = 0.;
148        let mut sum_xt = 0.;
149        let mut n = 0;
150        self.rolling_apply(
151            window,
152            move |v_rm, v| {
153                if v.not_none() {
154                    let v = v.unwrap().f64();
155                    n += 1;
156                    sum_xt += n.f64() * v; // 错位相减法, 忽略nan带来的系数和window不一致问题
157                    sum += v;
158                }
159                let res = if n >= min_periods {
160                    let n_f64 = n.f64();
161                    let nn_add_n = n.mul_add(n, n);
162                    let sum_t = (nn_add_n >> 1).f64(); // sum of time from 1 to window
163                    // denominator of slope
164                    let divisor = (n * nn_add_n * n.mul_add(2, 1)).f64() / 6. - sum_t.powi(2);
165                    (n_f64 * sum_xt - sum_t * sum) / divisor
166                } else {
167                    f64::NAN
168                };
169                if let Some(v_rm) = v_rm {
170                    if v_rm.not_none() {
171                        n -= 1;
172                        sum_xt -= sum;
173                        sum -= v_rm.unwrap().f64();
174                    }
175                }
176                res.cast()
177            },
178            out,
179        )
180    }
181
182    /// Calculates the rolling regression intercept for valid elements within a window.
183    ///
184    /// # Arguments
185    ///
186    /// * `window` - The size of the rolling window.
187    /// * `min_periods` - The minimum number of observations in window required to have a value.
188    /// * `out` - Optional output buffer to store the results.
189    ///
190    /// # Returns
191    ///
192    /// A vector containing the rolling regression intercept values.
193    #[no_out]
194    fn ts_vreg_intercept<O: Vec1<U>, U>(
195        &self,
196        window: usize,
197        min_periods: Option<usize>,
198        out: Option<O::UninitRefMut<'_>>,
199    ) -> O
200    where
201        T::Inner: Number,
202        f64: Cast<U>,
203    {
204        let min_periods = min_periods.unwrap_or(window / 2).min(window);
205        let mut sum = 0.;
206        let mut sum_xt = 0.;
207        let mut n = 0;
208        self.rolling_apply(
209            window,
210            move |v_rm, v| {
211                if v.not_none() {
212                    let v = v.unwrap().f64();
213                    n += 1;
214                    sum_xt += n.f64() * v; // 错位相减法, 忽略nan带来的系数和window不一致问题
215                    sum += v;
216                }
217                let res = if n >= min_periods {
218                    let n_f64 = n.f64();
219                    let nn_add_n = n.mul_add(n, n);
220                    let sum_t = (nn_add_n >> 1).f64(); // sum of time from 1 to window
221                    // denominator of slope
222                    let divisor = (n * nn_add_n * n.mul_add(2, 1)).f64() / 6. - sum_t.powi(2);
223                    let slope = (n_f64 * sum_xt - sum_t * sum) / divisor;
224                    sum_t.mul_add(-slope, sum) / n_f64
225                } else {
226                    f64::NAN
227                };
228                if let Some(v_rm) = v_rm {
229                    if v_rm.not_none() {
230                        n -= 1;
231                        sum_xt -= sum;
232                        sum -= v_rm.unwrap().f64();
233                    }
234                }
235                res.cast()
236            },
237            out,
238        )
239    }
240
241    /// Calculates the rolling regression residual mean for valid elements within a window.
242    ///
243    /// # Arguments
244    ///
245    /// * `window` - The size of the rolling window.
246    /// * `min_periods` - The minimum number of observations in window required to have a value.
247    /// * `out` - Optional output buffer to store the results.
248    ///
249    /// # Returns
250    ///
251    /// A vector containing the rolling regression residual mean values.
252    #[no_out]
253    fn ts_vreg_resid_mean<O: Vec1<U>, U>(
254        &self,
255        window: usize,
256        min_periods: Option<usize>,
257        out: Option<O::UninitRefMut<'_>>,
258    ) -> O
259    where
260        T::Inner: Number,
261        f64: Cast<U>,
262    {
263        let min_periods = min_periods.unwrap_or(window / 2).min(window);
264        let mut sum = 0.;
265        let mut sum_xx = 0.;
266        let mut sum_xt = 0.;
267        let mut n = 0;
268        self.rolling_apply(
269            window,
270            move |v_rm, v| {
271                if v.not_none() {
272                    let v = v.unwrap().f64();
273                    n += 1;
274                    sum_xt += n.f64() * v; // 错位相减法, 忽略nan带来的系数和window不一致问题
275                    sum += v;
276                    sum_xx += v * v;
277                }
278                let res = if n >= min_periods {
279                    let n_f64 = n.f64();
280                    let nn_add_n = n.mul_add(n, n);
281                    let sum_t = (nn_add_n >> 1).f64(); // sum of time from 1 to window
282                    // denominator of slope
283                    let sum_tt = (n * nn_add_n * n.mul_add(2, 1)).f64() / 6.;
284                    let divisor = sum_tt - sum_t.powi(2);
285                    let beta = (n_f64 * sum_xt - sum_t * sum) / divisor;
286                    let alpha = sum_t.mul_add(-beta, sum) / n_f64;
287                    let resid_sum = sum_xx - 2. * alpha * sum - 2. * beta * sum_xt
288                        + alpha * alpha * n_f64
289                        + 2. * alpha * beta * sum_t
290                        + beta * beta * sum_tt;
291                    resid_sum / n_f64
292                } else {
293                    f64::NAN
294                };
295                if let Some(v_rm) = v_rm {
296                    if v_rm.not_none() {
297                        let v_rm = v_rm.unwrap().f64();
298                        n -= 1;
299                        sum_xt -= sum;
300                        sum -= v_rm;
301                        sum_xx -= v_rm * v_rm;
302                    }
303                }
304                res.cast()
305            },
306            out,
307        )
308    }
309}
310
311/// Trait for rolling window regression operations on valid (non-None) elements with two input vectors.
312pub trait RollingValidRegBinary<T: IsNone>: Vec1View<T> {
313    /// Calculates the rolling regression alpha (intercept) for valid elements within a window.
314    ///
315    /// # Arguments
316    ///
317    /// * `other` - The second input vector for the regression.
318    /// * `window` - The size of the rolling window.
319    /// * `min_periods` - The minimum number of observations in window required to have a value.
320    /// * `out` - Optional output buffer to store the results.
321    ///
322    /// # Returns
323    ///
324    /// A vector containing the rolling regression alpha values.
325    #[no_out]
326    fn ts_vregx_alpha<O: Vec1<U>, U, V2: Vec1View<T2>, T2: IsNone>(
327        &self,
328        other: &V2,
329        window: usize,
330        min_periods: Option<usize>,
331        out: Option<O::UninitRefMut<'_>>,
332    ) -> O
333    where
334        T::Inner: Number,
335        T2::Inner: Number,
336        f64: Cast<U>,
337    {
338        let min_periods = min_periods.unwrap_or(window / 2).min(window);
339        let mut sum_a = 0.;
340        let mut sum_b = 0.;
341        let mut sum_b2 = 0.;
342        let mut sum_ab = 0.;
343        let mut n = 0;
344        self.rolling2_apply(
345            other,
346            window,
347            |remove_values, (va, vb)| {
348                if va.not_none() && vb.not_none() {
349                    n += 1;
350                    let (va, vb) = (va.unwrap().f64(), vb.unwrap().f64());
351                    sum_a += va;
352                    sum_b += vb;
353                    sum_b2 += vb * vb;
354                    sum_ab += va * vb;
355                };
356                let res = if n >= min_periods {
357                    // β = (n Σxy - Σx Σy) / (n Σx² - (Σx)²)
358                    let beta =
359                        (n.f64() * sum_ab - sum_a * sum_b) / (n.f64() * sum_b2 - sum_b.powi(2));
360                    // α = (Σy - β Σx) / n
361                    (sum_a - beta * sum_b) / n.f64()
362                } else {
363                    f64::NAN
364                };
365                if let Some((va, vb)) = remove_values {
366                    if va.not_none() && vb.not_none() {
367                        n -= 1;
368                        let (va, vb) = (va.unwrap().f64(), vb.unwrap().f64());
369                        sum_a -= va;
370                        sum_b -= vb;
371                        sum_b2 -= vb * vb;
372                        sum_ab -= va * vb;
373                    };
374                }
375                res.cast()
376            },
377            out,
378        )
379    }
380
381    /// Calculates the rolling regression beta (slope) for valid elements within a window.
382    ///
383    /// # Arguments
384    ///
385    /// * `other` - The second input vector for the regression.
386    /// * `window` - The size of the rolling window.
387    /// * `min_periods` - The minimum number of observations in window required to have a value.
388    /// * `out` - Optional output buffer to store the results.
389    ///
390    /// # Returns
391    ///
392    /// A vector containing the rolling regression beta values.
393    #[no_out]
394    fn ts_vregx_beta<O: Vec1<U>, U, V2: Vec1View<T2>, T2: IsNone>(
395        &self,
396        other: &V2,
397        window: usize,
398        min_periods: Option<usize>,
399        out: Option<O::UninitRefMut<'_>>,
400    ) -> O
401    where
402        T::Inner: Number,
403        T2::Inner: Number,
404        f64: Cast<U>,
405    {
406        let min_periods = min_periods.unwrap_or(window / 2).min(window);
407        let mut sum_a = 0.;
408        let mut sum_b = 0.;
409        let mut sum_b2 = 0.;
410        let mut sum_ab = 0.;
411        let mut n = 0;
412        self.rolling2_apply(
413            other,
414            window,
415            |remove_values, (va, vb)| {
416                if va.not_none() && vb.not_none() {
417                    n += 1;
418                    let (va, vb) = (va.unwrap().f64(), vb.unwrap().f64());
419                    sum_a += va;
420                    sum_b += vb;
421                    sum_b2 += vb * vb;
422                    sum_ab += va * vb;
423                };
424                let res = if n >= min_periods {
425                    // β = (n Σxy - Σx Σy) / (n Σx² - (Σx)²)
426                    (n.f64() * sum_ab - sum_a * sum_b) / (n.f64() * sum_b2 - sum_b.powi(2))
427                } else {
428                    f64::NAN
429                };
430                if let Some((va, vb)) = remove_values {
431                    if va.not_none() && vb.not_none() {
432                        n -= 1;
433                        let (va, vb) = (va.unwrap().f64(), vb.unwrap().f64());
434                        sum_a -= va;
435                        sum_b -= vb;
436                        sum_b2 -= vb * vb;
437                        sum_ab -= va * vb;
438                    };
439                }
440                res.cast()
441            },
442            out,
443        )
444    }
445
446    /// Calculates the rolling mean of regression residuals for valid elements within a window.
447    ///
448    /// # Arguments
449    ///
450    /// * `other` - The second input vector for the regression.
451    /// * `window` - The size of the rolling window.
452    /// * `min_periods` - The minimum number of observations in window required to have a value.
453    /// * `out` - Optional output buffer to store the results.
454    ///
455    /// # Returns
456    ///
457    /// A vector containing the rolling mean of regression residuals.
458    #[no_out]
459    fn ts_vregx_resid_mean<O: Vec1<U>, U, V2: Vec1View<T2>, T2: IsNone>(
460        &self,
461        other: &V2,
462        window: usize,
463        min_periods: Option<usize>,
464        out: Option<O::UninitRefMut<'_>>,
465    ) -> O
466    where
467        T::Inner: Number,
468        T2::Inner: Number,
469        f64: Cast<U>,
470    {
471        let min_periods = min_periods.unwrap_or(window / 2).min(window);
472        let mut sum_a = 0.;
473        let mut sum_b = 0.;
474        let mut sum_b2 = 0.;
475        let mut sum_ab = 0.;
476        let mut n = 0;
477        self.rolling2_apply_idx(
478            other,
479            window,
480            |start, end, (va, vb)| {
481                if va.not_none() && vb.not_none() {
482                    n += 1;
483                    let (va, vb) = (va.unwrap().f64(), vb.unwrap().f64());
484                    sum_a += va;
485                    sum_b += vb;
486                    sum_b2 += vb * vb;
487                    sum_ab += va * vb;
488                };
489                let res = if n >= min_periods {
490                    let beta =
491                        (n.f64() * sum_ab - sum_a * sum_b) / (n.f64() * sum_b2 - sum_b.powi(2));
492                    let alpha = (sum_a - beta * sum_b) / n.f64();
493                    (start.unwrap_or(0)..=end)
494                        .map(|j| {
495                            let (vy, vx) = unsafe { (self.uget(j), other.uget(j)) };
496                            if vy.not_none() && vx.not_none() {
497                                vy.unwrap().f64() - alpha - beta * vx.unwrap().f64()
498                            } else {
499                                f64::NAN
500                            }
501                        })
502                        .vmean()
503                } else {
504                    f64::NAN
505                };
506                if let Some(start) = start {
507                    let (va, vb) = unsafe { (self.uget(start), other.uget(start)) };
508                    if va.not_none() && vb.not_none() {
509                        n -= 1;
510                        let (va, vb) = (va.unwrap().f64(), vb.unwrap().f64());
511                        sum_a -= va;
512                        sum_b -= vb;
513                        sum_b2 -= vb * vb;
514                        sum_ab -= va * vb;
515                    };
516                }
517                res.cast()
518            },
519            out,
520        )
521    }
522
523    /// Calculates the rolling standard deviation of regression residuals for valid elements within a window.
524    ///
525    /// # Arguments
526    ///
527    /// * `other` - The second input vector for the regression.
528    /// * `window` - The size of the rolling window.
529    /// * `min_periods` - The minimum number of observations in window required to have a value.
530    /// * `out` - Optional output buffer to store the results.
531    ///
532    /// # Returns
533    ///
534    /// A vector containing the rolling standard deviation of regression residuals.
535    #[no_out]
536    fn ts_vregx_resid_std<O: Vec1<U>, U, V2: Vec1View<T2>, T2: IsNone>(
537        &self,
538        other: &V2,
539        window: usize,
540        min_periods: Option<usize>,
541        out: Option<O::UninitRefMut<'_>>,
542    ) -> O
543    where
544        T::Inner: Number,
545        T2::Inner: Number,
546        f64: Cast<U>,
547    {
548        let min_periods = min_periods.unwrap_or(window / 2).min(window);
549        let mut sum_a = 0.;
550        let mut sum_b = 0.;
551        let mut sum_b2 = 0.;
552        let mut sum_ab = 0.;
553        let mut n = 0;
554        self.rolling2_apply_idx(
555            other,
556            window,
557            |start, end, (va, vb)| {
558                if va.not_none() && vb.not_none() {
559                    n += 1;
560                    let (va, vb) = (va.unwrap().f64(), vb.unwrap().f64());
561                    sum_a += va;
562                    sum_b += vb;
563                    sum_b2 += vb * vb;
564                    sum_ab += va * vb;
565                };
566                let res = if n >= min_periods {
567                    let beta =
568                        (n.f64() * sum_ab - sum_a * sum_b) / (n.f64() * sum_b2 - sum_b.powi(2));
569                    let alpha = (sum_a - beta * sum_b) / n.f64();
570                    (start.unwrap_or(0)..=end)
571                        .map(|j| {
572                            let (vy, vx) = unsafe { (self.uget(j), other.uget(j)) };
573                            if vy.not_none() && vx.not_none() {
574                                vy.unwrap().f64() - alpha - beta * vx.unwrap().f64()
575                            } else {
576                                f64::NAN
577                            }
578                        })
579                        .vstd(2)
580                } else {
581                    f64::NAN
582                };
583                if let Some(start) = start {
584                    let (va, vb) = unsafe { (self.uget(start), other.uget(start)) };
585                    if va.not_none() && vb.not_none() {
586                        n -= 1;
587                        let (va, vb) = (va.unwrap().f64(), vb.unwrap().f64());
588                        sum_a -= va;
589                        sum_b -= vb;
590                        sum_b2 -= vb * vb;
591                        sum_ab -= va * vb;
592                    };
593                }
594                res.cast()
595            },
596            out,
597        )
598    }
599
600    /// Calculates the rolling skewness of regression residuals for valid elements within a window.
601    ///
602    /// # Arguments
603    ///
604    /// * `other` - The second input vector for the regression.
605    /// * `window` - The size of the rolling window.
606    /// * `min_periods` - The minimum number of observations in window required to have a value.
607    /// * `out` - Optional output buffer to store the results.
608    ///
609    /// # Returns
610    ///
611    /// A vector containing the rolling skewness of regression residuals.
612    #[no_out]
613    fn ts_vregx_resid_skew<O: Vec1<U>, U, V2: Vec1View<T2>, T2: IsNone>(
614        &self,
615        other: &V2,
616        window: usize,
617        min_periods: Option<usize>,
618        out: Option<O::UninitRefMut<'_>>,
619    ) -> O
620    where
621        T::Inner: Number,
622        T2::Inner: Number,
623        f64: Cast<U>,
624    {
625        let min_periods = min_periods.unwrap_or(window / 2).min(window);
626        let mut sum_a = 0.;
627        let mut sum_b = 0.;
628        let mut sum_b2 = 0.;
629        let mut sum_ab = 0.;
630        let mut n = 0;
631        self.rolling2_apply_idx(
632            other,
633            window,
634            |start, end, (va, vb)| {
635                if va.not_none() && vb.not_none() {
636                    n += 1;
637                    let (va, vb) = (va.unwrap().f64(), vb.unwrap().f64());
638                    sum_a += va;
639                    sum_b += vb;
640                    sum_b2 += vb * vb;
641                    sum_ab += va * vb;
642                };
643                let res = if n >= min_periods {
644                    let beta =
645                        (n.f64() * sum_ab - sum_a * sum_b) / (n.f64() * sum_b2 - sum_b.powi(2));
646                    let alpha = (sum_a - beta * sum_b) / n.f64();
647                    (start.unwrap_or(0)..=end)
648                        .map(|j| {
649                            let (vy, vx) = unsafe { (self.uget(j), other.uget(j)) };
650                            if vy.not_none() && vx.not_none() {
651                                vy.unwrap().f64() - alpha - beta * vx.unwrap().f64()
652                            } else {
653                                f64::NAN
654                            }
655                        })
656                        .vskew(3)
657                } else {
658                    f64::NAN
659                };
660                if let Some(start) = start {
661                    let (va, vb) = unsafe { (self.uget(start), other.uget(start)) };
662                    if va.not_none() && vb.not_none() {
663                        n -= 1;
664                        let (va, vb) = (va.unwrap().f64(), vb.unwrap().f64());
665                        sum_a -= va;
666                        sum_b -= vb;
667                        sum_b2 -= vb * vb;
668                        sum_ab -= va * vb;
669                    };
670                }
671                res.cast()
672            },
673            out,
674        )
675    }
676
677    /// Calculates rolling regression statistics for two vectors.
678    ///
679    /// This function computes rolling regression statistics (alpha, beta, and sum of squared errors)
680    /// for two input vectors over a specified window size.
681    ///
682    /// # Arguments
683    ///
684    /// * `other` - The second input vector for regression.
685    /// * `window` - The size of the rolling window.
686    /// * `min_periods` - The minimum number of observations required to have a value; defaults to `window / 2`.
687    ///
688    /// # Type Parameters
689    ///
690    /// * `O` - The output vector type, must implement `Vec1<(U, U, U)>`.
691    /// * `U` - The type of the output elements.
692    /// * `V2` - The type of the second input vector, must implement `Vec1View<T2>`.
693    /// * `T2` - The element type of the second input vector, must implement `IsNone`.
694    ///
695    /// # Returns
696    ///
697    /// Returns a vector of tuples `(alpha, beta, sse)` where:
698    /// * `alpha` is the y-intercept of the regression line.
699    /// * `beta` is the slope of the regression line.
700    /// * `sse` is the sum of squared errors.
701    ///
702    /// # Notes
703    ///
704    /// - The function uses a rolling window approach to calculate regression statistics.
705    /// - NaN values are returned for windows with insufficient observations.
706    /// - The calculation assumes that `T::Inner` and `T2::Inner` implement `Number`.
707    /// - The output type `U` must be able to be cast from `f64`.
708    fn ts_vregx_all<O: Vec1<(U, U, U)>, U, V2: Vec1View<T2>, T2: IsNone>(
709        &self,
710        other: &V2,
711        window: usize,
712        min_periods: Option<usize>,
713    ) -> O
714    where
715        T::Inner: Number,
716        T2::Inner: Number,
717        f64: Cast<U>,
718    {
719        let min_periods = min_periods.unwrap_or(window / 2).min(window);
720        let mut sum_a = 0.;
721        let mut sum_b = 0.;
722        let mut sum_b2 = 0.;
723        let mut sum_ab = 0.;
724        let mut sum_a2 = 0.;
725        let mut n = 0;
726        self.rolling2_apply(
727            other,
728            window,
729            |remove_values, (va, vb)| {
730                if va.not_none() && vb.not_none() {
731                    n += 1;
732                    let (va, vb) = (va.unwrap().f64(), vb.unwrap().f64());
733                    sum_a += va;
734                    sum_a2 += va * va;
735                    sum_b += vb;
736                    sum_b2 += vb * vb;
737                    sum_ab += va * vb;
738                };
739                let (alpha, beta, sse) = if n >= min_periods {
740                    // β = (n Σxy - Σx Σy) / (n Σx² - (Σx)²)
741                    let beta =
742                        (n.f64() * sum_ab - sum_a * sum_b) / (n.f64() * sum_b2 - sum_b.powi(2));
743                    // α = (Σy - β Σx) / n
744                    let alpha = (sum_a - beta * sum_b) / n.f64();
745                    // SSE = Σy² - α Σy - β Σxy
746                    let sse = sum_a2 - alpha * sum_a - beta * sum_ab;
747                    (alpha, beta, sse)
748                } else {
749                    (f64::NAN, f64::NAN, f64::NAN)
750                };
751                if let Some((va, vb)) = remove_values {
752                    if va.not_none() && vb.not_none() {
753                        n -= 1;
754                        let (va, vb) = (va.unwrap().f64(), vb.unwrap().f64());
755                        sum_a -= va;
756                        sum_a2 -= va * va;
757                        sum_b -= vb;
758                        sum_b2 -= vb * vb;
759                        sum_ab -= va * vb;
760                    };
761                }
762                (alpha.cast(), beta.cast(), sse.cast())
763            },
764            None,
765        )
766        .unwrap()
767    }
768}
769
770impl<T: IsNone, I: Vec1View<T>> RollingValidReg<T> for I {}
771impl<T: IsNone, I: Vec1View<T>> RollingValidRegBinary<T> for I {}