criterion/
estimate.rs

1use serde::{Deserialize, Serialize};
2use std::fmt;
3
4use crate::stats::Distribution;
5
6#[derive(Clone, Copy, Eq, Ord, PartialEq, PartialOrd, Deserialize, Serialize, Debug)]
7pub enum Statistic {
8    Mean,
9    Median,
10    MedianAbsDev,
11    Slope,
12    StdDev,
13    Typical,
14}
15
16impl fmt::Display for Statistic {
17    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
18        match *self {
19            Statistic::Mean => f.pad("mean"),
20            Statistic::Median => f.pad("median"),
21            Statistic::MedianAbsDev => f.pad("MAD"),
22            Statistic::Slope => f.pad("slope"),
23            Statistic::StdDev => f.pad("SD"),
24            Statistic::Typical => f.pad("typical"),
25        }
26    }
27}
28
29#[derive(Clone, PartialEq, Deserialize, Serialize, Debug)]
30pub struct ConfidenceInterval {
31    pub confidence_level: f64,
32    pub lower_bound: f64,
33    pub upper_bound: f64,
34}
35
36#[derive(Clone, PartialEq, Deserialize, Serialize, Debug)]
37pub struct Estimate {
38    /// The confidence interval for this estimate
39    pub confidence_interval: ConfidenceInterval,
40    /// The value of this estimate
41    pub point_estimate: f64,
42    /// The standard error of this estimate
43    pub standard_error: f64,
44}
45
46pub fn build_estimates(
47    distributions: &Distributions,
48    points: &PointEstimates,
49    cl: f64,
50) -> Estimates {
51    let to_estimate = |point_estimate, distribution: &Distribution<f64>| {
52        let (lb, ub) = distribution.confidence_interval(cl);
53
54        Estimate {
55            confidence_interval: ConfidenceInterval {
56                confidence_level: cl,
57                lower_bound: lb,
58                upper_bound: ub,
59            },
60            point_estimate,
61            standard_error: distribution.std_dev(None),
62        }
63    };
64
65    Estimates {
66        mean: to_estimate(points.mean, &distributions.mean),
67        median: to_estimate(points.median, &distributions.median),
68        median_abs_dev: to_estimate(points.median_abs_dev, &distributions.median_abs_dev),
69        slope: None,
70        std_dev: to_estimate(points.std_dev, &distributions.std_dev),
71    }
72}
73
74pub fn build_change_estimates(
75    distributions: &ChangeDistributions,
76    points: &ChangePointEstimates,
77    cl: f64,
78) -> ChangeEstimates {
79    let to_estimate = |point_estimate, distribution: &Distribution<f64>| {
80        let (lb, ub) = distribution.confidence_interval(cl);
81
82        Estimate {
83            confidence_interval: ConfidenceInterval {
84                confidence_level: cl,
85                lower_bound: lb,
86                upper_bound: ub,
87            },
88            point_estimate,
89            standard_error: distribution.std_dev(None),
90        }
91    };
92
93    ChangeEstimates {
94        mean: to_estimate(points.mean, &distributions.mean),
95        median: to_estimate(points.median, &distributions.median),
96    }
97}
98
99pub struct PointEstimates {
100    pub mean: f64,
101    pub median: f64,
102    pub median_abs_dev: f64,
103    pub std_dev: f64,
104}
105
106#[derive(Debug, Serialize, Deserialize, Clone)]
107pub struct Estimates {
108    pub mean: Estimate,
109    pub median: Estimate,
110    pub median_abs_dev: Estimate,
111    pub slope: Option<Estimate>,
112    pub std_dev: Estimate,
113}
114impl Estimates {
115    pub fn typical(&self) -> &Estimate {
116        self.slope.as_ref().unwrap_or(&self.mean)
117    }
118    pub fn get(&self, stat: Statistic) -> Option<&Estimate> {
119        match stat {
120            Statistic::Mean => Some(&self.mean),
121            Statistic::Median => Some(&self.median),
122            Statistic::MedianAbsDev => Some(&self.median_abs_dev),
123            Statistic::Slope => self.slope.as_ref(),
124            Statistic::StdDev => Some(&self.std_dev),
125            Statistic::Typical => Some(self.typical()),
126        }
127    }
128}
129
130pub struct Distributions {
131    pub mean: Distribution<f64>,
132    pub median: Distribution<f64>,
133    pub median_abs_dev: Distribution<f64>,
134    pub slope: Option<Distribution<f64>>,
135    pub std_dev: Distribution<f64>,
136}
137impl Distributions {
138    pub fn typical(&self) -> &Distribution<f64> {
139        self.slope.as_ref().unwrap_or(&self.mean)
140    }
141    pub fn get(&self, stat: Statistic) -> Option<&Distribution<f64>> {
142        match stat {
143            Statistic::Mean => Some(&self.mean),
144            Statistic::Median => Some(&self.median),
145            Statistic::MedianAbsDev => Some(&self.median_abs_dev),
146            Statistic::Slope => self.slope.as_ref(),
147            Statistic::StdDev => Some(&self.std_dev),
148            Statistic::Typical => Some(self.typical()),
149        }
150    }
151}
152
153pub struct ChangePointEstimates {
154    pub mean: f64,
155    pub median: f64,
156}
157
158#[derive(Debug, Serialize, Deserialize, Clone)]
159pub struct ChangeEstimates {
160    pub mean: Estimate,
161    pub median: Estimate,
162}
163impl ChangeEstimates {
164    pub fn get(&self, stat: Statistic) -> &Estimate {
165        match stat {
166            Statistic::Mean => &self.mean,
167            Statistic::Median => &self.median,
168            _ => panic!("Unexpected statistic"),
169        }
170    }
171}
172
173pub struct ChangeDistributions {
174    pub mean: Distribution<f64>,
175    pub median: Distribution<f64>,
176}
177impl ChangeDistributions {
178    pub fn get(&self, stat: Statistic) -> &Distribution<f64> {
179        match stat {
180            Statistic::Mean => &self.mean,
181            Statistic::Median => &self.median,
182            _ => panic!("Unexpected statistic"),
183        }
184    }
185}