criterion/plot/plotters_backend/
regression.rs

1use super::*;
2
3use std::path::Path;
4
5use crate::estimate::{ConfidenceInterval, Estimate};
6use crate::stats::bivariate::regression::Slope;
7use crate::stats::bivariate::Data;
8
9pub(crate) fn regression_figure(
10    title: Option<&str>,
11    path: &Path,
12    formatter: &dyn ValueFormatter,
13    measurements: &MeasurementData<'_>,
14    size: Option<(u32, u32)>,
15) {
16    let slope_estimate = measurements.absolute_estimates.slope.as_ref().unwrap();
17    let slope_dist = measurements.distributions.slope.as_ref().unwrap();
18    let (lb, ub) =
19        slope_dist.confidence_interval(slope_estimate.confidence_interval.confidence_level);
20
21    let data = &measurements.data;
22    let (max_iters, typical) = (data.x().max(), data.y().max());
23    let mut scaled_y: Vec<f64> = data.y().iter().cloned().collect();
24    let unit = formatter.scale_values(typical, &mut scaled_y);
25    let scaled_y = Sample::new(&scaled_y);
26
27    let point_estimate = Slope::fit(&measurements.data).0;
28    let mut scaled_points = [point_estimate * max_iters, lb * max_iters, ub * max_iters];
29    let _ = formatter.scale_values(typical, &mut scaled_points);
30    let [point, lb, ub] = scaled_points;
31
32    let exponent = (max_iters.log10() / 3.).floor() as i32 * 3;
33
34    let x_scale = 10f64.powi(-exponent);
35    let x_label = if exponent == 0 {
36        "Iterations".to_owned()
37    } else {
38        format!("Iterations (x 10^{})", exponent)
39    };
40
41    let size = size.unwrap_or(SIZE);
42    let root_area = SVGBackend::new(path, size).into_drawing_area();
43
44    let mut cb = ChartBuilder::on(&root_area);
45    if let Some(title) = title {
46        cb.caption(title, (DEFAULT_FONT, 20));
47    }
48
49    let x_range = plotters::data::fitting_range(data.x().iter());
50    let y_range = plotters::data::fitting_range(scaled_y.iter());
51
52    let mut chart = cb
53        .margin((5).percent())
54        .set_label_area_size(LabelAreaPosition::Left, (5).percent_width().min(60))
55        .set_label_area_size(LabelAreaPosition::Bottom, (5).percent_height().min(40))
56        .build_cartesian_2d(x_range, y_range)
57        .unwrap();
58
59    chart
60        .configure_mesh()
61        .x_desc(x_label)
62        .y_desc(format!("Total sample time ({})", unit))
63        .x_label_formatter(&|x| pretty_print_float(x * x_scale, true))
64        .light_line_style(TRANSPARENT)
65        .draw()
66        .unwrap();
67
68    chart
69        .draw_series(
70            data.x()
71                .iter()
72                .zip(scaled_y.iter())
73                .map(|(x, y)| Circle::new((*x, *y), POINT_SIZE, DARK_BLUE.filled())),
74        )
75        .unwrap()
76        .label("Sample")
77        .legend(|(x, y)| Circle::new((x + 10, y), POINT_SIZE, DARK_BLUE.filled()));
78
79    chart
80        .draw_series(std::iter::once(PathElement::new(
81            vec![(0.0, 0.0), (max_iters, point)],
82            DARK_BLUE,
83        )))
84        .unwrap()
85        .label("Linear regression")
86        .legend(|(x, y)| {
87            PathElement::new(
88                vec![(x, y), (x + 20, y)],
89                DARK_BLUE.filled().stroke_width(2),
90            )
91        });
92
93    chart
94        .draw_series(std::iter::once(Polygon::new(
95            vec![(0.0, 0.0), (max_iters, lb), (max_iters, ub)],
96            DARK_BLUE.mix(0.25).filled(),
97        )))
98        .unwrap()
99        .label("Confidence interval")
100        .legend(|(x, y)| {
101            Rectangle::new([(x, y - 5), (x + 20, y + 5)], DARK_BLUE.mix(0.25).filled())
102        });
103
104    if title.is_some() {
105        chart
106            .configure_series_labels()
107            .position(SeriesLabelPosition::UpperLeft)
108            .draw()
109            .unwrap();
110    }
111}
112
113pub(crate) fn regression_comparison_figure(
114    title: Option<&str>,
115    path: &Path,
116    formatter: &dyn ValueFormatter,
117    measurements: &MeasurementData<'_>,
118    comparison: &ComparisonData,
119    base_data: &Data<'_, f64, f64>,
120    size: Option<(u32, u32)>,
121) {
122    let data = &measurements.data;
123    let max_iters = base_data.x().max().max(data.x().max());
124    let typical = base_data.y().max().max(data.y().max());
125
126    let exponent = (max_iters.log10() / 3.).floor() as i32 * 3;
127    let x_scale = 10f64.powi(-exponent);
128
129    let x_label = if exponent == 0 {
130        "Iterations".to_owned()
131    } else {
132        format!("Iterations (x 10^{})", exponent)
133    };
134
135    let Estimate {
136        confidence_interval:
137            ConfidenceInterval {
138                lower_bound: base_lb,
139                upper_bound: base_ub,
140                ..
141            },
142        point_estimate: base_point,
143        ..
144    } = comparison.base_estimates.slope.as_ref().unwrap();
145
146    let Estimate {
147        confidence_interval:
148            ConfidenceInterval {
149                lower_bound: lb,
150                upper_bound: ub,
151                ..
152            },
153        point_estimate: point,
154        ..
155    } = measurements.absolute_estimates.slope.as_ref().unwrap();
156
157    let mut points = [
158        base_lb * max_iters,
159        base_point * max_iters,
160        base_ub * max_iters,
161        lb * max_iters,
162        point * max_iters,
163        ub * max_iters,
164    ];
165    let unit = formatter.scale_values(typical, &mut points);
166    let [base_lb, base_point, base_ub, lb, point, ub] = points;
167
168    let y_max = point.max(base_point);
169
170    let size = size.unwrap_or(SIZE);
171    let root_area = SVGBackend::new(path, size).into_drawing_area();
172
173    let mut cb = ChartBuilder::on(&root_area);
174    if let Some(title) = title {
175        cb.caption(title, (DEFAULT_FONT, 20));
176    }
177
178    let mut chart = cb
179        .margin((5).percent())
180        .set_label_area_size(LabelAreaPosition::Left, (5).percent_width().min(60))
181        .set_label_area_size(LabelAreaPosition::Bottom, (5).percent_height().min(40))
182        .build_cartesian_2d(0.0..max_iters, 0.0..y_max)
183        .unwrap();
184
185    chart
186        .configure_mesh()
187        .x_desc(x_label)
188        .y_desc(format!("Total sample time ({})", unit))
189        .x_label_formatter(&|x| pretty_print_float(x * x_scale, true))
190        .light_line_style(TRANSPARENT)
191        .draw()
192        .unwrap();
193
194    chart
195        .draw_series(vec![
196            PathElement::new(vec![(0.0, 0.0), (max_iters, base_point)], DARK_RED).into_dyn(),
197            Polygon::new(
198                vec![(0.0, 0.0), (max_iters, base_lb), (max_iters, base_ub)],
199                DARK_RED.mix(0.25).filled(),
200            )
201            .into_dyn(),
202        ])
203        .unwrap()
204        .label("Base Sample")
205        .legend(|(x, y)| {
206            PathElement::new(vec![(x, y), (x + 20, y)], DARK_RED.filled().stroke_width(2))
207        });
208
209    chart
210        .draw_series(vec![
211            PathElement::new(vec![(0.0, 0.0), (max_iters, point)], DARK_BLUE).into_dyn(),
212            Polygon::new(
213                vec![(0.0, 0.0), (max_iters, lb), (max_iters, ub)],
214                DARK_BLUE.mix(0.25).filled(),
215            )
216            .into_dyn(),
217        ])
218        .unwrap()
219        .label("New Sample")
220        .legend(|(x, y)| {
221            PathElement::new(
222                vec![(x, y), (x + 20, y)],
223                DARK_BLUE.filled().stroke_width(2),
224            )
225        });
226
227    if title.is_some() {
228        chart
229            .configure_series_labels()
230            .position(SeriesLabelPosition::UpperLeft)
231            .draw()
232            .unwrap();
233    }
234}