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 {}