|  | use std::fmt; | 
|  |  | 
|  | use crate::stats::Distribution; | 
|  |  | 
|  | #[derive(Clone, Copy, Eq, Ord, PartialEq, PartialOrd, Deserialize, Serialize, Debug)] | 
|  | pub enum Statistic { | 
|  | Mean, | 
|  | Median, | 
|  | MedianAbsDev, | 
|  | Slope, | 
|  | StdDev, | 
|  | Typical, | 
|  | } | 
|  |  | 
|  | impl fmt::Display for Statistic { | 
|  | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | 
|  | match *self { | 
|  | Statistic::Mean => f.pad("mean"), | 
|  | Statistic::Median => f.pad("median"), | 
|  | Statistic::MedianAbsDev => f.pad("MAD"), | 
|  | Statistic::Slope => f.pad("slope"), | 
|  | Statistic::StdDev => f.pad("SD"), | 
|  | Statistic::Typical => f.pad("typical"), | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | #[derive(Clone, PartialEq, Deserialize, Serialize, Debug)] | 
|  | pub struct ConfidenceInterval { | 
|  | pub confidence_level: f64, | 
|  | pub lower_bound: f64, | 
|  | pub upper_bound: f64, | 
|  | } | 
|  |  | 
|  | #[derive(Clone, PartialEq, Deserialize, Serialize, Debug)] | 
|  | pub struct Estimate { | 
|  | /// The confidence interval for this estimate | 
|  | pub confidence_interval: ConfidenceInterval, | 
|  | /// | 
|  | pub point_estimate: f64, | 
|  | /// The standard error of this estimate | 
|  | pub standard_error: f64, | 
|  | } | 
|  |  | 
|  | pub fn build_estimates( | 
|  | distributions: &Distributions, | 
|  | points: &PointEstimates, | 
|  | cl: f64, | 
|  | ) -> Estimates { | 
|  | let to_estimate = |point_estimate, distribution: &Distribution<f64>| { | 
|  | let (lb, ub) = distribution.confidence_interval(cl); | 
|  |  | 
|  | Estimate { | 
|  | confidence_interval: ConfidenceInterval { | 
|  | confidence_level: cl, | 
|  | lower_bound: lb, | 
|  | upper_bound: ub, | 
|  | }, | 
|  | point_estimate, | 
|  | standard_error: distribution.std_dev(None), | 
|  | } | 
|  | }; | 
|  |  | 
|  | Estimates { | 
|  | mean: to_estimate(points.mean, &distributions.mean), | 
|  | median: to_estimate(points.median, &distributions.median), | 
|  | median_abs_dev: to_estimate(points.median_abs_dev, &distributions.median_abs_dev), | 
|  | slope: None, | 
|  | std_dev: to_estimate(points.std_dev, &distributions.std_dev), | 
|  | } | 
|  | } | 
|  |  | 
|  | pub fn build_change_estimates( | 
|  | distributions: &ChangeDistributions, | 
|  | points: &ChangePointEstimates, | 
|  | cl: f64, | 
|  | ) -> ChangeEstimates { | 
|  | let to_estimate = |point_estimate, distribution: &Distribution<f64>| { | 
|  | let (lb, ub) = distribution.confidence_interval(cl); | 
|  |  | 
|  | Estimate { | 
|  | confidence_interval: ConfidenceInterval { | 
|  | confidence_level: cl, | 
|  | lower_bound: lb, | 
|  | upper_bound: ub, | 
|  | }, | 
|  | point_estimate, | 
|  | standard_error: distribution.std_dev(None), | 
|  | } | 
|  | }; | 
|  |  | 
|  | ChangeEstimates { | 
|  | mean: to_estimate(points.mean, &distributions.mean), | 
|  | median: to_estimate(points.median, &distributions.median), | 
|  | } | 
|  | } | 
|  |  | 
|  | pub struct PointEstimates { | 
|  | pub mean: f64, | 
|  | pub median: f64, | 
|  | pub median_abs_dev: f64, | 
|  | pub std_dev: f64, | 
|  | } | 
|  |  | 
|  | #[derive(Debug, Serialize, Deserialize, Clone)] | 
|  | pub struct Estimates { | 
|  | pub mean: Estimate, | 
|  | pub median: Estimate, | 
|  | pub median_abs_dev: Estimate, | 
|  | pub slope: Option<Estimate>, | 
|  | pub std_dev: Estimate, | 
|  | } | 
|  | impl Estimates { | 
|  | pub fn typical(&self) -> &Estimate { | 
|  | self.slope.as_ref().unwrap_or(&self.mean) | 
|  | } | 
|  | pub fn get(&self, stat: Statistic) -> Option<&Estimate> { | 
|  | match stat { | 
|  | Statistic::Mean => Some(&self.mean), | 
|  | Statistic::Median => Some(&self.median), | 
|  | Statistic::MedianAbsDev => Some(&self.median_abs_dev), | 
|  | Statistic::Slope => self.slope.as_ref(), | 
|  | Statistic::StdDev => Some(&self.std_dev), | 
|  | Statistic::Typical => Some(self.typical()), | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | pub struct Distributions { | 
|  | pub mean: Distribution<f64>, | 
|  | pub median: Distribution<f64>, | 
|  | pub median_abs_dev: Distribution<f64>, | 
|  | pub slope: Option<Distribution<f64>>, | 
|  | pub std_dev: Distribution<f64>, | 
|  | } | 
|  | impl Distributions { | 
|  | pub fn typical(&self) -> &Distribution<f64> { | 
|  | self.slope.as_ref().unwrap_or(&self.mean) | 
|  | } | 
|  | pub fn get(&self, stat: Statistic) -> Option<&Distribution<f64>> { | 
|  | match stat { | 
|  | Statistic::Mean => Some(&self.mean), | 
|  | Statistic::Median => Some(&self.median), | 
|  | Statistic::MedianAbsDev => Some(&self.median_abs_dev), | 
|  | Statistic::Slope => self.slope.as_ref(), | 
|  | Statistic::StdDev => Some(&self.std_dev), | 
|  | Statistic::Typical => Some(self.typical()), | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | pub struct ChangePointEstimates { | 
|  | pub mean: f64, | 
|  | pub median: f64, | 
|  | } | 
|  |  | 
|  | #[derive(Debug, Serialize, Deserialize, Clone)] | 
|  | pub struct ChangeEstimates { | 
|  | pub mean: Estimate, | 
|  | pub median: Estimate, | 
|  | } | 
|  | impl ChangeEstimates { | 
|  | pub fn get(&self, stat: Statistic) -> &Estimate { | 
|  | match stat { | 
|  | Statistic::Mean => &self.mean, | 
|  | Statistic::Median => &self.median, | 
|  | _ => panic!("Unexpected statistic"), | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | pub struct ChangeDistributions { | 
|  | pub mean: Distribution<f64>, | 
|  | pub median: Distribution<f64>, | 
|  | } | 
|  | impl ChangeDistributions { | 
|  | pub fn get(&self, stat: Statistic) -> &Distribution<f64> { | 
|  | match stat { | 
|  | Statistic::Mean => &self.mean, | 
|  | Statistic::Median => &self.median, | 
|  | _ => panic!("Unexpected statistic"), | 
|  | } | 
|  | } | 
|  | } |