use super::helpers::{chi_squared_p_value, fit_ols};
use super::types::DiagnosticTestResult;
use crate::error::{Error, Result};
use crate::linalg::{vec_mean, Matrix};
pub fn breusch_pagan_test(y: &[f64], x_vars: &[Vec<f64>]) -> Result<DiagnosticTestResult> {
let n = y.len();
let k = x_vars.len(); let p = k + 1;
if n <= p {
return Err(Error::InsufficientData {
required: p + 1,
available: n,
});
}
super::helpers::validate_regression_data(y, x_vars)?;
let mut x_data = vec![1.0; n * p];
for row in 0..n {
x_data[row * p] = 1.0; for (col, x_var) in x_vars.iter().enumerate() {
x_data[row * p + col + 1] = x_var[row];
}
}
let x_full = Matrix::new(n, p, x_data);
let beta = fit_ols(y, &x_full)?;
let predictions = x_full.mul_vec(&beta);
let residuals: Vec<f64> = y
.iter()
.zip(predictions.iter())
.map(|(&yi, &yi_hat)| yi - yi_hat)
.collect();
let rss: f64 = residuals.iter().map(|&r| r * r).sum();
let sigma2 = rss / (n as f64);
let u: Vec<f64> = residuals.iter().map(|&e| (e * e) / sigma2).collect();
let u_mean = vec_mean(&u);
if u_mean <= 0.0 || !u_mean.is_finite() {
return Err(Error::InvalidInput(
"Invalid mean of normalized squared residuals".to_string(),
));
}
let y_aux: Vec<f64> = u.iter().map(|&ui| ui / u_mean).collect();
let beta_aux = fit_ols(&y_aux, &x_full)?;
let pred_aux = x_full.mul_vec(&beta_aux);
let mean_y_aux = vec_mean(&y_aux);
let mut tss_aux = 0.0;
let mut regss_aux = 0.0;
for i in 0..n {
let diff = y_aux[i] - mean_y_aux;
tss_aux += diff * diff;
let pred_diff = pred_aux[i] - mean_y_aux;
regss_aux += pred_diff * pred_diff;
}
let r_squared_aux = if tss_aux > 1e-10 {
regss_aux / tss_aux
} else {
0.0
};
let r_squared_aux = r_squared_aux.clamp(0.0, 1.0);
let lm_stat = (n as f64) * r_squared_aux;
let df = k as f64;
let p_value = chi_squared_p_value(lm_stat, df);
let alpha = 0.05;
let passed = p_value > alpha;
let (interpretation, guidance) = if passed {
(
format!(
"p-value = {:.4} is greater than {:.2}. Cannot reject H0. No significant evidence of heteroscedasticity.",
p_value, alpha
),
"The assumption of homoscedasticity (constant variance) appears to be met."
)
} else {
(
format!(
"p-value = {:.4} is less than or equal to {:.2}. Reject H0. Significant evidence of heteroscedasticity detected.",
p_value, alpha
),
"Consider transforming the dependent variable (e.g., log transformation), using weighted least squares, or robust standard errors."
)
};
Ok(DiagnosticTestResult {
test_name: "Breusch-Pagan Test for Heteroscedasticity".to_string(),
statistic: lm_stat,
p_value,
passed,
interpretation,
guidance: guidance.to_string(),
})
}