Browse Source

Fixed bugs after running the Sample unit tests.

Fixed some spelling mistakes.
Changed the Histogram implementation to make lowerbound exclusive and upper bound inclusive. [http://mathnetnumerics.codeplex.com/Thread/View.aspx?ThreadId=75115]
Changed the Histogram implementation so that returned buckets are copies (so that users can't start messing with upper and lower bounds).

Signed-off-by: jvangael <jurgen.vangael@gmail.com>
la-knuth
Jurgen Van Gael 17 years ago
parent
commit
a2610c7931
  1. 17
      src/Numerics/Distributions/Discrete/Binomial.cs
  2. 7
      src/Numerics/Distributions/Discrete/Categorical.cs
  3. 65
      src/Numerics/Statistics/Histogram.cs
  4. 34
      src/UnitTests/DistributionTests/CommonDistributionTests.cs
  5. 32
      src/UnitTests/StatisticsTests/HistogramTests.cs

17
src/Numerics/Distributions/Discrete/Binomial.cs

@ -255,8 +255,13 @@ namespace MathNet.Numerics.Distributions
return 1.0;
}
int k = (int) Math.Floor(x);
return (_n - k) * Combinatorics.Combinations(_n,k) * SpecialFunctions.BetaRegularized(_n - k, 1 + k, 1-_p);
double cdf = 0.0;
for (int i = 0; i <= (int) Math.Floor(x); i++)
{
cdf += Combinatorics.Combinations(_n, i) * Math.Pow(_p, i) * Math.Pow(1.0 - _p, _n - i);
}
return cdf;
}
#endregion
@ -366,7 +371,7 @@ namespace MathNet.Numerics.Distributions
/// <summary>
/// Samples a Binomially distributed random variable.
/// </summary>
/// <returns>The number of successful trials.</returns>
/// <returns>The number of successes in <paramref name="n"/> trials.</returns>
public int Sample()
{
return DoSample(RandomSource, _p, _n);
@ -375,7 +380,7 @@ namespace MathNet.Numerics.Distributions
/// <summary>
/// Samples an array of Bernoulli distributed random variables.
/// </summary>
/// <returns>a sequence of successful trial counts.</returns>
/// <returns>a sequence of successes in <paramref name="n"/> trials.</returns>
public IEnumerable<int> Samples()
{
while (true)
@ -392,7 +397,7 @@ namespace MathNet.Numerics.Distributions
/// <param name="rnd">The random number generator to use.</param>
/// <param name="p">The success probability of a trial; must be in the interval [0.0, 1.0].</param>
/// <param name="n">The number of trials; must be positive.</param>
/// <returns>The number of successes in <see cref="N"/> trials.</returns>
/// <returns>The number of successes in <paramref name="n"/> trials.</returns>
public static int Sample(System.Random rnd, double p, int n)
{
if (Control.CheckDistributionParameters && !IsValidParameterSet(p, n))
@ -409,7 +414,7 @@ namespace MathNet.Numerics.Distributions
/// <param name="rnd">The random number generator to use.</param>
/// <param name="p">The success probability of a trial; must be in the interval [0.0, 1.0].</param>
/// <param name="n">The number of trials; must be positive.</param>
/// <returns>a sequence of successful trial counts.</returns>
/// <returns>a sequence of successes in <paramref name="n"/> trials.</returns>
public static IEnumerable<int> Samples(System.Random rnd, double p, int n)
{
if (Control.CheckDistributionParameters && !IsValidParameterSet(p, n))

7
src/Numerics/Distributions/Discrete/Categorical.cs

@ -322,7 +322,7 @@ namespace MathNet.Numerics.Distributions
/// <returns>The number of successful trials.</returns>
public int Sample()
{
return DoSample(RandomSource, _p);
return Sample(RandomSource, _p);
}
/// <summary>
@ -331,10 +331,7 @@ namespace MathNet.Numerics.Distributions
/// <returns>a sequence of successful trial counts.</returns>
public IEnumerable<int> Samples()
{
while (true)
{
yield return DoSample(RandomSource, _p);
}
return Samples(RandomSource, _p);
}
#endregion

65
src/Numerics/Statistics/Histogram.cs

@ -35,10 +35,10 @@ namespace MathNet.Numerics.Statistics
/// <summary>
/// A <see cref="Histogram"/> consists of a series of <see cref="Bucket"/>s,
/// each representing a region limited by a lower bound (inclusive) and an upper bound (exclusive).
/// each representing a region limited by a lower bound (exclusive) and an upper bound (inclusive).
/// </summary>
[Serializable]
public class Bucket : IComparable<Bucket>
public class Bucket : IComparable<Bucket>, ICloneable
{
/// <summary>
/// This <c>IComparer</c> performs comparisons between a point and a bucket.
@ -55,11 +55,11 @@ namespace MathNet.Numerics.Statistics
{
if (bkt2.Width == 0.0)
{
return -bkt1.Contains(bkt2.LowerBound);
return -bkt1.Contains(bkt2.UpperBound);
}
else
{
return -bkt2.Contains(bkt1.LowerBound);
return -bkt2.Contains(bkt1.UpperBound);
}
}
}
@ -108,6 +108,15 @@ namespace MathNet.Numerics.Statistics
Count = count;
}
/// <summary>
/// Creates a copy of the Bucket with the lowerbound, upperbound and counts exactly equal.
/// </summary>
/// <returns>A cloned Bucket object.</returns>
public Object Clone()
{
return new Bucket(LowerBound, UpperBound, Count);
}
/// <summary>
/// Width of the Bucket.
/// </summary>
@ -132,9 +141,9 @@ namespace MathNet.Numerics.Statistics
/// smaller than the bucket, +1 if the point is larger than the bucket.</returns>
public int Contains(double x)
{
if (LowerBound <= x)
if (LowerBound < x)
{
if (UpperBound > x)
if (UpperBound >= x)
{
return 0;
}
@ -146,7 +155,7 @@ namespace MathNet.Numerics.Statistics
}
/// <summary>
/// Comparison of two disjoint buckets.
/// Comparison of two disjoint buckets. The buckets cannot be overlapping.
/// </summary>
public int CompareTo(Bucket bucket)
{
@ -200,7 +209,7 @@ namespace MathNet.Numerics.Statistics
/// <returns></returns>
public override string ToString()
{
return "[" + this.LowerBound + ";" + this.UpperBound + ")";
return "(" + this.LowerBound + ";" + this.UpperBound + "] = " + this.Count;
}
}
@ -247,12 +256,13 @@ namespace MathNet.Numerics.Statistics
double upper = data.Maximum();
double width = (upper - lower) / nbuckets;
// Add buckets for each bin; the biggest bucket must be slightly larger then the maximal element.
for (int n = 0; n < nbuckets-1; n++)
// Add buckets for each bin; the smallest bucket's lowerbound must be slightly smaller
// than the minimal element.
AddBucket(new Bucket(lower.Decrement(), lower + width));
for (int n = 1; n < nbuckets; n++)
{
AddBucket(new Bucket(lower + n * width, lower + (n + 1) * width));
}
AddBucket(new Bucket(lower + (nbuckets - 1) * width, upper.Increment()));
AddData(data);
}
@ -294,20 +304,23 @@ namespace MathNet.Numerics.Statistics
/// <param name="d">The datapoint which we want to add.</param>
public void AddData(double d)
{
// Sort if needed.
LazySort();
if (d < this.LowerBound)
{
this[0].LowerBound = d;
this[0].Count++;
// Make the lower bound just slightly smaller than the datapoint so it is contained in this bucket.
buckets[0].LowerBound = d.Decrement();
buckets[0].Count++;
}
else if (d > this.UpperBound)
{
// Make the upper bound just slightly larger then the datapoint so it is contained in this bucket.
this[BucketCount - 1].UpperBound = d.Increment();
this[BucketCount - 1].Count++;
buckets[BucketCount - 1].UpperBound = d;
buckets[BucketCount - 1].Count++;
}
else
{
GetBucketOf(d).Count++;
buckets[GetBucketIndexOf(d)].Count++;
}
}
@ -345,24 +358,14 @@ namespace MathNet.Numerics.Statistics
}
}
/// <summary>
/// Returns the <c>Bucket</c> with index <paramref name="i"/>.
/// </summary>
/// <param name="i">The index of the bucket to retrieve.</param>
/// <returns>A reference to the bucket at index <paramref name="i"/>.</returns>
public Bucket GetBucket(int i)
{
return buckets[i];
}
/// <summary>
/// Returns the <c>Bucket</c> that contains the value <c>v</c>.
/// </summary>
/// <param name="v">The point to search the bucket for.</param>
/// <returns>The bucket containing the point.</returns>
/// <returns>A copy of the bucket containing point <paramref name="v"/>.</returns>
public Bucket GetBucketOf(double v)
{
return buckets[GetBucketIndexOf(v)];
return (Bucket) buckets[GetBucketIndexOf(v)].Clone();
}
/// <summary>
@ -415,13 +418,13 @@ namespace MathNet.Numerics.Statistics
/// Gets the <c>n</c>'th bucket.
/// </summary>
/// <param name="n">The index of the bucket to be returned.</param>
/// <returns>The <c>n</c>'th bucket.</returns>
/// <returns>A copy of the <c>n</c>'th bucket.</returns>
public Bucket this[int n]
{
get
{
LazySort();
return buckets[n];
return (Bucket) buckets[n].Clone();
}
}

34
src/UnitTests/DistributionTests/CommonDistributionTests.cs

@ -32,8 +32,9 @@ namespace MathNet.Numerics.UnitTests.DistributionTests
using System.Linq;
using System.Collections.Generic;
using MbUnit.Framework;
using MathNet.Numerics.Distributions;
using MathNet.Numerics.Random;
using MathNet.Numerics.Statistics;
using MathNet.Numerics.Distributions;
[TestFixture]
public class CommonDistributionTests
@ -116,29 +117,34 @@ namespace MathNet.Numerics.UnitTests.DistributionTests
[MultipleAsserts]
public void SampleFollowsCorrectDistribution()
{
Random rnd = new MersenneTwister();
// The test samples from the distributions, builds a histogram and checks
// whether the histogram follows the CDF.
foreach (var dd in discreteDistributions)
{
int[] samples = new int[numberOfTestSamples];
dd.RandomSource = rnd;
double[] samples = new double[numberOfTestSamples];
for (int i = 0; i < numberOfTestSamples; i++)
{
samples[i] = dd.Sample();
samples[i] = (double) dd.Sample();
}
var histogram = new Histogram(samples.Select(x => (double)x), numberOfBuckets);
var histogram = new Histogram(samples, numberOfBuckets);
for (int i = 0; i < numberOfBuckets; i++)
{
var bucket = histogram.GetBucket(i);
var bucket = histogram[i];
double empiricalProbability = bucket.Count / (double)numberOfTestSamples;
double realProbability = dd.CumulativeDistribution(bucket.UpperBound)
- dd.CumulativeDistribution(bucket.LowerBound);
Assert.LessThan(Math.Abs(empiricalProbability - realProbability), sampleAccuracy);
Assert.LessThan(Math.Abs(empiricalProbability - realProbability), sampleAccuracy, dd.ToString());
}
}
foreach (var cd in continuousDistributions)
{
cd.RandomSource = rnd;
double[] samples = new double[numberOfTestSamples];
for (int i = 0; i < numberOfTestSamples; i++)
{
@ -148,11 +154,11 @@ namespace MathNet.Numerics.UnitTests.DistributionTests
var histogram = new Histogram(samples, numberOfBuckets);
for (int i = 0; i < numberOfBuckets; i++)
{
var bucket = histogram.GetBucket(i);
var bucket = histogram[i];
double empiricalProbability = bucket.Count / (double)numberOfTestSamples;
double realProbability = cd.CumulativeDistribution(bucket.UpperBound)
- cd.CumulativeDistribution(bucket.LowerBound);
Assert.LessThan(Math.Abs(empiricalProbability - realProbability), sampleAccuracy);
Assert.LessThan(Math.Abs(empiricalProbability - realProbability), sampleAccuracy, cd.ToString());
}
}
}
@ -161,35 +167,39 @@ namespace MathNet.Numerics.UnitTests.DistributionTests
[MultipleAsserts]
public void SamplesFollowsCorrectDistribution()
{
Random rnd = new MersenneTwister();
// The test samples from the distributions, builds a histogram and checks
// whether the histogram follows the CDF.
foreach (var dd in discreteDistributions)
{
dd.RandomSource = rnd;
var samples = dd.Samples().Take(numberOfTestSamples).Select(x => (double)x);
var histogram = new Histogram(samples, numberOfBuckets);
for (int i = 0; i < numberOfBuckets; i++)
{
var bucket = histogram.GetBucket(i);
var bucket = histogram[i];
double empiricalProbability = bucket.Count / (double)numberOfTestSamples;
double realProbability = dd.CumulativeDistribution(bucket.UpperBound)
- dd.CumulativeDistribution(bucket.LowerBound);
Assert.LessThan(Math.Abs(empiricalProbability - realProbability), sampleAccuracy);
Assert.LessThan(Math.Abs(empiricalProbability - realProbability), sampleAccuracy, dd.ToString());
}
}
foreach (var cd in continuousDistributions)
{
cd.RandomSource = rnd;
var samples = cd.Samples().Take(numberOfTestSamples);
var histogram = new Histogram(samples, numberOfBuckets);
for (int i = 0; i < numberOfBuckets; i++)
{
var bucket = histogram.GetBucket(i);
var bucket = histogram[i];
double empiricalProbability = bucket.Count / (double)numberOfTestSamples;
double realProbability = cd.CumulativeDistribution(bucket.UpperBound)
- cd.CumulativeDistribution(bucket.LowerBound);
Assert.LessThan(Math.Abs(empiricalProbability - realProbability), sampleAccuracy);
Assert.LessThan(Math.Abs(empiricalProbability - realProbability), sampleAccuracy, cd.ToString());
}
}
}

32
src/UnitTests/StatisticsTests/HistogramTests.cs

@ -95,7 +95,9 @@ namespace MathNet.Numerics.UnitTests.StatisticsTests
}
[Test]
[Row(0.0, -1)]
[Row(1.0, 0)]
[Row(1.5, 0)]
[Row(2.0, 1)]
[Row(-1.0, -1)]
public void ValidateContains(double x, int r)
@ -106,9 +108,8 @@ namespace MathNet.Numerics.UnitTests.StatisticsTests
#endregion
#region Histogram Tests
#region
[Test]
public void CanCreateEmptyHistogram()
{
@ -123,9 +124,8 @@ namespace MathNet.Numerics.UnitTests.StatisticsTests
}
[Test]
[Row(0.0, 0)]
[Row(0.5, 0)]
[Row(1.0, 1)]
[Row(1.0, 0)]
[Row(10.0, 3)]
[Row(10000.0, 4)]
public void CanGetBucketIndexOf(double x, double i)
@ -139,6 +139,21 @@ namespace MathNet.Numerics.UnitTests.StatisticsTests
Assert.AreEqual(i, h.GetBucketIndexOf(x));
}
[Test]
[Row(0.0)]
[Row(-1.0)]
[ExpectedArgumentException]
public void CanGetBucketIndexOfFailsWhenBucketDoesntExist(double x)
{
var h = new Histogram();
h.AddBucket(new Bucket(0.0, 1.0));
h.AddBucket(new Bucket(1.0, 2.0));
h.AddBucket(new Bucket(2.0, 3.0));
h.AddBucket(new Bucket(3.0, 20.0));
h.AddBucket(new Bucket(20.0, Double.PositiveInfinity));
int i = h.GetBucketIndexOf(x);
}
[Test]
public void CanGetBucketOf()
{
@ -256,18 +271,19 @@ namespace MathNet.Numerics.UnitTests.StatisticsTests
Console.WriteLine("{0}", hist);
for (int i = 0; i < 8; i++)
for (int i = 1; i < 9; i++)
{
Console.WriteLine("{0} : {1}", i, hist[i].Count);
Assert.AreEqual(1.0, hist[i].Count);
}
Assert.AreEqual(2.0, hist[8].Count);
Assert.AreEqual(2.0, hist[0].Count);
Assert.AreEqual(0.5, hist.LowerBound);
Assert.AreEqual(9.5.Increment(), hist.UpperBound);
Assert.AreEqual(0.5.Decrement(), hist.LowerBound);
Assert.AreEqual(9.5, hist.UpperBound);
}
[Test]
[MultipleAsserts]
public void SmallDatasetHistogramWithBounds()
{
Histogram hist = new Histogram(smallDataset, 10, 0.0, 10.0);

Loading…
Cancel
Save