use super::{lm_heteroskedasticity_test, non_intercept_columns};
use crate::OlsFit;
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct WhiteTest {
pub statistic: f64,
pub df: usize,
pub p_value: f64,
}
pub fn white_test(fit: &OlsFit) -> WhiteTest {
let n = fit.n_observations();
let cols = non_intercept_columns(fit);
let x = fit.design_matrix();
let base: Vec<Vec<f64>> = cols.iter().map(|&j| x.column(j).to_vec()).collect();
let k = base.len();
let mut regressors: Vec<Vec<f64>> = Vec::new();
for col in &base {
regressors.push(col.clone());
}
for col in &base {
regressors.push(col.iter().map(|v| v * v).collect());
}
for a in 0..k {
for b in (a + 1)..k {
let cross: Vec<f64> = base[a]
.iter()
.zip(base[b].iter())
.map(|(u, v)| u * v)
.collect();
regressors.push(cross);
}
}
let regressors = dedup_regressors(n, regressors);
let resid_sq: Vec<f64> = fit.residuals().iter().map(|e| e * e).collect();
let (statistic, df, p_value) = lm_heteroskedasticity_test(n, ®ressors, &resid_sq);
WhiteTest {
statistic,
df,
p_value,
}
}
fn dedup_regressors(n: usize, cols: Vec<Vec<f64>>) -> Vec<Vec<f64>> {
let mut kept: Vec<Vec<f64>> = Vec::new();
for col in cols {
let first = col[0];
let is_constant = col
.iter()
.all(|&v| (v - first).abs() <= 1e-12 * first.abs().max(1.0));
if is_constant {
continue;
}
let dup = kept
.iter()
.any(|k| (0..n).all(|i| (k[i] - col[i]).abs() <= 1e-12 * col[i].abs().max(1.0)));
if !dup {
kept.push(col);
}
}
kept
}