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 ndarray::Array1;

use crate::residuals::internally_studentized_residuals;
use crate::OlsFit;

/// Cook's distance `Dᵢ` for each observation.
///
/// `Dᵢ = (rᵢ² / p) · (hᵢ / (1 − hᵢ))`
///
/// where `rᵢ` is the internally studentized residual and `hᵢ` the leverage —
/// reusing the scaled residuals from [`crate::residuals`] rather than
/// recomputing. Cook's distance rolls **residual size and leverage** into a
/// single per-observation influence measure: it is large only when an
/// observation is both poorly fit *and* has unusual predictor values.
///
/// A commonly cited flag is `Dᵢ > 4/n` (convention, not a hard rule).
///
/// Cook's distance and [`dffits`](super::dffits) usually agree on which points
/// are influential; they can diverge because DFFITS uses the *externally*
/// studentized residual (excluding the point from its own scale), making it
/// somewhat more sensitive to a single extreme outlier.
pub fn cooks_distance(fit: &OlsFit) -> Array1<f64> {
    let p = fit.n_parameters() as f64;
    let r = internally_studentized_residuals(fit);
    let lev = fit.leverage();

    Array1::from_shape_fn(fit.n_observations(), |i| {
        let h = lev[i];
        let one_minus_h = 1.0 - h;
        if one_minus_h <= 0.0 || p <= 0.0 {
            return f64::NAN;
        }
        (r[i] * r[i] / p) * (h / one_minus_h)
    })
}