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