1//! Bivariate analysis
2
3mod bootstrap;
4pub mod regression;
5mod resamples;
6
7use crate::stats::bivariate::resamples::Resamples;
8use crate::stats::float::Float;
9use crate::stats::tuple::{Tuple, TupledDistributionsBuilder};
10use crate::stats::univariate::Sample;
11#[cfg(feature = "rayon")]
12use rayon::iter::{IntoParallelIterator, ParallelIterator};
13
14/// Bivariate `(X, Y)` data
15///
16/// Invariants:
17///
18/// - No `NaN`s in the data
19/// - At least two data points in the set
20pub struct Data<'a, X, Y>(&'a [X], &'a [Y]);
21
22impl<'a, X, Y> Copy for Data<'a, X, Y> {}
23
24#[cfg_attr(feature = "cargo-clippy", allow(clippy::expl_impl_clone_on_copy))]
25impl<'a, X, Y> Clone for Data<'a, X, Y> {
26 fn clone(&self) -> Data<'a, X, Y> {
27 *self
28 }
29}
30
31impl<'a, X, Y> Data<'a, X, Y> {
32 /// Returns the length of the data set
33 pub fn len(&self) -> usize {
34 self.0.len()
35 }
36
37 /// Iterate over the data set
38 pub fn iter(&self) -> Pairs<'a, X, Y> {
39 Pairs {
40 data: *self,
41 state: 0,
42 }
43 }
44}
45
46impl<'a, X, Y> Data<'a, X, Y>
47where
48 X: Float,
49 Y: Float,
50{
51 /// Creates a new data set from two existing slices
52 pub fn new(xs: &'a [X], ys: &'a [Y]) -> Data<'a, X, Y> {
53 assert!(
54 xs.len() == ys.len()
55 && xs.len() > 1
56 && xs.iter().all(|x| !x.is_nan())
57 && ys.iter().all(|y| !y.is_nan())
58 );
59
60 Data(xs, ys)
61 }
62
63 // TODO Remove the `T` parameter in favor of `S::Output`
64 /// Returns the bootstrap distributions of the parameters estimated by the `statistic`
65 ///
66 /// - Multi-threaded
67 /// - Time: `O(nresamples)`
68 /// - Memory: `O(nresamples)`
69 pub fn bootstrap<T, S>(&self, nresamples: usize, statistic: S) -> T::Distributions
70 where
71 S: Fn(Data<X, Y>) -> T + Sync,
72 T: Tuple + Send,
73 T::Distributions: Send,
74 T::Builder: Send,
75 {
76 #[cfg(feature = "rayon")]
77 {
78 (0..nresamples)
79 .into_par_iter()
80 .map_init(
81 || Resamples::new(*self),
82 |resamples, _| statistic(resamples.next()),
83 )
84 .fold(
85 || T::Builder::new(0),
86 |mut sub_distributions, sample| {
87 sub_distributions.push(sample);
88 sub_distributions
89 },
90 )
91 .reduce(
92 || T::Builder::new(0),
93 |mut a, mut b| {
94 a.extend(&mut b);
95 a
96 },
97 )
98 .complete()
99 }
100 #[cfg(not(feature = "rayon"))]
101 {
102 let mut resamples = Resamples::new(*self);
103 (0..nresamples)
104 .map(|_| statistic(resamples.next()))
105 .fold(T::Builder::new(0), |mut sub_distributions, sample| {
106 sub_distributions.push(sample);
107 sub_distributions
108 })
109 .complete()
110 }
111 }
112
113 /// Returns a view into the `X` data
114 pub fn x(&self) -> &'a Sample<X> {
115 Sample::new(self.0)
116 }
117
118 /// Returns a view into the `Y` data
119 pub fn y(&self) -> &'a Sample<Y> {
120 Sample::new(self.1)
121 }
122}
123
124/// Iterator over `Data`
125pub struct Pairs<'a, X: 'a, Y: 'a> {
126 data: Data<'a, X, Y>,
127 state: usize,
128}
129
130impl<'a, X, Y> Iterator for Pairs<'a, X, Y> {
131 type Item = (&'a X, &'a Y);
132
133 fn next(&mut self) -> Option<(&'a X, &'a Y)> {
134 if self.state < self.data.len() {
135 let i = self.state;
136 self.state += 1;
137
138 // This is safe because i will always be < self.data.{0,1}.len()
139 debug_assert!(i < self.data.0.len());
140 debug_assert!(i < self.data.1.len());
141 unsafe { Some((self.data.0.get_unchecked(i), self.data.1.get_unchecked(i))) }
142 } else {
143 None
144 }
145 }
146}
147