1#[derive(Debug, Clone, PartialEq, Eq)]
17pub enum VarianceError {
18 EmptyInput,
19 InsufficientData,
20}
21
22pub fn sum_squared_deviations(values: &[f64]) -> Result<f64, VarianceError> {
23 if values.is_empty() {
24 return Err(VarianceError::EmptyInput);
25 }
26
27 let mean = mean(values);
28 Ok(values.iter().map(|value| (value - mean).powi(2)).sum())
29}
30
31pub fn population_variance(values: &[f64]) -> Result<f64, VarianceError> {
32 let squared_deviation_sum = sum_squared_deviations(values)?;
33 Ok(squared_deviation_sum / values.len() as f64)
34}
35
36pub fn sample_variance(values: &[f64]) -> Result<f64, VarianceError> {
37 if values.len() < 2 {
38 return Err(VarianceError::InsufficientData);
39 }
40
41 let squared_deviation_sum = sum_squared_deviations(values)?;
42 Ok(squared_deviation_sum / (values.len() as f64 - 1.0))
43}
44
45fn mean(values: &[f64]) -> f64 {
46 values.iter().sum::<f64>() / values.len() as f64
47}
48
49#[cfg(test)]
50mod tests {
51 use super::{population_variance, sample_variance, sum_squared_deviations, VarianceError};
52
53 fn approx_eq(left: f64, right: f64) {
54 assert!((left - right).abs() < 1.0e-10, "left={left}, right={right}");
55 }
56
57 #[test]
58 fn computes_population_and_sample_variance() {
59 let values = [2.0, 4.0, 4.0, 4.0, 5.0, 5.0, 7.0, 9.0];
60
61 approx_eq(sum_squared_deviations(&values).unwrap(), 32.0);
62 approx_eq(population_variance(&values).unwrap(), 4.0);
63 approx_eq(sample_variance(&values).unwrap(), 32.0 / 7.0);
64 }
65
66 #[test]
67 fn rejects_empty_input() {
68 assert_eq!(sum_squared_deviations(&[]), Err(VarianceError::EmptyInput));
69 assert_eq!(population_variance(&[]), Err(VarianceError::EmptyInput));
70 }
71
72 #[test]
73 fn handles_single_value_population_variance() {
74 approx_eq(population_variance(&[5.0]).unwrap(), 0.0);
75 assert_eq!(
76 sample_variance(&[5.0]),
77 Err(VarianceError::InsufficientData)
78 );
79 }
80}