1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
use ndarray::{azip, prelude::*};
use rayon::prelude::*;
use crate::{
datasets::{Dataset, GaussIncTable, GaussTable, GaussWtdTable, IncDataset, MissingMethod},
estimators::{CSSEstimator, ParCSSEstimator, SSE},
models::{GaussCPDS, Labelled},
types::{AXIS_CHUNK_LENGTH, Error, Result, Set},
};
impl SSE<'_, GaussTable> {
fn fit(d: ArrayView2<f64>, x: &Set<usize>, z: &Set<usize>) -> Result<GaussCPDS> {
// Initialize the sufficient statistics.
let mut s = {
let n = 0.;
let mu_x = Array::zeros(x.len());
let mu_z = Array::zeros(z.len());
let s_xx = Array::zeros((x.len(), x.len()));
let s_xz = Array::zeros((x.len(), z.len()));
let s_zz = Array::zeros((z.len(), z.len()));
GaussCPDS::new(mu_x, mu_z, s_xx, s_xz, s_zz, n)?
};
// Initialize the chunk buffers.
d.axis_chunks_iter(Axis(0), AXIS_CHUNK_LENGTH)
.try_for_each(|d| -> Result<_> {
// Select the columns of the variables.
let mut d_x = Array::zeros((d.nrows(), x.len()));
x.iter().enumerate().for_each(|(i, &j)| {
d_x.column_mut(i).assign(&d.column(j));
});
// Compute the mean.
let mu_x = d_x
.mean_axis(Axis(0))
.ok_or_else(|| Error::MissingSufficientStatistics())?;
// Select the columns of the conditioning variables.
let mut d_z = Array::zeros((d.nrows(), z.len()));
z.iter().enumerate().for_each(|(i, &j)| {
d_z.column_mut(i).assign(&d.column(j));
});
// Compute the mean.
let mu_z = d_z
.mean_axis(Axis(0))
.ok_or_else(|| Error::MissingSufficientStatistics())?;
// Center the variables.
d_x -= &mu_x;
d_z -= &mu_z;
// Compute the centered second moment statistics.
let s_xx = d_x.t().dot(&d_x);
let s_xz = d_x.t().dot(&d_z);
let s_zz = d_z.t().dot(&d_z);
// Get the sample size.
let n = d.nrows() as f64;
// Accumulate the sufficient statistics.
s += GaussCPDS::new(mu_x, mu_z, s_xx, s_xz, s_zz, n)?;
Ok(())
})?;
// Return the sufficient statistics.
Ok(s)
}
}
impl CSSEstimator<GaussCPDS> for SSE<'_, GaussTable> {
fn fit(&self, x: &Set<usize>, z: &Set<usize>) -> Result<GaussCPDS> {
// Check variables and conditioning variables must be disjoint.
if !x.is_disjoint(z) {
return Err(Error::SetsNotDisjoint(
&format!("{:?}", x),
&format!("{:?}", z),
));
}
// Get the values.
let d = self.dataset.values();
// Return the sufficient statistics.
Self::fit(d.view(), x, z)
}
}
impl ParCSSEstimator<GaussCPDS> for SSE<'_, GaussTable> {
fn par_fit(&self, x: &Set<usize>, z: &Set<usize>) -> Result<GaussCPDS> {
// Check variables and conditioning variables must be disjoint.
if !x.is_disjoint(z) {
return Err(Error::SetsNotDisjoint(
&format!("{:?}", x),
&format!("{:?}", z),
));
}
// Initialize the sufficient statistics.
let s_xz = {
let n = 0.;
let mu_x = Array::zeros(x.len());
let mu_z = Array::zeros(z.len());
let s_xx = Array::zeros((x.len(), x.len()));
let s_xz = Array::zeros((x.len(), z.len()));
let s_zz = Array::zeros((z.len(), z.len()));
GaussCPDS::new(mu_x, mu_z, s_xx, s_xz, s_zz, n)?
};
// Get the values.
let d = self.dataset.values();
// Get the values.
d.axis_chunks_iter(Axis(0), AXIS_CHUNK_LENGTH)
.into_par_iter()
// Compute the sufficient statistics for each chunk.
.map(|d| Self::fit(d, x, z))
// Aggregate the sufficient statistics.
.try_fold(|| s_xz.clone(), |a, b| Ok(a + b?))
.try_reduce(|| s_xz.clone(), |a, b| Ok(a + b))
}
}
impl SSE<'_, GaussWtdTable> {
fn fit(
d: ArrayView2<f64>,
norm_w: ArrayView2<f64>,
sum_w: f64,
x: &Set<usize>,
z: &Set<usize>,
) -> Result<GaussCPDS> {
// Initialize the sufficient statistics.
let mut s = {
let n = 0.;
let mu_x = Array::zeros(x.len());
let mu_z = Array::zeros(z.len());
let s_xx = Array::zeros((x.len(), x.len()));
let s_xz = Array::zeros((x.len(), z.len()));
let s_zz = Array::zeros((z.len(), z.len()));
GaussCPDS::new(mu_x, mu_z, s_xx, s_xz, s_zz, n)?
};
// Initialize the chunk buffers.
d.axis_chunks_iter(Axis(0), AXIS_CHUNK_LENGTH)
.zip(norm_w.axis_chunks_iter(Axis(0), AXIS_CHUNK_LENGTH))
.try_for_each(|(d, w)| -> Result<_> {
// Compute the root weights for centering.
let sqrt_w = w.mapv(f64::sqrt);
// Select the columns of the variables.
let mut d_x = Array::zeros((d.nrows(), x.len()));
x.iter().enumerate().for_each(|(i, &j)| {
azip!((c_i in &mut d_x.column_mut(i), c_j in d.column(j), w in &sqrt_w.column(0)) *c_i = c_j * w);
});
// Compute the weighted mean.
let mu_x = (&d_x * &sqrt_w).sum_axis(Axis(0));
// Select the columns of the conditioning variables.
let mut d_z = Array::zeros((d.nrows(), z.len()));
z.iter().enumerate().for_each(|(i, &j)| {
azip!((c_i in &mut d_z.column_mut(i), c_j in d.column(j), w in &sqrt_w.column(0)) *c_i = c_j * w);
});
// Compute the weighted mean.
let mu_z = (&d_z * &sqrt_w).sum_axis(Axis(0));
// Get the sample (mass) size.
let w_sum = w.sum();
let n = w_sum * sum_w;
// Accumulate the sufficient statistics.
if w_sum > 0. {
// Compute the mean.
let mean_x = &mu_x / w_sum;
let mean_z = &mu_z / w_sum;
// Compute the centering factor.
let c_x = &sqrt_w * &mean_x.view().insert_axis(Axis(0));
let c_z = &sqrt_w * &mean_z.view().insert_axis(Axis(0));
// Center the variables.
d_x -= &c_x;
d_z -= &c_z;
// Compute the weighted centered second moment statistics.
let s_xx = d_x.t().dot(&d_x) * sum_w;
let s_xz = d_x.t().dot(&d_z) * sum_w;
let s_zz = d_z.t().dot(&d_z) * sum_w;
s += GaussCPDS::new(mean_x, mean_z, s_xx, s_xz, s_zz, n)?;
}
Ok(())
})?;
// Return the sufficient statistics.
Ok(s)
}
}
impl CSSEstimator<GaussCPDS> for SSE<'_, GaussWtdTable> {
fn fit(&self, x: &Set<usize>, z: &Set<usize>) -> Result<GaussCPDS> {
// Check variables and conditioning variables must be disjoint.
if !x.is_disjoint(z) {
return Err(Error::SetsNotDisjoint(
&format!("{:?}", x),
&format!("{:?}", z),
));
}
// Get the values.
let d = self.dataset.values().values();
// Get the weights.
let w = self.dataset.weights();
// Sum the weights to normalize.
let sum_w = w.sum();
// Normalize the weights.
let w = w / sum_w;
// Align the axis for broadcasting.
let w = w.insert_axis(Axis(1));
// Return the sufficient statistics.
Self::fit(d.view(), w.view(), sum_w, x, z)
}
}
impl ParCSSEstimator<GaussCPDS> for SSE<'_, GaussWtdTable> {
fn par_fit(&self, x: &Set<usize>, z: &Set<usize>) -> Result<GaussCPDS> {
// Check variables and conditioning variables must be disjoint.
if !x.is_disjoint(z) {
return Err(Error::SetsNotDisjoint(
&format!("{:?}", x),
&format!("{:?}", z),
));
}
// Initialize the sufficient statistics.
let s_xz = {
let n = 0.;
let mu_x = Array::zeros(x.len());
let mu_z = Array::zeros(z.len());
let s_xx = Array::zeros((x.len(), x.len()));
let s_xz = Array::zeros((x.len(), z.len()));
let s_zz = Array::zeros((z.len(), z.len()));
GaussCPDS::new(mu_x, mu_z, s_xx, s_xz, s_zz, n)?
};
// Get the values.
let values = self.dataset.values().values();
// Get the weights.
let weights = self.dataset.weights();
// Sum the weights to normalize.
let sum_w: f64 = weights.par_iter().sum();
// Normalize the weights.
let weights = {
// Clone the weights.
let mut weights = weights.clone();
// Normalize the weights in parallel.
weights
.axis_chunks_iter_mut(Axis(0), AXIS_CHUNK_LENGTH)
.into_par_iter()
.for_each(|mut w| w /= sum_w);
// Return the normalized weights.
weights
};
// Align the axis for broadcasting.
let weights = weights.insert_axis(Axis(1));
// Get the values.
values
.axis_chunks_iter(Axis(0), AXIS_CHUNK_LENGTH)
.into_par_iter()
.zip(weights.axis_chunks_iter(Axis(0), AXIS_CHUNK_LENGTH))
// Compute the sufficient statistics for each chunk.
.map(|(d, w)| Self::fit(d, w, sum_w, x, z))
// Aggregate the sufficient statistics.
.try_fold(|| s_xz.clone(), |a, b| Ok(a + b?))
.try_reduce(|| s_xz.clone(), |a, b| Ok(a + b))
}
}
impl CSSEstimator<GaussCPDS> for SSE<'_, GaussIncTable> {
fn fit(&self, x: &Set<usize>, z: &Set<usize>) -> Result<GaussCPDS> {
// Get the union of X and Z.
let x_z = Some(&(x | z));
// Get the missing method or default to PW.
let m = self.missing_method.as_ref().unwrap_or(&MissingMethod::PW);
// Get the missing mechanism or default to None.
let r = self.missing_mechanism.as_ref();
// Apply the missing handling method.
let d = self.dataset.apply_missing_method(m, x_z, r)?;
// Get the labels of the original dataset.
let labels = self.dataset.labels();
// Map the indices from the original dataset to the new one.
let x = d.indices_from(x, labels)?;
let z = d.indices_from(z, labels)?;
// Estimate based on the resulting dataset.
d.map_either(
|d| SSE::new(&d).fit(&x, &z), // Complete case.
|d| SSE::new(&d).fit(&x, &z), // Weighted case.
)
.into_inner()
}
}
impl ParCSSEstimator<GaussCPDS> for SSE<'_, GaussIncTable> {
fn par_fit(&self, x: &Set<usize>, z: &Set<usize>) -> Result<GaussCPDS> {
// Get the union of X and Z.
let x_z = Some(&(x | z));
// Get the missing method or default to PW.
let m = self.missing_method.as_ref().unwrap_or(&MissingMethod::PW);
// Get the missing mechanism or default to None.
let r = self.missing_mechanism.as_ref();
// Apply the missing handling method.
let d = self.dataset.apply_missing_method(m, x_z, r)?;
// Get the labels of the original dataset.
let labels = self.dataset.labels();
// Map the indices from the original dataset to the new one.
let x = d.indices_from(x, labels)?;
let z = d.indices_from(z, labels)?;
// Estimate based on the resulting dataset.
d.map_either(
|d| SSE::new(&d).par_fit(&x, &z), // Complete case.
|d| SSE::new(&d).par_fit(&x, &z), // Weighted case.
)
.into_inner()
}
}