use ndarray::*;
use num_traits::Float;
use statrs::distribution::{ChiSquared, Univariate};
use crate::cast::*;
pub struct Chi2Fit {
x: Vec<usize>,
pub n: usize,
pub k: usize,
dist: ChiSquared,
}
pub struct Chi2Indep {
cross: Array2<usize>,
xi: Array1<usize>,
xj: Array1<usize>,
pub n: usize,
pub r: usize,
pub c: usize,
dist: ChiSquared,
}
impl Chi2Fit {
pub fn new(x: &[usize]) -> Self {
let x = x.to_owned();
let n = x.iter().sum();
let k = x.len();
let dist = ChiSquared::new(k as f64 - 1.0).unwrap();
Self { x, n, k, dist }
}
pub fn chi2_fit(&self, pi: &[f64]) -> f64 {
if self.k != pi.len() {
panic!()
}
let q = self.x.iter()
.zip(pi.iter())
.map(|(&xi, pii)| {
let npii = self.n as f64 * pii;
let xnpii = xi as f64 - npii;
xnpii * xnpii / npii
})
.sum::<f64>();
1.0 - self.dist.cdf(q)
}
}
impl Chi2Indep {
pub fn new<S: Data<Elem = usize>>(cross: &ArrayBase<S, Ix2>) -> Self {
let cross = cross.to_owned();
let xi = cross.sum_axis(Axis(1));
let xj = cross.sum_axis(Axis(0));
let n = xi.sum_axis(Axis(0)).into_scalar();
let r = xi.len();
let c = xj.len();
let dist = ChiSquared::new(((r - 1) * (c - 1)) as f64).unwrap();
Self { cross, xi, xj, n, r, c, dist }
}
pub fn chi2_indep(&self) -> f64 {
let mut q_mat = Array2::zeros((self.r, self.c));
Zip::from(&mut q_mat)
.and(&self.cross)
.and(&self.xi.broadcast((self.c, self.r)).unwrap().t())
.and(&self.xj.broadcast((self.r, self.c)).unwrap())
.apply(|a, &b, &c, &d| {
let ijn = (c * d) as f64 / self.n as f64;
let nume = b as f64 - ijn;
*a = nume * nume / ijn;
});
let q = q_mat.sum();
self.dist.cdf(q)
}
}
pub trait Chi2Vec<T: Float> {
fn chi2_fit(&self, pi: &[T], pi: T) -> bool;
}
pub trait Chi2Array<T: Float> {
fn chi2_indep(&self, p: T) -> bool;
}
impl Chi2Vec<f64> for Vec<usize> {
fn chi2_fit(&self, pi: &[f64], p: f64) -> bool {
chi2_fit(&self, pi, p)
}
}
impl Chi2Vec<f32> for Vec<usize> {
fn chi2_fit(&self, pi: &[f32], p: f32) -> bool {
chi2_fit(&self, &pi.to_vec_f64(), p as f64)
}
}
impl<S: Data<Elem = usize>> Chi2Vec<f64> for ArrayBase<S, Ix1> {
fn chi2_fit(&self, pi: &[f64], p: f64) -> bool {
self.to_vec().chi2_fit(pi, p)
}
}
impl<S: Data<Elem = usize>> Chi2Vec<f32> for ArrayBase<S, Ix1> {
fn chi2_fit(&self, pi: &[f32], p: f32) -> bool {
self.to_vec().chi2_fit(pi, p)
}
}
impl<S: Data<Elem = usize>, T: Float> Chi2Array<T> for ArrayBase<S, Ix2> {
fn chi2_indep(&self, p: T) -> bool {
chi2_indep(&self, p.to_f64().unwrap())
}
}
fn chi2_fit(x: &[usize], pi: &[f64], p: f64) -> bool {
let chi2f = Chi2Fit::new(x);
if 1.0 - chi2f.chi2_fit(pi) < p {
false
} else {
true
}
}
fn chi2_indep<S: Data<Elem = usize>>(cross: &ArrayBase<S, Ix2>, p: f64) -> bool {
let chi2i = Chi2Indep::new(cross);
if 1.0 - chi2i.chi2_indep() < p {
true
} else {
false
}
}