rstats/vecvec.rs
1use crate::*;
2use indxvec::Vecops;
3use medians::{MedError, Medianf64};
4use rayon::prelude::*;
5
6impl<T> VecVec<T> for &[Vec<T>]
7where
8 T: Clone + PartialOrd + Sync + Into<f64>,
9 Vec<Vec<T>>: IntoParallelIterator,
10 Vec<T>: IntoParallelIterator{
11
12 /// Linearly weighted approximate time series derivative at the last point (present time).
13 /// Weighted average (backwards half filter), minus the median.
14 fn dvdt(self, centre: &[f64]) -> Result<Vec<f64>, RE> {
15 let len = self.len();
16 if len < 2 {
17 return nodata_error(format!("dvdt time series is too short: {len}"));
18 };
19 let mut weight = 1_f64;
20 let mut sumwv:Vec<f64> = self[0].iter().map(|x| x.clone().into()).collect();
21 for v in self.iter().skip(1) {
22 weight += 1_f64;
23 sumwv.mutvadd(&v.smult(weight));
24 };
25 Ok(sumwv.smult(1.0/(sumn(len) as f64)).vsub(centre))
26 }
27
28 /// Maps scalar valued closure onto all vectors in self and collects
29 fn scalar_fn(self,f: impl Fn(&[T]) -> Result<f64,RE>) -> Result<Vec<f64>,RE> {
30 self.iter().map(|s|-> Result<f64,RE> {
31 f(s) }).collect::<Result<Vec<f64>,RE>>()
32 }
33
34 /// Maps vector valued closure onto all vectors in self and collects
35 fn vector_fn(self,f: impl Fn(&[T]) -> Result<Vec<f64>,RE>) -> Result<Vec<Vec<f64>>,RE> {
36 self.iter().map(|s|-> Result<Vec<f64>,RE> {
37 f(s) }).collect::<Result<Vec<Vec<f64>>,RE>>()
38 }
39
40 /// Exact radii magnitudes to all member points from the Geometric Median.
41 /// More accurate and usually faster as well than the approximate `eccentricities` above,
42 /// especially when there are many points.
43 fn radii(self, gm: &[f64]) -> Result<Vec<f64>, RE> {
44 self.scalar_fn(|s| Ok(gm.vdist(s)))
45 }
46
47 /// Selects a column by number
48 fn column(self, cnum: usize) -> Vec<f64> {
49 self.iter().map(|row| row[cnum].clone().into()).collect()
50 }
51
52 /// Multithreaded transpose of vec of vecs matrix
53 fn transpose(self) -> Vec<Vec<f64>> {
54 (0..self[0].len())
55 .into_par_iter()
56 .map(|cnum| self.column(cnum))
57 .collect()
58 }
59
60 /// Normalize columns of a matrix, so that they become unit row vectors
61 fn normalize(self) -> Result<Vec<Vec<f64>>, RE> {
62 (0..self[0].len())
63 .into_par_iter()
64 .map(|cnum| -> Result<Vec<f64>, RE> { self.column(cnum).vunit() })
65 .collect()
66 }
67
68 /// Householder's method returning triangular matrices (U',R), where
69 /// U are the reflector generators for use by house_uapply(m).
70 /// R is the upper triangular decomposition factor.
71 /// Here both U and R are returned for convenience in their transposed lower triangular forms.
72 /// Transposed input self for convenience, so that original columns get accessed easily as rows.
73 fn house_ur(self) -> Result<(TriangMat, TriangMat),RE> {
74 let n = self.len();
75 let d = self[0].len();
76 let min = if d <= n { d } else { n }; // minimal dimension
77 let mut r = self.transpose(); // self.iter().map(|s| s.tof64()).collect::<Vec<Vec<f64>>>(); // //
78 let mut ures = vec![0.; sumn(min)];
79 let mut rres = Vec::with_capacity(sumn(min));
80 for j in 0..min {
81 let Some(slc) = r[j].get(j..d)
82 else { return data_error("house_ur: failed to extract uvec slice"); };
83 let uvec = slc.house_reflector();
84 for rlast in r.iter_mut().take(d).skip(j) {
85 let rvec = uvec.house_reflect::<f64>(&rlast.drain(j..d).collect::<Vec<f64>>());
86 rlast.extend(rvec);
87 // drained, reflected with this uvec, and rebuilt, all remaining rows of r
88 }
89 // these uvecs are columns, so they must be saved column-wise
90 for (row, &usave) in uvec.iter().enumerate() {
91 ures[sumn(row + j) + j] = usave; // using triangular index
92 }
93 // save completed `r[j]` components only up to and including the diagonal
94 // we are not even storing the rest, so no need to set those to zero
95 for &rsave in r[j].iter().take(j + 1) {
96 rres.push(rsave)
97 }
98 }
99 Ok((
100 TriangMat {
101 kind: 3,
102 data: ures,
103 }, // transposed, non symmetric kind
104 TriangMat {
105 kind: 3,
106 data: rres,
107 }, // transposed, non symmetric kind
108 ))
109 }
110
111 /// Joint probability density function of n matched slices of the same length
112 fn jointpdfn(self) -> Result<Vec<f64>, RE> {
113 let d = self[0].len(); // their common dimensionality (length)
114 for v in self.iter().skip(1) {
115 if v.len() != d {
116 return data_error("jointpdfn: all vectors must be of equal length!");
117 };
118 }
119 let mut res: Vec<f64> = Vec::with_capacity(d);
120 let mut tuples = self.transpose();
121 let df = tuples.len() as f64; // for turning counts to probabilities
122 // lexical sort to group together occurrences of identical tuples
123 tuples.sort_unstable_by(
124 |a, b| a.partial_cmp(b)
125 .expect("jointpdfn: tuples comparison failed"));
126 let mut count = 1_usize; // running count
127 let mut lastindex = 0; // initial index of the last unique tuple
128 tuples.iter().enumerate().skip(1).for_each(|(i, ti)| {
129 if ti > &tuples[lastindex] {
130 // new tuple ti (Vec<T>) encountered
131 res.push((count as f64) / df); // save frequency count as probability
132 lastindex = i; // current index becomes the new one
133 count = 1_usize; // reset counter
134 } else {
135 count += 1;
136 }
137 });
138 res.push((count as f64) / df); // flush the rest!
139 Ok(res)
140 }
141
142 /// Joint entropy of vectors of the same length
143 fn jointentropyn(self) -> Result<f64, RE> {
144 let jpdf = self.jointpdfn()?;
145 Ok(jpdf.iter().map(|&x| -x * (x.ln())).sum())
146 }
147
148 /// Dependence (component wise) of a set of vectors.
149 /// i.e. `dependencen` returns 0 iff they are statistically independent
150 /// bigger values when they are dependentent
151 fn dependencen(self) -> Result<f64, RE> {
152 Ok((0..self.len())
153 .into_par_iter()
154 .map(|i| self[i].entropy())
155 .sum::<f64>()
156 / self.jointentropyn()?
157 - 1.0)
158 }
159
160 /// Flattened lower triangular part of a symmetric matrix for vectors in self.
161 /// The upper triangular part can be trivially generated for all j>i by: c(j,i) = c(i,j).
162 /// Applies closure f to compute a scalar binary relation between all pairs of vector
163 /// components of self.
164 /// The closure typically invokes one of the methods from Vecg trait (in vecg.rs),
165 /// such as dependencies.
166 /// Example call: `pts.transpose().crossfeatures(|v1,v2| v1.mediancorrf64(v2)?)?`
167 /// computes median correlations between all column vectors (features) in pts.
168 fn crossfeatures(self, f: fn(&[T], &[T]) -> f64) -> Result<TriangMat, RE> {
169 Ok(TriangMat {
170 kind: 2, // symmetric, non transposed
171 data: (0..self.len())
172 .into_par_iter()
173 .flat_map(|i| {
174 (0..i+1)
175 .map(|j| f(&self[i], &self[j]))
176 .collect::<Vec<f64>>()
177 })
178 .collect::<Vec<f64>>(),
179 })
180 }
181
182 /// Sum of nd points (or vectors)
183 fn sumv(self) -> Vec<f64> {
184 let mut resvec = vec![0_f64; self[0].len()];
185 for v in self {
186 resvec.mutvadd(v)
187 }
188 resvec
189 }
190
191 /// acentroid = multidimensional arithmetic mean
192 fn acentroid(self) -> Vec<f64> {
193 self.sumv().smult(1. / (self.len() as f64))
194 }
195
196 /// multithreaded acentroid = multidimensional arithmetic mean
197 fn par_acentroid(self) -> Vec<f64> {
198 let sumvec = self
199 .par_iter()
200 .fold(
201 || vec![0_f64; self[0].len()],
202 |mut vecsum: Vec<f64>, p| {
203 vecsum.mutvadd(p);
204 vecsum
205 },
206 )
207 .reduce(
208 || vec![0_f64; self[0].len()],
209 |mut finalsum: Vec<f64>, partsum: Vec<f64>| {
210 finalsum.mutvadd(&partsum);
211 finalsum
212 },
213 );
214 sumvec.smult(1. / (self.len() as f64))
215 }
216
217 /// gcentroid = multidimensional geometric mean
218 fn gcentroid(self) -> Result<Vec<f64>,RE> {
219 let logvs = self.iter().map(|v|-> Result<Vec<f64>,RE> {
220 Ok(v.vunit()?.smult(v.vmag().ln())) })
221 .collect::<Result<Vec<Vec<f64>>,RE>>()?;
222 let logcentroid = logvs.acentroid();
223 Ok(logcentroid.vunit()?.smult(logcentroid.vmag().exp()))
224 }
225
226 /// hcentroid = multidimensional harmonic mean
227 fn hcentroid(self) -> Result<Vec<f64>,RE> {
228 let mut centre = vec![0_f64; self[0].len()];
229 for v in self {
230 centre.mutvadd(&v.vinverse()?)
231 }
232 Ok(centre
233 .vinverse()?.smult(self.len() as f64))
234 }
235
236 /// For each member point, gives its sum of distances to all other points and their MinMax
237 fn distsums(self) -> Vec<f64> {
238 let n = self.len();
239 let mut dists = vec![0_f64; n]; // distances accumulator for all points
240 // examine all unique pairings (lower triangular part of symmetric flat matrix)
241 self.iter().enumerate().for_each(|(i, thisp)| {
242 self.iter().take(i).enumerate().for_each(|(j, thatp)| {
243 let d = thisp.vdist(thatp); // calculate each distance relation just once
244 dists[i] += d;
245 dists[j] += d; // but add it to both points' sums
246 })
247 });
248 dists
249 }
250
251 /// Points nearest and furthest from the geometric median.
252 /// Returns struct MinMax{min,minindex,max,maxindex}
253 fn medout(self, gm: &[f64]) -> Result<MinMax<f64>,RE> {
254 Ok(self.scalar_fn(|s| Ok(gm.vdist(s)))?.minmax())
255 }
256
257 /// Radius of a point specified by its subscript.
258 fn radius(self, i: usize, gm: &[f64]) -> Result<f64, RE> {
259 if i > self.len() {
260 return data_error("radius: invalid subscript");
261 }
262 Ok(self[i].vdist(gm))
263 }
264
265 /// Arith mean and std (in Params struct), Median and MAD (in another Params struct), Medoid and Outlier (in MinMax struct)
266 /// of scalar radii of points in self.
267 /// These are new robust measures of a cloud of multidimensional points (or multivariate sample).
268 fn eccinfo(self, gm: &[f64]) -> Result<(Params, Params, MinMax<f64>), RE>
269 where
270 Vec<f64>: FromIterator<f64>,
271 {
272 let rads: Vec<f64> = self.radii(gm)?;
273 let radsmed = rads.medf_unchecked();
274 let radsmad = rads.madf(radsmed);
275 Ok((rads.ameanstd()?, Params{centre:radsmed,spread:radsmad}, rads.minmax()))
276 }
277
278 /// Quasi median, recommended only for comparison purposes
279 fn quasimedian(self) -> Result<Vec<f64>, RE> {
280 Ok((0..self[0].len())
281 .map(|colnum| self.column(colnum).medf_checked())
282 .collect::<Result<Vec<f64>, MedError<String>>>()?)
283 }
284
285 /// Proportional projections on each +/- axis (by hemispheres).
286 /// Adds only points that are specified in idx.
287 /// Self should be zero median vectors, previously obtained by `self.translate(&gm)`.
288 /// The result is normalized to unit vector.
289 fn sigvec(self, idx: &[usize]) -> Result<Vec<f64>, RE> {
290 let dims = self[0].len();
291 if self.is_empty() {
292 return nodata_error("sigvec given empty data");
293 };
294 let mut hemis = vec![0_f64; 2 * dims];
295 for &i in idx {
296 for (j, component) in self[i].iter().enumerate() {
297 let cf = component.clone().into();
298 if cf < 0. {
299 hemis[dims + j] -= cf;
300 continue;
301 };
302 hemis[j] += cf;
303 };
304 };
305 hemis.vunit()
306 }
307
308 /// madgm median of distances from gm: stable nd data spread measure
309 fn madgm(self, gm: &[f64]) -> Result<f64, RE> {
310 if self.is_empty() {
311 return nodata_error("madgm given empty vec!"); };
312 Ok(self.radii(gm)?.medf_unchecked())
313 }
314
315 /// stdgm mean of distances from gm: nd data spread measure, aka nd standard deviation
316 fn stdgm(self, gm: &[f64]) -> Result<f64,RE> {
317 if self.is_empty() {
318 return nodata_error("stdgm given empty vec!")?; };
319 Ok( self.iter()
320 .map(|s| s.vdist(gm)).sum::<f64>()/self.len() as f64 )
321 }
322
323 /// Outer hull subscripts from their square radii and their sort index
324 fn outer_hull(self, sqrads: &[f64], sindex: &[usize]) -> Vec<usize> {
325 let mut hullindex: Vec<usize> = Vec::new();
326 let len = sindex.len();
327 // test ipoints in descending sqrads order
328 'ploop: for i in (0..len).rev() {
329 let ipoint = &self[sindex[i]];
330 // against jpoints with greater radius
331 for j in (i+1..len).rev() {
332 // this jpoint lies outside the normal plane to ipoint => reject ipoint
333 if self[sindex[j]].dotp(ipoint) > sqrads[sindex[i]] { continue 'ploop; };
334 };
335 hullindex.push(sindex[i]); // passed
336 };
337 hullindex
338 }
339
340 /// Inner hull points from their square radii and their sort index
341 fn inner_hull(self, sqrads: &[f64], sindex: &[usize]) -> Vec<usize> {
342 let mut hullindex: Vec<usize> = Vec::new();
343 let len = sindex.len();
344 // test ipoints in ascending sqrads order
345 'ploop: for i in 0..len {
346 let ipoint = &self[sindex[i]];
347 // against jpoints with smaller radius
348 for j in 0..i {
349 // this jpoint lies inside ipoint => reject ipoint
350 if self[sindex[j]].dotp(ipoint) > sqrads[sindex[j]] { continue 'ploop; };
351 };
352 hullindex.push(sindex[i]); // ipoint passed
353 };
354 hullindex
355 }
356
357 /// Likelihood of zero median point **p** belonging to zero median data cloud `self`,
358 /// based on the points outside of the normal plane through **p**.
359 /// Returns the sum of unit vectors of its outside points, projected onto unit **p**.
360 /// Index should be in the descending order of magnitudes of self points (for efficiency).
361 fn depth(self, descending_index: &[usize], p: &[f64]) -> Result<f64,RE> {
362 let psq = p.vmagsq();
363 let mut sumvec = vec![0_f64;p.len()];
364 for &i in descending_index {
365 let s = &self[i];
366 let ssq = s.vmagsq();
367 if ssq <= psq { break; }; // no more outside points
368 if s.dotp(p) > psq { sumvec.mutvadd(&s.smult(1.0/(ssq.sqrt()))) };
369 };
370 Ok(sumvec.dotp(&p.vunit()?))
371 }
372
373 /// Likelihood of zero median point **p** belonging to zero median data cloud `self`,
374 /// based on the proportion of points outside of the normal plane through **p**.
375 /// Index should be in the descending order of magnitudes of self points (for efficiency).
376 fn depth_ratio(self, descending_index: &[usize], p: &[f64]) -> f64 {
377 let psq = p.vmagsq();
378 let mut num = 0_f64;
379 for &i in descending_index {
380 let s = &self[i];
381 let ssq = s.vmagsq();
382 if ssq <= psq { break; }; // no more outside points
383 if s.dotp(p) > psq { num += 1.0; };
384 };
385 num/(self.len() as f64)
386 }
387
388 /// Collects indices of inner hull and outer hull, from zero median points in self.
389 /// We put a plane trough data point A, normal to its zero median vector **a**.
390 /// B is an inner hull point, when it lies inside all other points' normal planes.
391 /// C is an outer hull point, when there is no other point outside its own normal plane.
392 /// B can belong to both hulls, as when all the points lie on a hyper-sphere around **gm**.
393 /// The testing is done in increasing (decreasing) radius order.
394 /// B lies outside the normal plane of **a**, when its projection onto unit **a** exceeds
395 /// `|a|: |b|cos(θ) > |a| => a*b > |a|^2`,
396 /// such B immediately fails as a candidate for the inner hull.
397 /// Using square magnitudes, `|a|^2` saves taking square roots and dividing the dot product by |a|.
398 /// Similarly for the outer hull, where A and B simply swap roles.
399 fn hulls(self) -> (Vec<usize>, Vec<usize>) {
400 let sqradii = self.iter().map(|s| s.vmagsq()).collect::<Vec<f64>>();
401 let sindex = sqradii.mergesort_indexed();
402 let innerindex = self.inner_hull(&sqradii,&sindex);
403 let outerindex = self.outer_hull(&sqradii,&sindex);
404 (innerindex, outerindex)
405 }
406
407 /// Geometric median's residual error
408 fn gmerror(self, g: &[f64]) -> Result<f64, RE> {
409 let mut unitvecssum = vec![0_f64; self[0].len()];
410 for v in self { unitvecssum.mutvadd(&v.vsub(g).vunit()?); };
411 Ok(unitvecssum.vmag())
412 }
413
414 /// Geometric Median (gm) is the point that minimises the sum of distances to a given set of points.
415 /// It has (provably) only vector iterative solutions.
416 /// Search methods are slow and difficult in highly dimensional space.
417 /// Weiszfeld's fixed point iteration formula has known problems with sometimes failing to converge.
418 /// Especially, when the points are dense in the close proximity of the gm, or gm coincides with one of them.
419 /// However, these problems are fixed in my new algorithm here.
420 /// The sum of reciprocals is strictly increasing and so is used here as
421 /// easy to evaluate termination condition.
422 fn gmedian(self, eps: f64) -> Vec<f64> {
423 let mut g = self.acentroid(); // start iterating from the mean or vec![0_f64; self[0].len()];
424 let mut recsum = 0f64;
425 loop {
426 // vector iteration till accuracy eps is exceeded
427 let mut nextg = vec![0_f64; self[0].len()];
428 let mut nextrecsum = 0_f64;
429 for p in self {
430 // |p-g| done in-place for speed. Could have simply called p.vdist(g)
431 let mag: f64 = p
432 .iter()
433 .zip(&g)
434 .map(|(vi, gi)| (vi.clone().into() - gi).powi(2))
435 .sum();
436 if mag > eps {
437 // reciprocal of distance (scalar)
438 let rec = 1.0_f64 / (mag.sqrt());
439 // vsum increment by components
440 for (vi, gi) in p.iter().zip(&mut nextg) {
441 *gi += vi.clone().into() * rec
442 }
443 // add the scaling reciprocal
444 nextrecsum += rec
445 } // ignore point p when |p-g| <= eps
446 }
447 nextg.iter_mut().for_each(|gi| *gi /= nextrecsum);
448 if nextrecsum - recsum < eps {
449 return nextg;
450 }; // termination
451 g = nextg;
452 recsum = nextrecsum;
453 }
454 }
455
456 /// Parallel (multithreaded) implementation of Geometric Median. Possibly the fastest you will find.
457 /// Geometric Median (gm) is the point that minimises the sum of distances to a given set of points.
458 /// It has (provably) only vector iterative solutions.
459 /// Search methods are slow and difficult in hyper space.
460 /// Weiszfeld's fixed point iteration formula has known problems and sometimes fails to converge.
461 /// Specifically, when the points are dense in the close proximity of the gm, or gm coincides with one of them.
462 /// However, these problems are solved in my new algorithm here.
463 /// The sum of reciprocals is strictly increasing and so is used to easily evaluate the termination condition.
464 fn par_gmedian(self, eps: f64) -> Vec<f64> {
465 let mut g = self.par_acentroid(); // start iterating from the mean or vec![0_f64; self[0].len()];
466 let mut recsum = 0_f64;
467 loop {
468 // vector iteration till accuracy eps is exceeded
469 let (mut nextg, nextrecsum) = self
470 .par_iter()
471 .fold(
472 || (vec![0_f64; self[0].len()], 0_f64),
473 |mut pair: (Vec<f64>, f64), p: &Vec<T>| {
474 // |p-g| done in-place for speed. Could have simply called p.vdist(g)
475 let mag: f64 = p
476 .iter()
477 .zip(&g)
478 .map(|(vi, gi)| (vi.clone().into() - gi).powi(2))
479 .sum();
480 if mag > eps {
481 let rec = 1.0_f64 / (mag.sqrt()); // reciprocal of distance (scalar)
482 for (vi, gi) in p.iter().zip(&mut pair.0) {
483 *gi += vi.clone().into() * rec
484 }
485 pair.1 += rec; // add separately the reciprocals for the final scaling
486 } // else simply ignore this point should its distance from g be zero
487 pair
488 },
489 )
490 // must run reduce on the partial sums produced by fold
491 .reduce(
492 || (vec![0_f64; self[0].len()], 0_f64),
493 |mut pairsum: (Vec<f64>, f64), pairin: (Vec<f64>, f64)| {
494 pairsum.0.mutvadd(&pairin.0);
495 pairsum.1 += pairin.1;
496 pairsum
497 },
498 );
499 nextg.iter_mut().for_each(|gi| *gi /= nextrecsum);
500 if nextrecsum - recsum < eps {
501 return nextg;
502 }; // termination test
503 g = nextg;
504 recsum = nextrecsum;
505 }
506 }
507
508 /// Like `gmedian` but returns also the sum of reciprocals.
509 fn gmparts(self, eps: f64) -> (Vec<f64>, f64) {
510 let mut g = self.acentroid(); // start iterating from the Centre
511 let mut recsum = 0f64;
512 loop {
513 // vector iteration till accuracy eps is exceeded
514 let mut nextg = vec![0_f64; self[0].len()];
515 let mut nextrecsum = 0f64;
516 for x in self {
517 let mag = g
518 .iter()
519 .zip(x)
520 .map(|(&gi, xi)| (xi.clone().into() - gi).powi(2))
521 .sum::<f64>();
522 if mag.is_normal() {
523 let rec = 1.0_f64 / (mag.sqrt()); // reciprocal of distance (scalar)
524 // vsum increments by components
525 nextg
526 .iter_mut()
527 .zip(x)
528 .for_each(|(vi, xi)| *vi += xi.clone().into() * rec);
529 nextrecsum += rec // add separately the reciprocals for final scaling
530 } // else simply ignore this point should its distance from g be zero
531 }
532 if nextrecsum - recsum < eps {
533 return (
534 nextg
535 .iter()
536 .map(|&gi| gi / nextrecsum)
537 .collect::<Vec<f64>>(),
538 nextrecsum,
539 );
540 }; // termination
541 nextg.iter_mut().for_each(|gi| *gi /= nextrecsum);
542 g = nextg;
543 recsum = nextrecsum;
544 }
545 }
546
547 /// Symmetric covariance matrix. Becomes comediance when argument `mid`
548 /// is the geometric median instead of the centroid.
549 /// The indexing is always in this order: (row,column) (left to right, top to bottom).
550 /// The items are flattened into a single vector in this order.
551 fn covar(self, mid:&[f64]) -> Result<TriangMat,RE> {
552 let d = self[0].len(); // dimension of the vector(s)
553 if d != mid.len() {
554 return data_error("covar self and mid dimensions mismatch"); };
555 let mut covsum = self
556 .par_iter()
557 .fold(
558 || vec![0_f64; (d+1)*d/2],
559 | mut cov: Vec<f64>, selfp | {
560 let mut covsub = 0_usize; // subscript into the flattened array cov
561 let vm = selfp.vsub(mid); // zero mean vector
562 vm.iter().enumerate().for_each(|(i,diag)|
563 // its products up to and including the diagonal (itself)
564 // the number of elements to take is always the subscript + 1!
565 vm.iter().take(i+1).for_each(|vmi| {
566 cov[covsub] += diag*vmi;
567 covsub += 1;
568 }));
569 cov
570 }
571 )
572 .reduce(
573 || vec![0_f64; (d+1)*d/2],
574 | mut covout: Vec<f64>, covin: Vec<f64> | {
575 covout.mutvadd(&covin);
576 covout
577 }
578 );
579 // now compute the means and return
580 let lf = self.len() as f64;
581 covsum.iter_mut().for_each(|c| *c /= lf);
582 Ok(TriangMat{ kind:2,data:covsum }) // symmetric, non transposed
583 }
584
585 /// Symmetric covariance matrix. Becomes comediance when supplied argument `mid`
586 /// is the geometric median instead of the centroid.
587 /// Indexing is always in this order: (row,column) (left to right, top to bottom).
588 fn serial_covar(self, mid:&[f64]) -> Result<TriangMat,RE> {
589 let d = self[0].len(); // dimension of the vector(s)
590 if d != mid.len() {
591 return data_error("serial_covar self and mid dimensions mismatch")?; };
592 let mut covsums = vec![0_f64; (d+1)*d/2];
593 for p in self {
594 let mut covsub = 0_usize; // subscript into the flattened array cov
595 let zp = p.vsub(mid); // zero mean/median vector
596 zp.iter().enumerate().for_each(|(i,thisc)|
597 // its products up to and including the diagonal
598 zp.iter().take(i+1).for_each(|otherc| {
599 covsums[covsub] += thisc*otherc;
600 covsub += 1;
601 }) )
602 };
603 // now compute the means and return
604 let lf = self.len() as f64;
605 for c in covsums.iter_mut() { *c /= lf };
606 Ok(TriangMat{ kind:2,data:covsums }) // kind 2 = symmetric, non transposed
607 }
608
609 /// Projects self onto a given basis, e.g. dimensional reduction
610 /// The returned vectors will have lengths equal to the number of supplied basis vectors.
611 fn projection(self, basis: &[Vec<f64>]) -> Result<Vec<Vec<f64>>, RE>
612 {
613 if self.is_empty() {
614 return nodata_error("projection: empty data");
615 };
616 let olddims = self[0].len();
617 if basis.len() > olddims {
618 return data_error("projection: given too many basis vectors");
619 };
620 if olddims != basis[0].len() {
621 return data_error("projection: lengths of data vectors and basis vectors differ!");
622 };
623 let mut res = Vec::with_capacity(self.len());
624 for dvec in self {
625 res.push(
626 basis
627 .iter()
628 .map(|ev| dvec.dotp(ev))
629 .collect::<Vec<f64>>(),
630 )
631 };
632 Ok(res)
633 }
634}