plotters-statistical 0.2.0

Statistical chart primitives (box, violin, ROC, PR, regularization-path, residual) as native plotters series
Documentation
//! Rendering-layer tests for the v0.2 chart types (series + figures).

use plotters::coord::Shift;
use plotters::prelude::*;
use plotters_statistical::{
    figures::CorrelationHeatmap, figures::MissingnessHeatmap, figures::PairPlot, CalibrationCurve,
    Ecdf, GainChart, Heatmap, QqPlot,
};

fn render<F>(f: F) -> String
where
    F: FnOnce(&DrawingArea<SVGBackend, Shift>) -> Result<(), Box<dyn std::error::Error>>,
{
    let mut buf = String::new();
    {
        let root = SVGBackend::with_string(&mut buf, (500, 400)).into_drawing_area();
        root.fill(&WHITE).unwrap();
        f(&root).unwrap();
        root.present().unwrap();
    }
    buf
}

fn has_shape(svg: &str) -> bool {
    [
        "<rect",
        "<circle",
        "<polygon",
        "<polyline",
        "<path",
        "<line",
        "<text",
    ]
    .iter()
    .any(|t| svg.contains(t))
}

fn assert_ok(svg: &str) {
    assert!(svg.contains("<svg") && svg.contains("</svg>"));
    assert!(has_shape(svg), "no drawn content");
}

#[test]
fn renders_ecdf_with_band() {
    let svg = render(|root| {
        let mut chart = ChartBuilder::on(root).build_cartesian_2d(0f64..10f64, 0f64..1f64)?;
        let data: Vec<f64> = (0..40)
            .map(|i| (i as f64 * 0.25).sin() * 3.0 + 5.0)
            .collect();
        chart.draw_series(std::iter::once(
            Ecdf::from_data(&data)?.confidence_band(0.05).markers(true),
        ))?;
        Ok(())
    });
    assert_ok(&svg);
}

#[test]
fn renders_qq_plot() {
    let svg = render(|root| {
        let mut chart = ChartBuilder::on(root).build_cartesian_2d(-3f64..3f64, -3f64..3f64)?;
        let data: Vec<f64> = (0..60)
            .map(|i| ((i as f64 + 0.5) / 60.0 - 0.5) * 4.0)
            .collect();
        chart.draw_series(std::iter::once(QqPlot::from_data(&data)?))?;
        Ok(())
    });
    assert_ok(&svg);
}

#[test]
fn renders_calibration_curve() {
    let svg = render(|root| {
        let mut chart = ChartBuilder::on(root).build_cartesian_2d(0f64..1f64, 0f64..1f64)?;
        let scores: Vec<f64> = (0..100).map(|i| i as f64 / 100.0).collect();
        let labels: Vec<bool> = scores.iter().map(|&s| s > 0.5).collect();
        chart.draw_series(std::iter::once(CalibrationCurve::from_scores(
            &scores, &labels, 10,
        )?))?;
        Ok(())
    });
    assert_ok(&svg);
}

#[test]
fn renders_gain_chart() {
    use plotters_statistical::series::gain_chart::GainMode;
    let svg = render(|root| {
        let mut chart = ChartBuilder::on(root).build_cartesian_2d(0f64..1f64, 0f64..3f64)?;
        let scores: Vec<f64> = (0..100)
            .map(|i| (i % 7) as f64 + i as f64 / 100.0)
            .collect();
        let labels: Vec<bool> = (0..100).map(|i| i % 3 == 0).collect();
        chart.draw_series(std::iter::once(
            GainChart::from_scores(&scores, &labels)?.mode(GainMode::Lift),
        ))?;
        Ok(())
    });
    assert_ok(&svg);
}

#[test]
fn renders_heatmap_series_annotated() {
    use plotters_statistical::series::heatmap::HeatmapAnnotation;
    let svg = render(|root| {
        let values = vec![vec![0.1, 0.5, 0.9], vec![0.4, 0.2, 0.7]];
        let mut chart =
            ChartBuilder::on(root).build_cartesian_2d(-0.5f64..2.5f64, -0.5f64..1.5f64)?;
        chart.draw_series(std::iter::once(
            Heatmap::new(&values).annotate(HeatmapAnnotation::default()),
        ))?;
        Ok(())
    });
    assert_ok(&svg);
}

#[test]
fn renders_correlation_heatmap_figure() {
    let svg = render(|root| {
        let cols = vec![
            vec![1.0, 2.0, 3.0, 4.0, 5.0],
            vec![2.0, 4.0, 5.0, 4.0, 5.0],
            vec![5.0, 4.0, 3.0, 2.0, 1.0],
        ];
        let labels = vec!["a".to_string(), "b".to_string(), "c".to_string()];
        use plotters_statistical::stats::CorrelationMethod;
        CorrelationHeatmap::from_columns(&cols, labels, CorrelationMethod::Pearson)?
            .title("corr")
            .draw(root)?;
        Ok(())
    });
    assert_ok(&svg);
}

#[test]
fn renders_missingness_heatmap_figure() {
    let svg = render(|root| {
        let cols = vec![
            vec![Some(1.0), None, Some(3.0), Some(4.0)],
            vec![Some(1.0), Some(2.0), None, None],
        ];
        let labels = vec!["x".to_string(), "y".to_string()];
        MissingnessHeatmap::from_columns(&cols, labels)?.draw(root)?;
        Ok(())
    });
    assert_ok(&svg);
}

#[test]
fn renders_pair_plot_figure() {
    let svg = render(|root| {
        let cols = vec![
            (0..40).map(|i| i as f64).collect::<Vec<_>>(),
            (0..40).map(|i| (i as f64).sin()).collect::<Vec<_>>(),
        ];
        let labels = vec!["t".to_string(), "sin".to_string()];
        PairPlot::new(cols, labels).draw(root)?;
        Ok(())
    });
    assert_ok(&svg);
}