regression-diagnostics 0.2.0

Statistical diagnostics for OLS regression in Rust: VIF, condition number, adjusted R2, F/AIC/BIC, residual tests (Durbin-Watson, Breusch-Pagan, White, Jarque-Bera), influence measures (leverage, Cook's distance, DFFITS), QQ-plot data, and an R/statsmodels-style summary().
Documentation
use crate::OlsFit;

/// Variance Inflation Factor for each predictor.
///
/// `VIFⱼ = 1 / (1 − R²ⱼ)`, where `R²ⱼ` is the coefficient of determination from
/// regressing predictor `j` on **all the other predictors** (reusing the fit's
/// own OLS machinery — the diagnostic is computed with the same tool it
/// diagnoses).
///
/// # Return layout
///
/// The returned vector is aligned to the design-matrix columns: entry `j` is the
/// VIF of column `j`. The **intercept column's entry is [`f64::NAN`]** — VIF is
/// undefined for the constant term — so callers can index straight back into the
/// design without an off-by-one. A perfectly collinear predictor yields
/// [`f64::INFINITY`] rather than a silent `NaN`.
///
/// VIF assumes the model contains an intercept (the standard definition); on an
/// intercept-free fit the auxiliary regressions are still computed but the
/// values follow the through-the-origin convention.
///
/// # Example
///
/// ```
/// use ndarray::array;
/// use regression_diagnostics::{OlsFit, multicollinearity::vif};
///
/// // x2 is nearly 2*x1: strong collinearity, so both get a large VIF.
/// let x = array![
///     [1.0, 1.0, 2.01],
///     [1.0, 2.0, 3.99],
///     [1.0, 3.0, 6.02],
///     [1.0, 4.0, 7.98],
///     [1.0, 5.0, 10.01],
/// ];
/// let y = array![1.0, 2.1, 2.9, 4.2, 5.0];
/// let fit = OlsFit::new(x, y).unwrap();
/// let v = vif(&fit);
/// assert!(v[0].is_nan());      // intercept
/// assert!(v[1] > 5.0 && v[2] > 5.0);
/// ```
pub fn vif(fit: &OlsFit) -> Vec<f64> {
    (0..fit.n_parameters())
        .map(|j| match fit.column_on_others_r2(j) {
            None => f64::NAN, // intercept column
            Some(r2) => {
                let denom = 1.0 - r2;
                if denom <= 0.0 {
                    f64::INFINITY
                } else {
                    1.0 / denom
                }
            }
        })
        .collect()
}