Skip to content

Commit

Permalink
Means (#20)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
LukeMathWalker authored and jturner314 committed Jan 10, 2019
1 parent 722e6ae commit b3161ce
Show file tree
Hide file tree
Showing 5 changed files with 171 additions and 2 deletions.
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ addons:
- libssl-dev
cache: cargo
rust:
- 1.30.0
- 1.31.0
- stable
- beta
- nightly
Expand Down
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,4 @@ itertools = { version = "0.7.0", default-features = false }
[dev-dependencies]
quickcheck = "0.7"
ndarray-rand = "0.9"
approx = "0.3"
7 changes: 6 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,15 +37,20 @@ extern crate ndarray_rand;
#[cfg(test)]
#[macro_use(quickcheck)]
extern crate quickcheck;
#[cfg(test)]
#[macro_use(abs_diff_eq)]
extern crate approx;

pub use maybe_nan::{MaybeNan, MaybeNanExt};
pub use quantile::{interpolate, QuantileExt, Quantile1dExt};
pub use sort::Sort1dExt;
pub use correlation::CorrelationExt;
pub use histogram::HistogramExt;
pub use summary_statistics::SummaryStatisticsExt;

mod maybe_nan;
mod quantile;
mod sort;
mod correlation;
pub mod histogram;
mod summary_statistics;
pub mod histogram;
97 changes: 97 additions & 0 deletions src/summary_statistics/means.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
use ndarray::{Data, Dimension, ArrayBase};
use num_traits::{FromPrimitive, Float, Zero};
use std::ops::{Add, Div};
use super::SummaryStatisticsExt;


impl<A, S, D> SummaryStatisticsExt<A, S, D> for ArrayBase<S, D>
where
S: Data<Elem = A>,
D: Dimension,
{
fn mean(&self) -> Option<A>
where
A: Clone + FromPrimitive + Add<Output=A> + Div<Output=A> + Zero
{
let n_elements = self.len();
if n_elements == 0 {
None
} else {
let n_elements = A::from_usize(n_elements)
.expect("Converting number of elements to `A` must not fail.");
Some(self.sum() / n_elements)
}
}

fn harmonic_mean(&self) -> Option<A>
where
A: Float + FromPrimitive,
{
self.map(|x| x.recip()).mean().map(|x| x.recip())
}

fn geometric_mean(&self) -> Option<A>
where
A: Float + FromPrimitive,
{
self.map(|x| x.ln()).mean().map(|x| x.exp())
}
}

#[cfg(test)]
mod tests {
use super::SummaryStatisticsExt;
use std::f64;
use noisy_float::types::N64;
use ndarray::Array1;

#[test]
fn test_means_with_nan_values() {
let a = array![f64::NAN, 1.];
assert!(a.mean().unwrap().is_nan());
assert!(a.harmonic_mean().unwrap().is_nan());
assert!(a.geometric_mean().unwrap().is_nan());
}

#[test]
fn test_means_with_empty_array_of_floats() {
let a: Array1<f64> = array![];
assert!(a.mean().is_none());
assert!(a.harmonic_mean().is_none());
assert!(a.geometric_mean().is_none());
}

#[test]
fn test_means_with_empty_array_of_noisy_floats() {
let a: Array1<N64> = array![];
assert!(a.mean().is_none());
assert!(a.harmonic_mean().is_none());
assert!(a.geometric_mean().is_none());
}

#[test]
fn test_means_with_array_of_floats() {
let a: Array1<f64> = array![
0.99889651, 0.0150731 , 0.28492482, 0.83819218, 0.48413156,
0.80710412, 0.41762936, 0.22879429, 0.43997224, 0.23831807,
0.02416466, 0.6269962 , 0.47420614, 0.56275487, 0.78995021,
0.16060581, 0.64635041, 0.34876609, 0.78543249, 0.19938356,
0.34429457, 0.88072369, 0.17638164, 0.60819363, 0.250392 ,
0.69912532, 0.78855523, 0.79140914, 0.85084218, 0.31839879,
0.63381769, 0.22421048, 0.70760302, 0.99216018, 0.80199153,
0.19239188, 0.61356023, 0.31505352, 0.06120481, 0.66417377,
0.63608897, 0.84959691, 0.43599069, 0.77867775, 0.88267754,
0.83003623, 0.67016118, 0.67547638, 0.65220036, 0.68043427
];
// Computed using NumPy
let expected_mean = 0.5475494059146699;
// Computed using SciPy
let expected_harmonic_mean = 0.21790094950226022;
// Computed using SciPy
let expected_geometric_mean = 0.4345897639796527;

abs_diff_eq!(a.mean().unwrap(), expected_mean, epsilon = f64::EPSILON);
abs_diff_eq!(a.harmonic_mean().unwrap(), expected_harmonic_mean, epsilon = f64::EPSILON);
abs_diff_eq!(a.geometric_mean().unwrap(), expected_geometric_mean, epsilon = f64::EPSILON);
}
}
66 changes: 66 additions & 0 deletions src/summary_statistics/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
//! Summary statistics (e.g. mean, variance, etc.).
use ndarray::{Data, Dimension};
use num_traits::{FromPrimitive, Float, Zero};
use std::ops::{Add, Div};

/// Extension trait for `ArrayBase` providing methods
/// to compute several summary statistics (e.g. mean, variance, etc.).
pub trait SummaryStatisticsExt<A, S, D>
where
S: Data<Elem = A>,
D: Dimension,
{
/// Returns the [`arithmetic mean`] x̅ of all elements in the array:
///
/// ```text
/// 1 n
/// x̅ = ― ∑ xᵢ
/// n i=1
/// ```
///
/// If the array is empty, `None` is returned.
///
/// **Panics** if `A::from_usize()` fails to convert the number of elements in the array.
///
/// [`arithmetic mean`]: https://en.wikipedia.org/wiki/Arithmetic_mean
fn mean(&self) -> Option<A>
where
A: Clone + FromPrimitive + Add<Output=A> + Div<Output=A> + Zero;

/// Returns the [`harmonic mean`] `HM(X)` of all elements in the array:
///
/// ```text
/// ⎛ n ⎞⁻¹
/// HM(X) = n ⎜ ∑ xᵢ⁻¹⎟
/// ⎝i=1 ⎠
/// ```
///
/// If the array is empty, `None` is returned.
///
/// **Panics** if `A::from_usize()` fails to convert the number of elements in the array.
///
/// [`harmonic mean`]: https://en.wikipedia.org/wiki/Harmonic_mean
fn harmonic_mean(&self) -> Option<A>
where
A: Float + FromPrimitive;

/// Returns the [`geometric mean`] `GM(X)` of all elements in the array:
///
/// ```text
/// ⎛ n ⎞¹⁄ₙ
/// GM(X) = ⎜ ∏ xᵢ⎟
/// ⎝i=1 ⎠
/// ```
///
/// If the array is empty, `None` is returned.
///
/// **Panics** if `A::from_usize()` fails to convert the number of elements in the array.
///
/// [`geometric mean`]: https://en.wikipedia.org/wiki/Geometric_mean
fn geometric_mean(&self) -> Option<A>
where
A: Float + FromPrimitive;

}

mod means;

0 comments on commit b3161ce

Please sign in to comment.