plotters-statistical 0.1.0

Statistical chart primitives (box, violin, ROC, PR, regularization-path, residual) as native plotters series
Documentation
//! Residual plot rendered to `residual_plot.svg`.
//!
//! The synthetic residuals deliberately trend upward and fan out with the
//! fitted value — non-random structure a good model would not have — so the
//! zero line and the moving-average trend line both earn their place.

use plotters::prelude::*;
use plotters_statistical::ResidualPlot;

fn noise(i: usize) -> f64 {
    // Deterministic pseudo-random value in [-1, 1).
    (((i as f64 * 78.233).sin() * 43758.5453).rem_euclid(1.0)) * 2.0 - 1.0
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let root = SVGBackend::new("residual_plot.svg", (760, 520)).into_drawing_area();
    root.fill(&WHITE)?;

    let n = 220;
    let mut fitted = Vec::with_capacity(n);
    let mut residuals = Vec::with_capacity(n);
    for i in 0..n {
        let x = 10.0 * i as f64 / (n as f64 - 1.0);
        // Upward trend + variance that grows with x (heteroscedastic fan-out).
        let r = 0.45 * (x - 5.0) + noise(i) * (0.6 + 0.35 * x);
        fitted.push(x);
        residuals.push(r);
    }

    let mut chart = ChartBuilder::on(&root)
        .caption("Residual plot (structured residuals)", ("sans-serif", 22))
        .margin(20)
        .set_label_area_size(LabelAreaPosition::Left, 50)
        .set_label_area_size(LabelAreaPosition::Bottom, 45)
        .build_cartesian_2d(0f64..10f64, -8f64..8f64)?;

    chart
        .configure_mesh()
        .x_desc("fitted value")
        .y_desc("residual")
        .draw()?;

    chart.draw_series(std::iter::once(
        ResidualPlot::from_residuals(&fitted, &residuals)?.trend(true),
    ))?;

    root.present()?;
    println!("wrote residual_plot.svg");
    Ok(())
}