blob: 54bffa6146472e4a2ee44c93738a81b66de56fdb [file] [log] [blame]
use super::*;
use std::path::Path;
use crate::estimate::{ConfidenceInterval, Estimate};
use crate::stats::bivariate::regression::Slope;
use crate::stats::bivariate::Data;
pub(crate) fn regression_figure(
title: Option<&str>,
path: &Path,
formatter: &dyn ValueFormatter,
measurements: &MeasurementData<'_>,
size: Option<(u32, u32)>,
) {
let slope_estimate = measurements.absolute_estimates.slope.as_ref().unwrap();
let slope_dist = measurements.distributions.slope.as_ref().unwrap();
let (lb, ub) =
slope_dist.confidence_interval(slope_estimate.confidence_interval.confidence_level);
let data = &measurements.data;
let (max_iters, typical) = (data.x().max(), data.y().max());
let mut scaled_y: Vec<f64> = data.y().iter().cloned().collect();
let unit = formatter.scale_values(typical, &mut scaled_y);
let scaled_y = Sample::new(&scaled_y);
let point_estimate = Slope::fit(&measurements.data).0;
let mut scaled_points = [point_estimate * max_iters, lb * max_iters, ub * max_iters];
let _ = formatter.scale_values(typical, &mut scaled_points);
let [point, lb, ub] = scaled_points;
let exponent = (max_iters.log10() / 3.).floor() as i32 * 3;
let x_scale = 10f64.powi(-exponent);
let x_label = if exponent == 0 {
"Iterations".to_owned()
} else {
format!("Iterations (x 10^{})", exponent)
};
let size = size.unwrap_or(SIZE);
let root_area = SVGBackend::new(path, size).into_drawing_area();
let mut cb = ChartBuilder::on(&root_area);
if let Some(title) = title {
cb.caption(title, (DEFAULT_FONT, 20));
}
let x_range = plotters::data::fitting_range(data.x().iter());
let y_range = plotters::data::fitting_range(scaled_y.iter());
let mut chart = cb
.margin((5).percent())
.set_label_area_size(LabelAreaPosition::Left, (5).percent_width().min(60))
.set_label_area_size(LabelAreaPosition::Bottom, (5).percent_height().min(40))
.build_ranged(x_range, y_range)
.unwrap();
chart
.configure_mesh()
.x_desc(x_label)
.y_desc(format!("Total sample time ({})", unit))
.x_label_formatter(&|x| pretty_print_float(x * x_scale, true))
.line_style_2(&TRANSPARENT)
.draw()
.unwrap();
chart
.draw_series(
data.x()
.iter()
.zip(scaled_y.iter())
.map(|(x, y)| Circle::new((*x, *y), POINT_SIZE, DARK_BLUE.filled())),
)
.unwrap()
.label("Sample")
.legend(|(x, y)| Circle::new((x + 10, y), POINT_SIZE, DARK_BLUE.filled()));
chart
.draw_series(std::iter::once(PathElement::new(
vec![(0.0, 0.0), (max_iters, point)],
&DARK_BLUE,
)))
.unwrap()
.label("Linear regression")
.legend(|(x, y)| {
PathElement::new(
vec![(x, y), (x + 20, y)],
DARK_BLUE.filled().stroke_width(2),
)
});
chart
.draw_series(std::iter::once(Polygon::new(
vec![(0.0, 0.0), (max_iters, lb), (max_iters, ub)],
DARK_BLUE.mix(0.25).filled(),
)))
.unwrap()
.label("Confidence interval")
.legend(|(x, y)| {
Rectangle::new([(x, y - 5), (x + 20, y + 5)], DARK_BLUE.mix(0.25).filled())
});
if title.is_some() {
chart
.configure_series_labels()
.position(SeriesLabelPosition::UpperLeft)
.draw()
.unwrap();
}
}
pub(crate) fn regression_comparison_figure(
title: Option<&str>,
path: &Path,
formatter: &dyn ValueFormatter,
measurements: &MeasurementData<'_>,
comparison: &ComparisonData,
base_data: &Data<'_, f64, f64>,
size: Option<(u32, u32)>,
) {
let data = &measurements.data;
let max_iters = base_data.x().max().max(data.x().max());
let typical = base_data.y().max().max(data.y().max());
let exponent = (max_iters.log10() / 3.).floor() as i32 * 3;
let x_scale = 10f64.powi(-exponent);
let x_label = if exponent == 0 {
"Iterations".to_owned()
} else {
format!("Iterations (x 10^{})", exponent)
};
let Estimate {
confidence_interval:
ConfidenceInterval {
lower_bound: base_lb,
upper_bound: base_ub,
..
},
point_estimate: base_point,
..
} = comparison.base_estimates.slope.as_ref().unwrap();
let Estimate {
confidence_interval:
ConfidenceInterval {
lower_bound: lb,
upper_bound: ub,
..
},
point_estimate: point,
..
} = measurements.absolute_estimates.slope.as_ref().unwrap();
let mut points = [
base_lb * max_iters,
base_point * max_iters,
base_ub * max_iters,
lb * max_iters,
point * max_iters,
ub * max_iters,
];
let unit = formatter.scale_values(typical, &mut points);
let [base_lb, base_point, base_ub, lb, point, ub] = points;
let y_max = point.max(base_point);
let size = size.unwrap_or(SIZE);
let root_area = SVGBackend::new(path, size).into_drawing_area();
let mut cb = ChartBuilder::on(&root_area);
if let Some(title) = title {
cb.caption(title, (DEFAULT_FONT, 20));
}
let mut chart = cb
.margin((5).percent())
.set_label_area_size(LabelAreaPosition::Left, (5).percent_width().min(60))
.set_label_area_size(LabelAreaPosition::Bottom, (5).percent_height().min(40))
.build_ranged(0.0..max_iters, 0.0..y_max)
.unwrap();
chart
.configure_mesh()
.x_desc(x_label)
.y_desc(format!("Total sample time ({})", unit))
.x_label_formatter(&|x| pretty_print_float(x * x_scale, true))
.line_style_2(&TRANSPARENT)
.draw()
.unwrap();
chart
.draw_series(vec![
PathElement::new(vec![(0.0, 0.0), (max_iters, base_point)], &DARK_RED).into_dyn(),
Polygon::new(
vec![(0.0, 0.0), (max_iters, base_lb), (max_iters, base_ub)],
DARK_RED.mix(0.25).filled(),
)
.into_dyn(),
])
.unwrap()
.label("Base Sample")
.legend(|(x, y)| {
PathElement::new(vec![(x, y), (x + 20, y)], DARK_RED.filled().stroke_width(2))
});
chart
.draw_series(vec![
PathElement::new(vec![(0.0, 0.0), (max_iters, point)], &DARK_BLUE).into_dyn(),
Polygon::new(
vec![(0.0, 0.0), (max_iters, lb), (max_iters, ub)],
DARK_BLUE.mix(0.25).filled(),
)
.into_dyn(),
])
.unwrap()
.label("New Sample")
.legend(|(x, y)| {
PathElement::new(
vec![(x, y), (x + 20, y)],
DARK_BLUE.filled().stroke_width(2),
)
});
if title.is_some() {
chart
.configure_series_labels()
.position(SeriesLabelPosition::UpperLeft)
.draw()
.unwrap();
}
}