Skip to main content

r2rs_stats/funcs/
cov.rs

1// "Whatever you do, work at it with all your heart, as working for the Lord,
2// not for human masters, since you know that you will receive an inheritance
3// from the Lord as a reward. It is the Lord Christ you are serving."
4// (Col 3:23-24)
5
6use nalgebra::DMatrix;
7use num_traits::Float;
8use r2rs_base::traits::StatisticalSlice;
9
10#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
11pub enum NAMethod {
12    #[default]
13    Everything,
14    AllObs,
15    CompleteObs,
16    NAOrComplete,
17    PairwiseCompleteObs,
18}
19
20#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
21pub enum Method {
22    #[default]
23    Pearson,
24    Kendall,
25    Spearman,
26}
27
28fn clamp(x: f64) -> f64 {
29    if x > 1.0 {
30        1.0
31    } else if x < -1.0 {
32        -1.0
33    } else {
34        x
35    }
36}
37
38fn sign(x: f64) -> f64 {
39    if x == 0.0 {
40        0.0
41    } else {
42        x.signum()
43    }
44}
45
46pub fn sd(x: &[f64]) -> f64 {
47    var(
48        &DMatrix::from_column_slice(x.len(), 1, x),
49        NAMethod::Everything,
50    )
51    .sqrt()
52}
53
54pub fn var(x: &DMatrix<f64>, na_method: NAMethod) -> f64 {
55    let x = DMatrix::from_column_slice(x.as_slice().len(), 1, x.as_slice());
56    c_cov(&x, Some(&x), na_method, false)[(0, 0)]
57}
58
59pub fn cov(
60    x: &DMatrix<f64>,
61    y: &DMatrix<f64>,
62    method: Method,
63    na_method: NAMethod,
64) -> DMatrix<f64> {
65    if let Method::Pearson = method {
66        c_cov(x, Some(y), na_method, false)
67    } else if na_method == NAMethod::CompleteObs || na_method == NAMethod::NAOrComplete {
68        let nas = x
69            .row_iter()
70            .zip(y.row_iter())
71            .enumerate()
72            .filter_map(|(i, (x, y))| {
73                if x.iter().any(|x_i| x_i.is_nan()) || y.iter().any(|y_i| y_i.is_nan()) {
74                    Some(i)
75                } else {
76                    None
77                }
78            })
79            .collect::<Vec<_>>();
80
81        let mut r_x = x.clone().remove_rows_at(&nas);
82        for mut column in r_x.column_iter_mut() {
83            for (&rank, x_i) in column.as_slice().rank().iter().zip(column.iter_mut()) {
84                *x_i = rank;
85            }
86        }
87
88        let mut r_y = y.clone().remove_rows_at(&nas);
89        for mut column in r_y.column_iter_mut() {
90            for (&rank, y_i) in column.as_slice().rank().iter().zip(column.iter_mut()) {
91                *y_i = rank;
92            }
93        }
94
95        c_cov(&r_x, Some(&r_y), na_method, method == Method::Kendall)
96    } else if na_method != NAMethod::PairwiseCompleteObs {
97        let mut r_x = x.clone();
98        for mut column in r_x.column_iter_mut() {
99            for (&rank, x_i) in column.as_slice().rank().iter().zip(column.iter_mut()) {
100                *x_i = rank;
101            }
102        }
103
104        let mut r_y = y.clone();
105        for mut column in r_y.column_iter_mut() {
106            for (&rank, y_i) in column.as_slice().rank().iter().zip(column.iter_mut()) {
107                *y_i = rank;
108            }
109        }
110
111        c_cov(&r_x, Some(&r_y), na_method, method == Method::Kendall)
112    } else {
113        panic!("Cannot handle Pairwise Complete Obs")
114    }
115}
116
117pub fn cor(
118    x: &DMatrix<f64>,
119    y: &DMatrix<f64>,
120    na_method: NAMethod,
121    method: Method,
122) -> DMatrix<f64> {
123    if method == Method::Pearson {
124        c_cor(x, Some(y), na_method, false)
125    } else if na_method == NAMethod::CompleteObs || na_method == NAMethod::NAOrComplete {
126        let nas = x
127            .row_iter()
128            .zip(y.row_iter())
129            .enumerate()
130            .filter_map(|(i, (x, y))| {
131                if x.iter().any(|x_i| x_i.is_nan()) || y.iter().any(|y_i| y_i.is_nan()) {
132                    Some(i)
133                } else {
134                    None
135                }
136            })
137            .collect::<Vec<_>>();
138
139        let mut r_x = x.clone().remove_rows_at(&nas);
140        for mut column in r_x.column_iter_mut() {
141            for (&rank, x_i) in column.as_slice().rank().iter().zip(column.iter_mut()) {
142                *x_i = rank;
143            }
144        }
145
146        let mut r_y = y.clone().remove_rows_at(&nas);
147        for mut column in r_y.column_iter_mut() {
148            for (&rank, y_i) in column.as_slice().rank().iter().zip(column.iter_mut()) {
149                *y_i = rank;
150            }
151        }
152
153        c_cor(&r_x, Some(&r_y), na_method, method == Method::Kendall)
154    } else if na_method != NAMethod::PairwiseCompleteObs {
155        let mut r_x = x.clone();
156        for mut column in r_x.column_iter_mut() {
157            for (&rank, x_i) in column.as_slice().rank().iter().zip(column.iter_mut()) {
158                *x_i = rank;
159            }
160        }
161
162        let mut r_y = y.clone();
163        for mut column in r_y.column_iter_mut() {
164            for (&rank, y_i) in column.as_slice().rank().iter().zip(column.iter_mut()) {
165                *y_i = rank;
166            }
167        }
168
169        c_cor(&r_x, Some(&r_y), na_method, method == Method::Kendall)
170    } else {
171        let mut r = DMatrix::zeros(x.ncols(), y.ncols());
172        for i in 0..x.ncols() {
173            for j in 0..y.ncols() {
174                let mut x2 = x.column(i).iter().cloned().collect::<Vec<_>>();
175                let mut y2 = y.column(j).iter().cloned().collect::<Vec<_>>();
176                let nas = x2
177                    .iter()
178                    .zip(x2.iter())
179                    .enumerate()
180                    .filter_map(|(i, (x_i, y_i))| {
181                        if x_i.is_nan() || y_i.is_nan() {
182                            Some(i)
183                        } else {
184                            None
185                        }
186                    })
187                    .collect::<Vec<_>>();
188                x2 = x2
189                    .into_iter()
190                    .enumerate()
191                    .filter_map(|(i, x_i)| if nas.contains(&i) { None } else { Some(x_i) })
192                    .collect::<Vec<_>>()
193                    .rank();
194                y2 = y2
195                    .into_iter()
196                    .enumerate()
197                    .filter_map(|(i, y_i)| if nas.contains(&i) { None } else { Some(y_i) })
198                    .collect::<Vec<_>>()
199                    .rank();
200                r[(i, j)] = if !x2.is_empty() && !y2.is_empty() {
201                    c_cor(
202                        &DMatrix::from_vec(x2.len(), 1, x2),
203                        Some(&DMatrix::from_vec(y2.len(), 1, y2)),
204                        NAMethod::AllObs,
205                        method == Method::Kendall,
206                    )[(0, 0)]
207                } else {
208                    f64::nan()
209                };
210            }
211        }
212        r
213    }
214}
215
216fn c_cov(
217    x: &DMatrix<f64>,
218    y: Option<&DMatrix<f64>>,
219    na_method: NAMethod,
220    kendall: bool,
221) -> DMatrix<f64> {
222    corcov(x, y, na_method, kendall, false)
223}
224
225fn c_cor(
226    x: &DMatrix<f64>,
227    y: Option<&DMatrix<f64>>,
228    na_method: NAMethod,
229    kendall: bool,
230) -> DMatrix<f64> {
231    corcov(x, y, na_method, kendall, true)
232}
233
234fn corcov(
235    x: &DMatrix<f64>,
236    y: Option<&DMatrix<f64>>,
237    na_method: NAMethod,
238    kendall: bool,
239    cor: bool,
240) -> DMatrix<f64> {
241    let mut na_fail = false;
242    let mut everything = false;
243    let mut empty_err = true;
244    let mut pair = false;
245    match na_method {
246        NAMethod::AllObs => na_fail = true,
247        NAMethod::CompleteObs => {}
248        NAMethod::PairwiseCompleteObs => pair = true,
249        NAMethod::Everything => {
250            everything = true;
251            empty_err = false;
252        }
253        NAMethod::NAOrComplete => empty_err = false,
254    };
255
256    if let Some(y) = y {
257        if everything {
258            cov_na_2(x, y, cor, kendall)
259        } else if !pair {
260            let ind = complete2(x, y, na_fail);
261            if empty_err && !ind.iter().any(|&i| i == 1.0) {
262                panic!("No complete element pairs");
263            }
264            cov_complete2(x, y, &ind, cor, kendall)
265        } else {
266            cov_pairwise2(x, y, cor, kendall)
267        }
268    } else if everything {
269        cov_na_2(x, x, cor, kendall)
270    } else if !pair {
271        let ind = complete2(x, x, na_fail);
272        if empty_err && !ind.iter().any(|&i| i == 1.0) {
273            panic!("No complete element pairs");
274        }
275        cov_complete2(x, x, &ind, cor, kendall)
276    } else {
277        cov_pairwise2(x, x, cor, kendall)
278    }
279}
280
281fn mean_2(x: &DMatrix<f64>, has_na: &[bool]) -> Vec<f64> {
282    let mut xm = vec![0.0; x.nrows()];
283    for i in 0..x.ncols() {
284        let mut tmp;
285        if has_na[i] {
286            tmp = f64::nan();
287        } else {
288            let xx = &x.column(i);
289            let mut sum = 0.0;
290            for k in 0..x.nrows() {
291                sum += xx[k];
292            }
293            tmp = sum / x.nrows() as f64;
294            if tmp.is_finite() {
295                sum = 0.;
296                for k in 0..x.nrows() {
297                    sum += xx[k] - tmp;
298                }
299                tmp += sum / x.nrows() as f64;
300            }
301        }
302        xm[i] = tmp;
303    }
304    xm
305}
306
307fn cov_na_2(x: &DMatrix<f64>, y: &DMatrix<f64>, cor: bool, kendall: bool) -> DMatrix<f64> {
308    let mut ans = DMatrix::repeat(x.ncols(), y.ncols(), f64::nan());
309
310    let has_na_x = x
311        .column_iter()
312        .map(|c| c.iter().any(|c_i| c_i.is_nan()))
313        .collect::<Vec<_>>();
314    let has_na_y = y
315        .column_iter()
316        .map(|c| c.iter().any(|c_i| c_i.is_nan()))
317        .collect::<Vec<_>>();
318
319    let n = x.nrows();
320    let n1 = n - 1;
321
322    let mut xm = if kendall {
323        vec![0.0; x.ncols()]
324    } else {
325        mean_2(x, &has_na_x)
326    };
327    let mut ym = if kendall {
328        vec![0.0; x.ncols()]
329    } else {
330        mean_2(y, &has_na_y)
331    };
332
333    for i in 0..x.ncols() {
334        if has_na_x[i] {
335            for j in 0..y.ncols() {
336                ans[(i, j)] = f64::nan();
337            }
338        } else {
339            let xx = x.column(i);
340            if !kendall {
341                let xxm = xm[i];
342                for j in 0..y.ncols() {
343                    if has_na_y[j] {
344                        ans[(i, j)] = f64::nan();
345                    } else {
346                        let yy = y.column(j);
347                        let yym = ym[j];
348                        let mut sum = 0.0;
349                        for k in 0..n {
350                            sum += (xx[k] - xxm) * (yy[k] - yym);
351                        }
352                        ans[(i, j)] = sum / n1 as f64;
353                    }
354                }
355            } else {
356                for j in 0..y.ncols() {
357                    if has_na_y[j] {
358                        ans[(i, j)] = f64::nan();
359                    } else {
360                        let yy = y.column(j);
361                        let mut sum = 0.0;
362                        for k in 0..n {
363                            for n1 in 0..n {
364                                sum += sign(xx[k] - xx[n1]) * sign(yy[k] - yy[n1]);
365                            }
366                        }
367                        ans[(i, j)] = sum;
368                    }
369                }
370            }
371        }
372    }
373
374    if cor {
375        let cov_sdev = |x: &DMatrix<f64>, has_na_x: &[bool], xm: &mut [f64]| {
376            for i in 0..x.ncols() {
377                if !has_na_x[i] {
378                    let xx = x.column(i);
379                    let mut sum = 0.0;
380                    if !kendall {
381                        let xxm = xm[i];
382                        for k in 0..n {
383                            sum += (xx[k] - xxm) * (xx[k] - xxm);
384                        }
385                        sum /= n1 as f64;
386                    } else {
387                        for k in 0..n {
388                            for n1 in 0..n {
389                                if xx[k] != xx[n1] {
390                                    sum += 1.0;
391                                }
392                            }
393                        }
394                    }
395                    xm[i] = sum.sqrt();
396                }
397            }
398        };
399
400        cov_sdev(x, &has_na_x, &mut xm);
401        cov_sdev(y, &has_na_y, &mut ym);
402
403        let mut _sd_0 = false;
404        for i in 0..x.ncols() {
405            if !has_na_x[i] {
406                for j in 0..y.ncols() {
407                    if !has_na_y[j] {
408                        if xm[i] == 0.0 || ym[j] == 0.0 {
409                            _sd_0 = true;
410                            ans[(i, j)] = f64::nan();
411                        } else {
412                            ans[(i, j)] /= xm[i] * ym[j];
413                            ans[(i, j)] = clamp(ans[(i, j)]);
414                        }
415                    }
416                }
417            }
418        }
419    }
420
421    ans
422}
423
424fn complete2(x: &DMatrix<f64>, y: &DMatrix<f64>, na_fail: bool) -> Vec<f64> {
425    let mut ind = vec![1.0; x.nrows()];
426    for j in 0..x.ncols() {
427        let z = x.column(j);
428        for i in 0..x.nrows() {
429            if z[i].is_nan() {
430                if na_fail {
431                    panic!("Missing observations in cov/cor")
432                } else {
433                    ind[i] = 0.0;
434                }
435            }
436        }
437    }
438    for j in 0..y.ncols() {
439        let z = y.column(j);
440        for i in 0..x.nrows() {
441            if z[i].is_nan() {
442                if na_fail {
443                    panic!("Missing observations in cov/cor")
444                } else {
445                    ind[i] = 0.0;
446                }
447            }
448        }
449    }
450    ind
451}
452
453fn cov_complete2(
454    x: &DMatrix<f64>,
455    y: &DMatrix<f64>,
456    ind: &[f64],
457    cor: bool,
458    kendall: bool,
459) -> DMatrix<f64> {
460    let mut ans = DMatrix::repeat(x.ncols(), y.ncols(), f64::nan());
461
462    let n = x.nrows();
463    let n1 = n - 1;
464    let mut xm = x
465        .column_iter()
466        .map(|c| c.as_slice().mean())
467        .collect::<Vec<_>>();
468    let mut ym = y
469        .column_iter()
470        .map(|c| c.as_slice().mean())
471        .collect::<Vec<_>>();
472
473    for i in 0..x.ncols() {
474        let xx = x.column(i);
475        let xxm = xm[i];
476        for j in 0..y.ncols() {
477            let yy = y.column(j);
478            let mut sum = 0.0;
479            if !kendall {
480                let yym = ym[j];
481                for k in 0..n {
482                    if ind[k] != 0.0 {
483                        sum += (xx[k] - xxm) * (yy[k] - yym);
484                    }
485                }
486                ans[(i, j)] = sum / n1 as f64;
487            } else {
488                for k in 0..n {
489                    if ind[k] != 0.0 {
490                        for n1 in 0..n {
491                            if ind[n1] != 0.0 {
492                                sum += sign(xx[k] - xx[n1]) * sign(yy[k] - yy[n1]);
493                            }
494                        }
495                    }
496                }
497                ans[(i, j)] = sum;
498            }
499        }
500    }
501
502    if cor {
503        let cov_sdev = |x: &DMatrix<f64>, xm: &mut [f64], ind: &[f64]| {
504            for (i, xx) in x.column_iter().enumerate() {
505                let mut sum = 0.0;
506                if !kendall {
507                    let xxm = xm[i];
508                    for k in 0..n {
509                        if ind[k] != 0.0 {
510                            sum += (xx[k] - xxm) * (xx[k] - xxm);
511                        }
512                    }
513                    sum /= n1 as f64;
514                } else {
515                    for k in 0..n {
516                        if ind[k] != 0.0 {
517                            for n1 in 0..n {
518                                if ind[n1] != 0.0 && xx[k] != xx[n1] {
519                                    sum += 1.0;
520                                }
521                            }
522                        }
523                    }
524                }
525                xm[i] = sum.sqrt();
526            }
527        };
528
529        cov_sdev(x, &mut xm, ind);
530        cov_sdev(y, &mut ym, ind);
531
532        let mut _sd_0 = false;
533        for i in 0..x.ncols() {
534            for j in 0..y.ncols() {
535                if xm[i] == 0.0 || ym[j] == 0.0 {
536                    _sd_0 = true;
537                    ans[(i, j)] = f64::nan();
538                } else {
539                    ans[(i, j)] /= xm[i] * ym[j];
540                    ans[(i, j)] = clamp(ans[(i, j)]);
541                }
542            }
543        }
544    }
545
546    ans
547}
548
549fn cov_pairwise2(x: &DMatrix<f64>, y: &DMatrix<f64>, cor: bool, kendall: bool) -> DMatrix<f64> {
550    let mut ans = DMatrix::repeat(x.ncols(), y.ncols(), f64::nan());
551    let mut _sd_0 = false;
552
553    let n = x.nrows();
554    let mut n1 = n - 1;
555
556    for i in 0..x.ncols() {
557        let xx = x.column(i);
558        for j in 0..y.ncols() {
559            let yy = y.column(j);
560            let mut xmean = 0.0;
561            let mut ymean = 0.0;
562
563            let mut nobs = 0;
564            for k in 0..n {
565                if !xx[k].is_nan() && !yy[k].is_nan() {
566                    nobs += 1;
567                    if !kendall {
568                        xmean += xx[k];
569                        ymean += yy[k];
570                    }
571                }
572            }
573
574            if nobs >= 2 {
575                let mut xsd = 0.0;
576                let mut ysd = 0.0;
577                let mut sum = 0.0;
578
579                if !kendall {
580                    xmean /= nobs as f64;
581                    ymean /= nobs as f64;
582                    n1 = nobs - 1;
583                }
584
585                for k in 0..n {
586                    if !xx[k].is_nan() && !yy[k].is_nan() {
587                        if !kendall {
588                            let xm = xx[k] - xmean;
589                            let ym = yy[k] - ymean;
590
591                            sum += xm * ym;
592                            if cor {
593                                xsd += xm * xm;
594                                ysd += ym * ym;
595                            }
596                        } else {
597                            for n1 in 0..k {
598                                if !xx[n1].is_nan() && !yy[n1].is_nan() {
599                                    let xm = sign(xx[k] - xx[n1]);
600                                    let ym = sign(yy[k] - yy[n1]);
601
602                                    sum += xm * ym;
603                                    if cor {
604                                        xsd += xm * xm;
605                                        ysd += ym * ym;
606                                    }
607                                }
608                            }
609                        }
610                    }
611                }
612
613                if cor {
614                    if xsd == 0.0 || ysd == 0.0 {
615                        _sd_0 = true;
616                        sum = f64::nan();
617                    } else {
618                        if !kendall {
619                            xsd /= n1 as f64;
620                            ysd /= n1 as f64;
621                            sum /= n1 as f64;
622                        }
623                        sum /= xsd.sqrt() * ysd.sqrt();
624                        sum = clamp(sum);
625                    }
626                } else if !kendall {
627                    sum /= n1 as f64;
628                }
629
630                ans[(i, j)] = sum;
631            } else {
632                ans[(i, j)] = f64::nan();
633            }
634        }
635    }
636
637    ans
638}
639
640pub fn cov2cor(v: &DMatrix<f64>) -> DMatrix<f64> {
641    let is = v
642        .diagonal()
643        .iter()
644        .map(|i| (1.0 / i).sqrt())
645        .collect::<Vec<_>>();
646
647    let mut ret = v.clone();
648    ret.column_iter_mut()
649        .for_each(|mut c| c.iter_mut().zip(is.iter()).for_each(|(c_i, i)| *c_i *= i));
650    ret.row_iter_mut()
651        .for_each(|mut r| r.iter_mut().zip(is.iter()).for_each(|(r_i, i)| *r_i *= i));
652
653    ret
654}