plotters-statistical 0.1.0

Statistical chart primitives (box, violin, ROC, PR, regularization-path, residual) as native plotters series
Documentation
//! Regularization path rendered to `regularization_path.svg`.
//!
//! Uses a log-scale x-axis (the conventional way to show a strength sweep) and a
//! per-feature legend. Several coefficients shrink to exactly zero at different
//! strengths, so the zero-crossing markers have something to demonstrate.

use plotters::prelude::*;
use plotters_statistical::RegularizationPath;

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

    // Log-spaced regularization strengths from 1e-3 to 1e1.
    let n = 30;
    let strengths: Vec<f64> = (0..n)
        .map(|i| {
            let t = i as f64 / (n as f64 - 1.0);
            10f64.powf(-3.0 + 4.0 * t)
        })
        .collect();

    // (name, base coefficient, strength at which it hits zero). L1-style: each
    // coefficient decays linearly then clamps at zero past its threshold.
    let specs = [
        ("x1", 3.0f64, 3.0f64),
        ("x2", -2.0, 0.3),
        ("x3", 1.5, 1.0),
        ("x4", 2.5, 0.05),
    ];
    let coefficients: Vec<Vec<f64>> = strengths
        .iter()
        .map(|&s| {
            specs
                .iter()
                .map(|&(_, base, thr)| base * (1.0 - s / thr).max(0.0))
                .collect()
        })
        .collect();

    let mut chart = ChartBuilder::on(&root)
        .caption("Regularization path (L1)", ("sans-serif", 24))
        .margin(20)
        .set_label_area_size(LabelAreaPosition::Left, 50)
        .set_label_area_size(LabelAreaPosition::Bottom, 45)
        .build_cartesian_2d((1e-3f64..1e1f64).log_scale(), -2.5f64..3.5f64)?;

    chart
        .configure_mesh()
        .x_desc("regularization strength (log)")
        .y_desc("coefficient")
        .draw()?;

    let path = RegularizationPath::new(&strengths, &coefficients)?
        .feature_names(specs.iter().map(|s| s.0))
        .stroke_width(2);

    // One draw_series call per line gives each feature its own legend entry.
    for line in path.lines() {
        let color = line.color();
        let name = line.name().unwrap_or_default().to_string();
        chart
            .draw_series(std::iter::once(line))?
            .label(name)
            .legend(move |(x, y)| PathElement::new(vec![(x, y), (x + 18, y)], color));
    }

    chart
        .configure_series_labels()
        .position(SeriesLabelPosition::UpperRight)
        .border_style(BLACK)
        .background_style(WHITE.mix(0.85))
        .draw()?;

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