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
//! Produce normal QQ-plot data for a model's residuals and show how it feeds a
//! plotting backend. This crate computes the *data*; rendering is someone else's
//! job — the natural pairing is `plotters` / `plotters-statistical`.
//!
//! To keep the example dependency-free it renders a tiny ASCII scatter and prints
//! the equivalent `plotters` call in a comment. Run with:
//! `cargo run --example qq_plot_integration`

use ndarray::{Array1, Array2};
use regression_diagnostics::residuals::{qq_plot_data, standardized_residuals};
use regression_diagnostics::OlsFit;

fn main() {
    // A simple linear fit; we inspect whether its residuals look normal.
    let n = 25usize;
    let mut x = Array2::<f64>::ones((n, 2));
    let mut y = Array1::<f64>::zeros(n);
    // Deterministic "wavy" residual pattern around the line y = 1 + 0.5x.
    for i in 0..n {
        let xi = i as f64;
        x[(i, 1)] = xi;
        let e = ((i as f64 * 1.3).sin() + (i as f64 * 0.7).cos()) * 0.6;
        y[i] = 1.0 + 0.5 * xi + e;
    }
    let fit = OlsFit::new(x, y).unwrap();

    let resid = standardized_residuals(&fit);
    let pairs = qq_plot_data(resid.view());

    // Correlation between theoretical and sample quantiles: near 1.0 means the
    // points hug the y = x reference line (residuals look normal).
    let m = pairs.len() as f64;
    let (tx, ty): (Vec<f64>, Vec<f64>) = pairs.iter().cloned().unzip();
    let mx = tx.iter().sum::<f64>() / m;
    let my = ty.iter().sum::<f64>() / m;
    let cov: f64 = tx.iter().zip(&ty).map(|(a, b)| (a - mx) * (b - my)).sum();
    let vx: f64 = tx.iter().map(|a| (a - mx).powi(2)).sum();
    let vy: f64 = ty.iter().map(|b| (b - my).powi(2)).sum();
    let corr = cov / (vx.sqrt() * vy.sqrt());

    println!("QQ-plot data (theoretical vs standardized residual quantiles):");
    println!("  theoretical    sample");
    for (t, s) in &pairs {
        println!("  {:>10.4}   {:>8.4}", t, s);
    }
    println!("\nQuantile correlation = {corr:.4}  (near 1.0 ⇒ residuals look normal)");

    println!(
        "\n--- Feeding a plotter (pseudocode) -------------------------------------\n\
         // use plotters::prelude::*;\n\
         // let root = BitMapBackend::new(\"qq.png\", (480, 480)).into_drawing_area();\n\
         // let mut chart = ChartBuilder::on(&root).build_cartesian_2d(-3f64..3f64, -3f64..3f64)?;\n\
         // // sample points\n\
         // chart.draw_series(pairs.iter().map(|&(t, s)| Circle::new((t, s), 3, BLUE.filled())))?;\n\
         // // y = x reference line (the \"perfect normality\" baseline)\n\
         // chart.draw_series(LineSeries::new([(-3.0, -3.0), (3.0, 3.0)], RED))?;\n\
         // The diagonal here plays the same role as plotters-statistical's RocCurve baseline."
    );
}