Skip to content

Commit b3161ce

Browse files
LukeMathWalkerjturner314
authored andcommitted
Means (#20)
* New module * First implementation of mean and harmonic_mean * Return a Result instead of panicking * Added documentation * Improved test suite * Implemented geometric mean * Reorganized to only expose the extension trait, instead of the whole submodule tree * Implement review comments * Fixed documentation * Use Rust 1.31
1 parent 722e6ae commit b3161ce

File tree

5 files changed

+171
-2
lines changed

5 files changed

+171
-2
lines changed

.travis.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ addons:
77
- libssl-dev
88
cache: cargo
99
rust:
10-
- 1.30.0
10+
- 1.31.0
1111
- stable
1212
- beta
1313
- nightly

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,4 @@ itertools = { version = "0.7.0", default-features = false }
2424
[dev-dependencies]
2525
quickcheck = "0.7"
2626
ndarray-rand = "0.9"
27+
approx = "0.3"

src/lib.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,15 +37,20 @@ extern crate ndarray_rand;
3737
#[cfg(test)]
3838
#[macro_use(quickcheck)]
3939
extern crate quickcheck;
40+
#[cfg(test)]
41+
#[macro_use(abs_diff_eq)]
42+
extern crate approx;
4043

4144
pub use maybe_nan::{MaybeNan, MaybeNanExt};
4245
pub use quantile::{interpolate, QuantileExt, Quantile1dExt};
4346
pub use sort::Sort1dExt;
4447
pub use correlation::CorrelationExt;
4548
pub use histogram::HistogramExt;
49+
pub use summary_statistics::SummaryStatisticsExt;
4650

4751
mod maybe_nan;
4852
mod quantile;
4953
mod sort;
5054
mod correlation;
51-
pub mod histogram;
55+
mod summary_statistics;
56+
pub mod histogram;

src/summary_statistics/means.rs

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
use ndarray::{Data, Dimension, ArrayBase};
2+
use num_traits::{FromPrimitive, Float, Zero};
3+
use std::ops::{Add, Div};
4+
use super::SummaryStatisticsExt;
5+
6+
7+
impl<A, S, D> SummaryStatisticsExt<A, S, D> for ArrayBase<S, D>
8+
where
9+
S: Data<Elem = A>,
10+
D: Dimension,
11+
{
12+
fn mean(&self) -> Option<A>
13+
where
14+
A: Clone + FromPrimitive + Add<Output=A> + Div<Output=A> + Zero
15+
{
16+
let n_elements = self.len();
17+
if n_elements == 0 {
18+
None
19+
} else {
20+
let n_elements = A::from_usize(n_elements)
21+
.expect("Converting number of elements to `A` must not fail.");
22+
Some(self.sum() / n_elements)
23+
}
24+
}
25+
26+
fn harmonic_mean(&self) -> Option<A>
27+
where
28+
A: Float + FromPrimitive,
29+
{
30+
self.map(|x| x.recip()).mean().map(|x| x.recip())
31+
}
32+
33+
fn geometric_mean(&self) -> Option<A>
34+
where
35+
A: Float + FromPrimitive,
36+
{
37+
self.map(|x| x.ln()).mean().map(|x| x.exp())
38+
}
39+
}
40+
41+
#[cfg(test)]
42+
mod tests {
43+
use super::SummaryStatisticsExt;
44+
use std::f64;
45+
use noisy_float::types::N64;
46+
use ndarray::Array1;
47+
48+
#[test]
49+
fn test_means_with_nan_values() {
50+
let a = array![f64::NAN, 1.];
51+
assert!(a.mean().unwrap().is_nan());
52+
assert!(a.harmonic_mean().unwrap().is_nan());
53+
assert!(a.geometric_mean().unwrap().is_nan());
54+
}
55+
56+
#[test]
57+
fn test_means_with_empty_array_of_floats() {
58+
let a: Array1<f64> = array![];
59+
assert!(a.mean().is_none());
60+
assert!(a.harmonic_mean().is_none());
61+
assert!(a.geometric_mean().is_none());
62+
}
63+
64+
#[test]
65+
fn test_means_with_empty_array_of_noisy_floats() {
66+
let a: Array1<N64> = array![];
67+
assert!(a.mean().is_none());
68+
assert!(a.harmonic_mean().is_none());
69+
assert!(a.geometric_mean().is_none());
70+
}
71+
72+
#[test]
73+
fn test_means_with_array_of_floats() {
74+
let a: Array1<f64> = array![
75+
0.99889651, 0.0150731 , 0.28492482, 0.83819218, 0.48413156,
76+
0.80710412, 0.41762936, 0.22879429, 0.43997224, 0.23831807,
77+
0.02416466, 0.6269962 , 0.47420614, 0.56275487, 0.78995021,
78+
0.16060581, 0.64635041, 0.34876609, 0.78543249, 0.19938356,
79+
0.34429457, 0.88072369, 0.17638164, 0.60819363, 0.250392 ,
80+
0.69912532, 0.78855523, 0.79140914, 0.85084218, 0.31839879,
81+
0.63381769, 0.22421048, 0.70760302, 0.99216018, 0.80199153,
82+
0.19239188, 0.61356023, 0.31505352, 0.06120481, 0.66417377,
83+
0.63608897, 0.84959691, 0.43599069, 0.77867775, 0.88267754,
84+
0.83003623, 0.67016118, 0.67547638, 0.65220036, 0.68043427
85+
];
86+
// Computed using NumPy
87+
let expected_mean = 0.5475494059146699;
88+
// Computed using SciPy
89+
let expected_harmonic_mean = 0.21790094950226022;
90+
// Computed using SciPy
91+
let expected_geometric_mean = 0.4345897639796527;
92+
93+
abs_diff_eq!(a.mean().unwrap(), expected_mean, epsilon = f64::EPSILON);
94+
abs_diff_eq!(a.harmonic_mean().unwrap(), expected_harmonic_mean, epsilon = f64::EPSILON);
95+
abs_diff_eq!(a.geometric_mean().unwrap(), expected_geometric_mean, epsilon = f64::EPSILON);
96+
}
97+
}

src/summary_statistics/mod.rs

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
//! Summary statistics (e.g. mean, variance, etc.).
2+
use ndarray::{Data, Dimension};
3+
use num_traits::{FromPrimitive, Float, Zero};
4+
use std::ops::{Add, Div};
5+
6+
/// Extension trait for `ArrayBase` providing methods
7+
/// to compute several summary statistics (e.g. mean, variance, etc.).
8+
pub trait SummaryStatisticsExt<A, S, D>
9+
where
10+
S: Data<Elem = A>,
11+
D: Dimension,
12+
{
13+
/// Returns the [`arithmetic mean`] x̅ of all elements in the array:
14+
///
15+
/// ```text
16+
/// 1 n
17+
/// x̅ = ― ∑ xᵢ
18+
/// n i=1
19+
/// ```
20+
///
21+
/// If the array is empty, `None` is returned.
22+
///
23+
/// **Panics** if `A::from_usize()` fails to convert the number of elements in the array.
24+
///
25+
/// [`arithmetic mean`]: https://en.wikipedia.org/wiki/Arithmetic_mean
26+
fn mean(&self) -> Option<A>
27+
where
28+
A: Clone + FromPrimitive + Add<Output=A> + Div<Output=A> + Zero;
29+
30+
/// Returns the [`harmonic mean`] `HM(X)` of all elements in the array:
31+
///
32+
/// ```text
33+
/// ⎛ n ⎞⁻¹
34+
/// HM(X) = n ⎜ ∑ xᵢ⁻¹⎟
35+
/// ⎝i=1 ⎠
36+
/// ```
37+
///
38+
/// If the array is empty, `None` is returned.
39+
///
40+
/// **Panics** if `A::from_usize()` fails to convert the number of elements in the array.
41+
///
42+
/// [`harmonic mean`]: https://en.wikipedia.org/wiki/Harmonic_mean
43+
fn harmonic_mean(&self) -> Option<A>
44+
where
45+
A: Float + FromPrimitive;
46+
47+
/// Returns the [`geometric mean`] `GM(X)` of all elements in the array:
48+
///
49+
/// ```text
50+
/// ⎛ n ⎞¹⁄ₙ
51+
/// GM(X) = ⎜ ∏ xᵢ⎟
52+
/// ⎝i=1 ⎠
53+
/// ```
54+
///
55+
/// If the array is empty, `None` is returned.
56+
///
57+
/// **Panics** if `A::from_usize()` fails to convert the number of elements in the array.
58+
///
59+
/// [`geometric mean`]: https://en.wikipedia.org/wiki/Geometric_mean
60+
fn geometric_mean(&self) -> Option<A>
61+
where
62+
A: Float + FromPrimitive;
63+
64+
}
65+
66+
mod means;

0 commit comments

Comments
 (0)