Move `add` and `merge` to trait

This should make it possible to write more generic code.
This commit is contained in:
Vinzent Steinberg 2017-06-25 14:39:23 +02:00
parent d5b3fc80ab
commit 5d6d67bac9
19 changed files with 310 additions and 224 deletions

View File

@ -19,7 +19,7 @@
//! ### Example
//!
//! ```
//! use average::MeanWithError;
//! use average::{MeanWithError, Estimate};
//!
//! let mut a: MeanWithError = (1..6).map(Into::into).collect();
//! a.add(42.);

View File

@ -40,7 +40,7 @@ macro_rules! assert_almost_eq {
/// # extern crate core;
/// # #[macro_use] extern crate average;
/// # fn main() {
/// use average::{Min, Max};
/// use average::{Min, Max, Estimate};
///
/// concatenate!(MinMax, [Min, min], [Max, max]);
///
@ -54,7 +54,7 @@ macro_rules! assert_almost_eq {
/// The generated code looks roughly like this:
///
/// ```
/// # use average::{Min, Max};
/// # use average::{Min, Max, Estimate};
/// #
/// struct MinMax {
/// min: Min,
@ -88,7 +88,7 @@ macro_rules! assert_almost_eq {
/// # extern crate core;
/// # #[macro_use] extern crate average;
/// # fn main() {
/// use average::{Variance, Quantile};
/// use average::{Variance, Quantile, Estimate};
///
/// concatenate!(Estimator,
/// [Variance, variance, mean, sample_variance],

View File

@ -1,6 +1,7 @@
use core;
use super::reduce::Reduce;
use super::{Estimate, Merge};
/// Calculate the minimum of `a` and `b`.
fn min(a: f64, b: f64) -> f64 {
@ -43,38 +44,11 @@ impl Min {
Min::from_value(::core::f64::INFINITY)
}
/// Add an observation sampled from the population.
#[inline]
pub fn add(&mut self, x: f64) {
self.r.add(x);
}
/// Estimate the minium of the population.
#[inline]
pub fn min(&self) -> f64 {
self.r.reduction()
}
/// Merge another sample into this one.
///
///
/// ## Example
///
/// ```
/// use average::Min;
///
/// let sequence: &[f64] = &[1., 2., 3., 4., 5., 6., 7., 8., 9.];
/// let (left, right) = sequence.split_at(3);
/// let min_total: Min = sequence.iter().map(|x| *x).collect();
/// let mut min_left: Min = left.iter().map(|x| *x).collect();
/// let min_right: Min = right.iter().map(|x| *x).collect();
/// min_left.merge(&min_right);
/// assert_eq!(min_total.min(), min_left.min());
/// ```
#[inline]
pub fn merge(&mut self, other: &Min) {
self.r.merge(&other.r);
}
}
impl core::default::Default for Min {
@ -85,6 +59,41 @@ impl core::default::Default for Min {
impl_from_iterator!(Min);
impl Estimate for Min {
#[inline]
fn add(&mut self, x: f64) {
self.r.add(x);
}
#[inline]
fn estimate(&self) -> f64 {
self.min()
}
}
impl Merge for Min {
/// Merge another sample into this one.
///
///
/// ## Example
///
/// ```
/// use average::{Min, Merge};
///
/// let sequence: &[f64] = &[1., 2., 3., 4., 5., 6., 7., 8., 9.];
/// let (left, right) = sequence.split_at(3);
/// let min_total: Min = sequence.iter().map(|x| *x).collect();
/// let mut min_left: Min = left.iter().map(|x| *x).collect();
/// let min_right: Min = right.iter().map(|x| *x).collect();
/// min_left.merge(&min_right);
/// assert_eq!(min_total.min(), min_left.min());
/// ```
#[inline]
fn merge(&mut self, other: &Min) {
self.r.merge(&other.r);
}
}
/// Estimate the maximum of a sequence of numbers ("population").
///
///
@ -116,38 +125,11 @@ impl Max {
Max::from_value(::core::f64::NEG_INFINITY)
}
/// Add an observation sampled from the population.
#[inline]
pub fn add(&mut self, x: f64) {
self.r.add(x);
}
/// Estimate the maxium of the population.
#[inline]
pub fn max(&self) -> f64 {
self.r.reduction()
}
/// Merge another sample into this one.
///
///
/// ## Example
///
/// ```
/// use average::Max;
///
/// let sequence: &[f64] = &[1., 2., 3., 4., 5., 6., 7., 8., 9.];
/// let (left, right) = sequence.split_at(3);
/// let max_total: Max = sequence.iter().map(|x| *x).collect();
/// let mut max_left: Max = left.iter().map(|x| *x).collect();
/// let max_right: Max = right.iter().map(|x| *x).collect();
/// max_left.merge(&max_right);
/// assert_eq!(max_total.max(), max_left.max());
/// ```
#[inline]
pub fn merge(&mut self, other: &Max) {
self.r.merge(&other.r);
}
}
impl core::default::Default for Max {
@ -157,3 +139,38 @@ impl core::default::Default for Max {
}
impl_from_iterator!(Max);
impl Estimate for Max {
#[inline]
fn add(&mut self, x: f64) {
self.r.add(x);
}
#[inline]
fn estimate(&self) -> f64 {
self.max()
}
}
impl Merge for Max {
/// Merge another sample into this one.
///
///
/// ## Example
///
/// ```
/// use average::{Max, Merge};
///
/// let sequence: &[f64] = &[1., 2., 3., 4., 5., 6., 7., 8., 9.];
/// let (left, right) = sequence.split_at(3);
/// let max_total: Max = sequence.iter().map(|x| *x).collect();
/// let mut max_left: Max = left.iter().map(|x| *x).collect();
/// let max_right: Max = right.iter().map(|x| *x).collect();
/// max_left.merge(&max_right);
/// assert_eq!(max_total.max(), max_left.max());
/// ```
#[inline]
fn merge(&mut self, other: &Max) {
self.r.merge(&other.r);
}
}

View File

@ -20,15 +20,6 @@ impl Kurtosis {
}
}
/// Add an observation sampled from the population.
#[inline]
pub fn add(&mut self, x: f64) {
let delta = x - self.mean();
self.increment();
let n = f64::approx_from(self.len()).unwrap();
self.add_inner(delta, delta/n);
}
/// Increment the sample size.
///
/// This does not update anything else.
@ -114,9 +105,26 @@ impl Kurtosis {
n * self.sum_4 / (self.avg.avg.sum_2 * self.avg.avg.sum_2) - 3.
}
/// Merge another sample into this one.
}
impl Estimate for Kurtosis {
#[inline]
pub fn merge(&mut self, other: &Kurtosis) {
fn add(&mut self, x: f64) {
let delta = x - self.mean();
self.increment();
let n = f64::approx_from(self.len()).unwrap();
self.add_inner(delta, delta/n);
}
#[inline]
fn estimate(&self) -> f64 {
self.kurtosis()
}
}
impl Merge for Kurtosis {
#[inline]
fn merge(&mut self, other: &Kurtosis) {
let len_self = f64::approx_from(self.len()).unwrap();
let len_other = f64::approx_from(other.len()).unwrap();
let len_total = len_self + len_other;

View File

@ -1,8 +1,3 @@
use core;
use conv::ApproxFrom;
/// Estimate the arithmetic mean of a sequence of numbers ("population").
///
///
@ -29,15 +24,6 @@ impl Mean {
Mean { avg: 0., n: 0 }
}
/// Add an observation sampled from the population.
#[inline]
pub fn add(&mut self, sample: f64) {
self.increment();
let delta_n = (sample - self.avg)
/ f64::approx_from(self.n).unwrap();
self.add_inner(delta_n);
}
/// Increment the sample size.
///
/// This does not update anything else.
@ -80,13 +66,36 @@ impl Mean {
self.n
}
}
impl core::default::Default for Mean {
fn default() -> Mean {
Mean::new()
}
}
impl Estimate for Mean {
#[inline]
fn add(&mut self, sample: f64) {
self.increment();
let delta_n = (sample - self.avg)
/ f64::approx_from(self.n).unwrap();
self.add_inner(delta_n);
}
fn estimate(&self) -> f64 {
self.mean()
}
}
impl Merge for Mean {
/// Merge another sample into this one.
///
///
/// ## Example
///
/// ```
/// use average::Mean;
/// use average::{Mean, Merge};
///
/// let sequence: &[f64] = &[1., 2., 3., 4., 5., 6., 7., 8., 9.];
/// let (left, right) = sequence.split_at(3);
@ -97,7 +106,7 @@ impl Mean {
/// assert_eq!(avg_total.mean(), avg_left.mean());
/// ```
#[inline]
pub fn merge(&mut self, other: &Mean) {
fn merge(&mut self, other: &Mean) {
// This algorithm was proposed by Chan et al. in 1979.
//
// See https://en.wikipedia.org/wiki/Algorithms_for_calculating_variance.
@ -114,10 +123,4 @@ impl Mean {
}
}
impl core::default::Default for Mean {
fn default() -> Mean {
Mean::new()
}
}
impl_from_iterator!(Mean);

View File

@ -1,3 +1,9 @@
use core;
use conv::ApproxFrom;
use super::{Estimate, Merge};
include!("mean.rs");
include!("variance.rs");
include!("skewness.rs");

View File

@ -20,15 +20,6 @@ impl Skewness {
}
}
/// Add an observation sampled from the population.
#[inline]
pub fn add(&mut self, x: f64) {
let delta = x - self.mean();
self.increment();
let n = f64::approx_from(self.len()).unwrap();
self.add_inner(delta, delta/n);
}
/// Increment the sample size.
///
/// This does not update anything else.
@ -107,10 +98,32 @@ impl Skewness {
debug_assert_ne!(sum_2, 0.);
n.sqrt() * self.sum_3 / (sum_2*sum_2*sum_2).sqrt()
}
}
/// Merge another sample into this one.
impl Default for Skewness {
fn default() -> Skewness {
Skewness::new()
}
}
impl Estimate for Skewness {
#[inline]
pub fn merge(&mut self, other: &Skewness) {
fn add(&mut self, x: f64) {
let delta = x - self.mean();
self.increment();
let n = f64::approx_from(self.len()).unwrap();
self.add_inner(delta, delta/n);
}
#[inline]
fn estimate(&self) -> f64 {
self.skewness()
}
}
impl Merge for Skewness {
#[inline]
fn merge(&mut self, other: &Skewness) {
let len_self = f64::approx_from(self.len()).unwrap();
let len_other = f64::approx_from(other.len()).unwrap();
let len_total = len_self + len_other;

View File

@ -27,15 +27,6 @@ impl Variance {
Variance { avg: Mean::new(), sum_2: 0. }
}
/// Add an observation sampled from the population.
#[inline]
pub fn add(&mut self, sample: f64) {
self.increment();
let delta_n = (sample - self.avg.mean())
/ f64::approx_from(self.len()).unwrap();
self.add_inner(delta_n);
}
/// Increment the sample size.
///
/// This does not update anything else.
@ -113,13 +104,37 @@ impl Variance {
(self.sample_variance() / f64::approx_from(n).unwrap()).sqrt()
}
}
impl core::default::Default for Variance {
fn default() -> Variance {
Variance::new()
}
}
impl Estimate for Variance {
#[inline]
fn add(&mut self, sample: f64) {
self.increment();
let delta_n = (sample - self.avg.mean())
/ f64::approx_from(self.len()).unwrap();
self.add_inner(delta_n);
}
#[inline]
fn estimate(&self) -> f64 {
self.population_variance()
}
}
impl Merge for Variance {
/// Merge another sample into this one.
///
///
/// ## Example
///
/// ```
/// use average::Variance;
/// use average::{Variance, Merge};
///
/// let sequence: &[f64] = &[1., 2., 3., 4., 5., 6., 7., 8., 9.];
/// let (left, right) = sequence.split_at(3);
@ -131,7 +146,7 @@ impl Variance {
/// assert_eq!(avg_total.sample_variance(), avg_left.sample_variance());
/// ```
#[inline]
pub fn merge(&mut self, other: &Variance) {
fn merge(&mut self, other: &Variance) {
// This algorithm was proposed by Chan et al. in 1979.
//
// See https://en.wikipedia.org/wiki/Algorithms_for_calculating_variance.
@ -144,10 +159,4 @@ impl Variance {
}
}
impl core::default::Default for Variance {
fn default() -> Variance {
Variance::new()
}
}
impl_from_iterator!(Variance);

View File

@ -4,6 +4,8 @@ use core::cmp::min;
use conv::{ApproxFrom, ConvAsUtil, ValueFrom};
use quickersort::sort_floats;
use super::Estimate;
/// Estimate the p-quantile of a sequence of numbers ("population").
#[derive(Debug, Clone)]
pub struct Quantile {
@ -38,62 +40,6 @@ impl Quantile {
self.dm[2]
}
/// Add an observation sampled from the population.
#[inline]
pub fn add(&mut self, x: f64) {
// n[4] is the sample size.
if self.n[4] < 5 {
self.q[usize::value_from(self.n[4]).unwrap()] = x; // n[4] < 5
self.n[4] += 1;
if self.n[4] == 5 {
sort_floats(&mut self.q);
}
return;
}
// Find cell k.
let mut k: usize;
if x < self.q[0] {
self.q[0] = x;
k = 0;
} else {
k = 4;
for i in 1..5 {
if x < self.q[i] {
k = i;
break;
}
}
if self.q[4] < x {
self.q[4] = x;
}
};
// Increment all positions greater than k.
for i in k..5 {
self.n[i] += 1;
}
for i in 0..5 {
self.m[i] += self.dm[i];
}
// Adjust height of markers.
for i in 1..4 {
let d: f64 = self.m[i] - f64::approx_from(self.n[i]).unwrap();
if d >= 1. && self.n[i + 1] - self.n[i] > 1 ||
d <= -1. && self.n[i - 1] - self.n[i] < -1 {
let d = d.signum();
let q_new = self.parabolic(i, d);
if self.q[i - 1] < q_new && q_new < self.q[i + 1] {
self.q[i] = q_new;
} else {
self.q[i] = self.linear(i, d);
}
self.n[i] += d.approx().unwrap(); // d == +-1
}
}
}
/// Parabolic prediction for marker height.
#[inline]
fn parabolic(&self, i: usize, d: f64) -> f64 {
@ -171,6 +117,67 @@ impl core::default::Default for Quantile {
}
}
impl Estimate for Quantile {
#[inline]
fn add(&mut self, x: f64) {
// n[4] is the sample size.
if self.n[4] < 5 {
self.q[usize::value_from(self.n[4]).unwrap()] = x; // n[4] < 5
self.n[4] += 1;
if self.n[4] == 5 {
sort_floats(&mut self.q);
}
return;
}
// Find cell k.
let mut k: usize;
if x < self.q[0] {
self.q[0] = x;
k = 0;
} else {
k = 4;
for i in 1..5 {
if x < self.q[i] {
k = i;
break;
}
}
if self.q[4] < x {
self.q[4] = x;
}
};
// Increment all positions greater than k.
for i in k..5 {
self.n[i] += 1;
}
for i in 0..5 {
self.m[i] += self.dm[i];
}
// Adjust height of markers.
for i in 1..4 {
let d: f64 = self.m[i] - f64::approx_from(self.n[i]).unwrap();
if d >= 1. && self.n[i + 1] - self.n[i] > 1 ||
d <= -1. && self.n[i - 1] - self.n[i] < -1 {
let d = d.signum();
let q_new = self.parabolic(i, d);
if self.q[i - 1] < q_new && q_new < self.q[i + 1] {
self.q[i] = q_new;
} else {
self.q[i] = self.linear(i, d);
}
self.n[i] += d.approx().unwrap(); // d == +-1
}
}
}
fn estimate(&self) -> f64 {
self.quantile()
}
}
#[test]
fn reference() {
let observations = [

View File

@ -1,3 +1,5 @@
use super::{Estimate, Merge};
/// Estimate the reduction of a sequence of numbers ("population").
///
/// The reduction is a given function `Fn(f64, f64) -> f64`.
@ -10,30 +12,41 @@ pub struct Reduce<F> {
reduce: F,
}
impl<F> Reduce<F>
where F: Fn(f64, f64) -> f64
{
impl<F> Reduce<F> {
/// Create a new reduction estimator given an initial value and a reduction.
#[inline]
pub fn from_value_and_fn(x: f64, f: F) -> Reduce<F> {
Reduce { x: x, reduce: f }
}
/// Add an element sampled from the population.
#[inline]
pub fn add(&mut self, x: f64) {
self.x = (self.reduce)(self.x, x);
}
/// Estimate the reduction of the population.
#[inline]
pub fn reduction(&self) -> f64 {
self.x
}
}
impl<F> Estimate for Reduce<F>
where F: Fn(f64, f64) -> f64,
{
#[inline]
fn add(&mut self, x: f64) {
self.x = (self.reduce)(self.x, x);
}
#[inline]
fn estimate(&self) -> f64 {
self.reduction()
}
}
impl<F> Merge for Reduce<F>
where F: Fn(f64, f64) -> f64,
{
/// Merge another sample into this one.
#[inline]
pub fn merge(&mut self, other: &Reduce<F>) {
fn merge(&mut self, other: &Reduce<F>) {
self.add(other.x);
}
}

View File

@ -1,6 +1,6 @@
use core;
use super::MeanWithError;
use super::{MeanWithError, Estimate, Merge};
/// Estimate the weighted and unweighted arithmetic mean of a sequence of
@ -32,7 +32,7 @@ impl WeightedMean {
}
}
/// Add a weighted observation sampled from the population.
/// Add an observation sampled from the population.
#[inline]
pub fn add(&mut self, sample: f64, weight: f64) {
// The algorithm for the unweighted mean was suggested by Welford in 1962.
@ -70,33 +70,6 @@ impl WeightedMean {
pub fn mean(&self) -> f64 {
self.weighted_avg
}
/// Merge another sample into this one.
///
///
/// ## Example
///
/// ```
/// use average::WeightedMean;
///
/// let weighted_sequence: &[(f64, f64)] = &[
/// (1., 0.1), (2., 0.2), (3., 0.3), (4., 0.4), (5., 0.5),
/// (6., 0.6), (7., 0.7), (8., 0.8), (9., 0.9)];
/// let (left, right) = weighted_sequence.split_at(3);
/// let avg_total: WeightedMean = weighted_sequence.iter().map(|&x| x).collect();
/// let mut avg_left: WeightedMean = left.iter().map(|&x| x).collect();
/// let avg_right: WeightedMean = right.iter().map(|&x| x).collect();
/// avg_left.merge(&avg_right);
/// assert!((avg_total.mean() - avg_left.mean()).abs() < 1e-15);
/// ```
#[inline]
pub fn merge(&mut self, other: &WeightedMean) {
let total_weight_sum = self.weight_sum + other.weight_sum;
self.weighted_avg = (self.weight_sum * self.weighted_avg
+ other.weight_sum * other.weighted_avg)
/ total_weight_sum;
self.weight_sum = total_weight_sum;
}
}
impl core::default::Default for WeightedMean {
@ -117,6 +90,35 @@ impl core::iter::FromIterator<(f64, f64)> for WeightedMean {
}
}
impl Merge for WeightedMean {
/// Merge another sample into this one.
///
///
/// ## Example
///
/// ```
/// use average::{WeightedMean, Merge};
///
/// let weighted_sequence: &[(f64, f64)] = &[
/// (1., 0.1), (2., 0.2), (3., 0.3), (4., 0.4), (5., 0.5),
/// (6., 0.6), (7., 0.7), (8., 0.8), (9., 0.9)];
/// let (left, right) = weighted_sequence.split_at(3);
/// let avg_total: WeightedMean = weighted_sequence.iter().map(|&x| x).collect();
/// let mut avg_left: WeightedMean = left.iter().map(|&x| x).collect();
/// let avg_right: WeightedMean = right.iter().map(|&x| x).collect();
/// avg_left.merge(&avg_right);
/// assert!((avg_total.mean() - avg_left.mean()).abs() < 1e-15);
/// ```
#[inline]
fn merge(&mut self, other: &WeightedMean) {
let total_weight_sum = self.weight_sum + other.weight_sum;
self.weighted_avg = (self.weight_sum * self.weighted_avg
+ other.weight_sum * other.weighted_avg)
/ total_weight_sum;
self.weight_sum = total_weight_sum;
}
}
/// Estimate the weighted and unweighted arithmetic mean and the unweighted
/// variance of a sequence of numbers ("population").
///
@ -153,7 +155,7 @@ impl WeightedMeanWithError {
}
}
/// Add a weighted observation sampled from the population.
/// Add an observation sampled from the population.
#[inline]
pub fn add(&mut self, sample: f64, weight: f64) {
// The algorithm for the unweighted mean was suggested by Welford in 1962.
@ -225,6 +227,7 @@ impl WeightedMeanWithError {
/// Calculate the *unweighted* population variance of the sample.
///
/// This is a biased estimator of the variance of the population.
#[inline]
pub fn population_variance(&self) -> f64 {
self.unweighted_avg.population_variance()
}
@ -232,6 +235,7 @@ impl WeightedMeanWithError {
/// Calculate the *unweighted* sample variance.
///
/// This is an unbiased estimator of the variance of the population.
#[inline]
pub fn sample_variance(&self) -> f64 {
self.unweighted_avg.sample_variance()
}
@ -242,6 +246,7 @@ impl WeightedMeanWithError {
///
/// This unbiased estimator assumes that the samples were independently
/// drawn from the same population with constant variance.
#[inline]
pub fn error(&self) -> f64 {
// This uses the same estimate as WinCross, which should provide better
// results than the ones used by SPSS or Mentor.
@ -254,14 +259,16 @@ impl WeightedMeanWithError {
let inv_effective_len = self.weight_sum_sq / (weight_sum * weight_sum);
(self.sample_variance() * inv_effective_len).sqrt()
}
}
impl Merge for WeightedMeanWithError {
/// Merge another sample into this one.
///
///
/// ## Example
///
/// ```
/// use average::WeightedMeanWithError;
/// use average::{WeightedMeanWithError, Merge};
///
/// let weighted_sequence: &[(f64, f64)] = &[
/// (1., 0.1), (2., 0.2), (3., 0.3), (4., 0.4), (5., 0.5),
@ -274,7 +281,8 @@ impl WeightedMeanWithError {
/// assert!((avg_total.weighted_mean() - avg_left.weighted_mean()).abs() < 1e-15);
/// assert!((avg_total.error() - avg_left.error()).abs() < 1e-15);
/// ```
pub fn merge(&mut self, other: &WeightedMeanWithError) {
#[inline]
fn merge(&mut self, other: &WeightedMeanWithError) {
self.weight_sum_sq += other.weight_sum_sq;
self.weighted_avg.merge(&other.weighted_avg);
self.unweighted_avg.merge(&other.unweighted_avg);

View File

@ -6,7 +6,7 @@ extern crate core;
use core::iter::Iterator;
use average::Kurtosis;
use average::{Kurtosis, Estimate, Merge};
#[test]
fn trivial() {

View File

@ -2,6 +2,8 @@
extern crate core;
use average::Estimate;
#[test]
fn concatenate_simple() {
use average::{Min, Max};

View File

@ -6,7 +6,7 @@ extern crate core;
use core::iter::Iterator;
use average::Max;
use average::{Max, Estimate, Merge};
#[test]
fn trivial() {

View File

@ -6,7 +6,7 @@ extern crate core;
use core::iter::Iterator;
use average::MeanWithError;
use average::{MeanWithError, Estimate, Merge};
#[test]
fn trivial() {

View File

@ -6,7 +6,7 @@ extern crate core;
use core::iter::Iterator;
use average::Min;
use average::{Min, Estimate, Merge};
#[test]
fn trivial() {

View File

@ -4,7 +4,7 @@
extern crate rand;
use average::Kurtosis;
use average::{Kurtosis, Estimate};
#[test]
fn normal_distribution() {

View File

@ -6,7 +6,7 @@ extern crate core;
use core::iter::Iterator;
use average::Skewness;
use average::{Skewness, Estimate, Merge};
#[test]
fn trivial() {

View File

@ -6,7 +6,7 @@ extern crate core;
use core::iter::Iterator;
use average::WeightedMeanWithError;
use average::{WeightedMeanWithError, Merge};
#[test]
fn trivial() {