blob: 4f26e4e0d3cb63357955efd952c68c99402b349a [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.commons.math4.legacy.distribution;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URL;
import java.util.ArrayList;
import org.apache.commons.rng.UniformRandomProvider;
import org.apache.commons.rng.simple.RandomSource;
import org.apache.commons.statistics.distribution.ContinuousDistribution;
import org.apache.commons.statistics.distribution.UniformContinuousDistribution;
import org.apache.commons.statistics.distribution.NormalDistribution;
import org.apache.commons.statistics.distribution.ExponentialDistribution;
import org.apache.commons.math4.legacy.TestUtils;
import org.apache.commons.math4.legacy.analysis.UnivariateFunction;
import org.apache.commons.math4.legacy.analysis.integration.BaseAbstractUnivariateIntegrator;
import org.apache.commons.math4.legacy.analysis.integration.IterativeLegendreGaussIntegrator;
import org.apache.commons.math4.legacy.exception.NotStrictlyPositiveException;
import org.apache.commons.math4.legacy.stat.descriptive.SummaryStatistics;
import org.apache.commons.math4.legacy.core.jdkmath.AccurateMath;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
/**
* Test cases for the {@link EmpiricalDistribution} class.
*/
public final class EmpiricalDistributionTest extends RealDistributionAbstractTest {
private EmpiricalDistribution empiricalDistribution = null;
private double[] dataArray = null;
private final int n = 10000;
/** Uniform bin mass = 10/10001 == mass of all but the first bin */
private final double binMass = 10d / (n + 1);
/** Mass of first bin = 11/10001 */
private final double firstBinMass = 11d / (n + 1);
@Override
@Before
public void setUp() {
super.setUp();
final URL url = getClass().getResource("testData.txt");
final ArrayList<Double> list = new ArrayList<>();
try {
final BufferedReader in = new BufferedReader(new InputStreamReader(url.openStream()));
String str = null;
while ((str = in.readLine()) != null) {
list.add(Double.valueOf(str));
}
in.close();
} catch (IOException ex) {
Assert.fail("IOException " + ex);
}
dataArray = new double[list.size()];
int i = 0;
for (Double data : list) {
dataArray[i] = data.doubleValue();
i++;
}
empiricalDistribution = EmpiricalDistribution.from(100, dataArray);
}
// MATH-1279
@Test(expected=NotStrictlyPositiveException.class)
public void testPrecondition1() {
EmpiricalDistribution.from(0, new double[] {1,2,3});
}
/**
* Test using data taken from sample data file.
* Check that the sampleCount, mu and sigma match data in the sample data file.
*/
@Test
public void testDoubleLoad() {
// testData File has 10000 values, with mean ~ 5.0, std dev ~ 1
// Make sure that loaded distribution matches this
Assert.assertEquals(empiricalDistribution.getSampleStats().getN(),
1000, 1e-7);
//TODO: replace with statistical tests
Assert.assertEquals(empiricalDistribution.getSampleStats().getMean(),
5.069831575018909, 1e-7);
Assert.assertEquals(empiricalDistribution.getSampleStats().getStandardDeviation(),
1.0173699343977738, 1e-7);
double[] bounds = empiricalDistribution.getGeneratorUpperBounds();
Assert.assertEquals(bounds.length, 100);
Assert.assertEquals(bounds[99], 1.0, 10e-12);
}
// MATH-1531
@Test
public void testMath1531() {
final double[] data = new double[] {
50.993456376721454, 49.455345691918055, 49.527276095295804, 50.017183448668845, 49.10508147470046,
49.813998274118696, 50.87195348756139, 50.419474110037, 50.63614906979689, 49.49694777179407,
50.71799078406067, 50.03192853759164, 49.915092423165994, 49.56895392597687, 51.034638001064934,
50.681227971275945, 50.43749845081759, 49.86513120270245, 50.21475262482965, 49.99202971042547,
50.02382189838519, 49.386888585302884, 49.45585010202781, 49.988009479855435, 49.8136712206123,
49.6715197127997, 50.1981278397565, 49.842297508010276, 49.62491227740015, 50.05101916097176,
48.834912763303926, 49.806787657848574, 49.478236106374695, 49.56648347371614, 49.95069238081982,
49.71845132077346, 50.6097468705947, 49.80724637775541, 49.90448813086025, 49.39641861662603,
50.434295712893714, 49.227176959566734, 49.541126466050905, 49.03416593170446, 49.11584328494423,
49.61387482435674, 49.92877857995328, 50.70638552955101, 50.60078208448842, 49.39326233277838,
49.21488424364095, 49.69503351015096, 50.13733214001718, 50.22084761458942, 51.09804435604931,
49.18559131120419, 49.52286371605357, 49.34804374996689, 49.6901827776375, 50.01316351359638,
48.7751460520373, 50.12961836291053, 49.9978419772511, 49.885658399408584, 49.673438879979834,
49.45565980965606, 50.429747484906564, 49.40129274804164, 50.13034614008073, 49.87685735146651,
50.12967905393557, 50.323560376181696, 49.83519233651367, 49.37333369733053, 49.70074301611427,
50.11626105774947, 50.28249500380083, 50.543354367136466, 50.05866241335002, 50.39516515672527,
49.4838561463057, 50.451757089234796, 50.31370674203726, 49.79063762614284, 50.19652349768548,
49.75881420748814, 49.98371855036422, 49.82171344472916, 48.810793204162415, 49.37040569084592,
50.050641186203976, 50.48360952263646, 50.86666450358076, 50.463268776129844, 50.137489751888666,
50.23823061444118, 49.881460479468004, 50.641174398764356, 49.09314136851421, 48.80877928574451,
50.46197084844826, 49.97691704141741, 49.99933997561926, 50.25692254481885, 49.52973451252715,
49.81229858420664, 48.996112655915994, 48.740531054814674, 50.026642633066416, 49.98696633604899,
49.61307159972952, 50.5115278979726, 50.75245152442404, 50.51807785445929, 49.60929671768147,
49.1079533564074, 49.65347196551866, 49.31684818724059, 50.4906368627049, 50.37483603684714
};
EmpiricalDistribution.from(120, data).inverseCumulativeProbability(0.7166666666666669);
}
/**
* Generate 1000 random values and make sure they look OK.<br>
* Note that there is a non-zero (but very small) probability that
* these tests will fail even if the code is working as designed.
*/
@Test
public void testNext() {
tstGen(empiricalDistribution,
0.1);
}
/**
* Make sure we can handle a grid size that is too fine
*/
@Test
public void testGridTooFine() {
tstGen(EmpiricalDistribution.from(1001, dataArray),
0.1);
}
/**
* How about too fat?
*/
@Test
public void testGridTooFat() {
tstGen(EmpiricalDistribution.from(1, dataArray),
5); // ridiculous tolerance; but ridiculous grid size
// really just checking to make sure we do not bomb
}
/**
* Test bin index overflow problem (BZ 36450)
*/
@Test
public void testBinIndexOverflow() {
double[] x = new double[] {9474.94326071674, 2080107.8865462579};
EmpiricalDistribution.from(1000, x);
}
@Test(expected=NullPointerException.class)
public void testLoadNullDoubleArray() {
EmpiricalDistribution.from(1000, null);
}
/**
* MATH-298
*/
@Test
public void testGetBinUpperBounds() {
double[] testData = {0, 1, 1, 2, 3, 4, 4, 5, 6, 7, 8, 9, 10};
EmpiricalDistribution dist = EmpiricalDistribution.from(5, testData);
double[] expectedBinUpperBounds = {2, 4, 6, 8, 10};
double[] expectedGeneratorUpperBounds = {4d/13d, 7d/13d, 9d/13d, 11d/13d, 1};
double tol = 10E-12;
TestUtils.assertEquals(expectedBinUpperBounds, dist.getUpperBounds(), tol);
TestUtils.assertEquals(expectedGeneratorUpperBounds, dist.getGeneratorUpperBounds(), tol);
}
private void verifySame(EmpiricalDistribution d1,
EmpiricalDistribution d2) {
Assert.assertEquals(d1.getBinCount(), d2.getBinCount());
Assert.assertEquals(d1.getSampleStats(), d2.getSampleStats());
for (int i = 0; i < d1.getUpperBounds().length; i++) {
Assert.assertEquals(d1.getUpperBounds()[i], d2.getUpperBounds()[i], 0);
}
Assert.assertEquals(d1.getBinStats(), d2.getBinStats());
}
private void tstGen(EmpiricalDistribution dist,
double tolerance) {
final ContinuousDistribution.Sampler sampler
= dist.createSampler(RandomSource.WELL_19937_C.create(1000));
final SummaryStatistics stats = new SummaryStatistics();
for (int i = 1; i < 1000; i++) {
stats.addValue(sampler.sample());
}
Assert.assertEquals("mean", 5.069831575018909, stats.getMean(),tolerance);
Assert.assertEquals("std dev", 1.0173699343977738, stats.getStandardDeviation(),tolerance);
}
// Setup for distribution tests
@Override
public ContinuousDistribution makeDistribution() {
// Create a uniform distribution on [0, 10,000].
final double[] sourceData = new double[n + 1];
for (int i = 0; i < n + 1; i++) {
sourceData[i] = i;
}
EmpiricalDistribution dist = EmpiricalDistribution.from(1000, sourceData);
return dist;
}
@Override
public double[] makeCumulativeTestPoints() {
final double[] testPoints = new double[] {9, 10, 15, 1000, 5004, 9999};
return testPoints;
}
@Override
public double[] makeCumulativeTestValues() {
/*
* Bins should be [0, 10], (10, 20], ..., (9990, 10000]
* Kernels should be N(4.5, 3.02765), N(14.5, 3.02765)...
* Each bin should have mass 10/10000 = .001
*/
final double[] testPoints = getCumulativeTestPoints();
final double[] cumValues = new double[testPoints.length];
final EmpiricalDistribution empiricalDistribution = (EmpiricalDistribution) makeDistribution();
final double[] binBounds = empiricalDistribution.getUpperBounds();
for (int i = 0; i < testPoints.length; i++) {
final int bin = findBin(testPoints[i]);
final double lower = bin == 0 ? empiricalDistribution.getSupportLowerBound() :
binBounds[bin - 1];
final double upper = binBounds[bin];
// Compute bMinus = sum or mass of bins below the bin containing the point
// First bin has mass 11 / 10000, the rest have mass 10 / 10000.
final double bMinus = bin == 0 ? 0 : (bin - 1) * binMass + firstBinMass;
final ContinuousDistribution kernel = findKernel(lower, upper);
final double withinBinKernelMass = kernel.probability(lower, upper);
final double kernelCum = kernel.probability(lower, testPoints[i]);
cumValues[i] = bMinus + (bin == 0 ? firstBinMass : binMass) * kernelCum/withinBinKernelMass;
}
return cumValues;
}
@Override
public double[] makeDensityTestValues() {
final double[] testPoints = getCumulativeTestPoints();
final double[] densityValues = new double[testPoints.length];
final EmpiricalDistribution empiricalDistribution = (EmpiricalDistribution) makeDistribution();
final double[] binBounds = empiricalDistribution.getUpperBounds();
for (int i = 0; i < testPoints.length; i++) {
final int bin = findBin(testPoints[i]);
final double lower = bin == 0 ? empiricalDistribution.getSupportLowerBound() :
binBounds[bin - 1];
final double upper = binBounds[bin];
final ContinuousDistribution kernel = findKernel(lower, upper);
final double withinBinKernelMass = kernel.probability(lower, upper);
final double density = kernel.density(testPoints[i]);
densityValues[i] = density * (bin == 0 ? firstBinMass : binMass) / withinBinKernelMass;
}
return densityValues;
}
/**
* Modify test integration bounds from the default. Because the distribution
* has discontinuities at bin boundaries, integrals spanning multiple bins
* will face convergence problems. Only test within-bin integrals and spans
* across no more than 3 bin boundaries.
*/
@Override
@Test
public void testDensityIntegrals() {
final ContinuousDistribution distribution = makeDistribution();
final double tol = 1.0e-9;
final BaseAbstractUnivariateIntegrator integrator =
new IterativeLegendreGaussIntegrator(5, 1.0e-12, 1.0e-10);
final UnivariateFunction d = new UnivariateFunction() {
@Override
public double value(double x) {
return distribution.density(x);
}
};
final double[] lower = {0, 5, 1000, 5001, 9995};
final double[] upper = {5, 12, 1030, 5010, 10000};
for (int i = 1; i < 5; i++) {
Assert.assertEquals(
distribution.probability(
lower[i], upper[i]),
integrator.integrate(
1000000, // Triangle integrals are very slow to converge
d, lower[i], upper[i]), tol);
}
}
/**
* MATH-984
* Verify that sampled values do not go outside of the range of the data.
*/
@Test
public void testSampleValuesRange() {
// Concentrate values near the endpoints of (0, 1).
// Unconstrained Gaussian kernel would generate values outside the interval.
final double[] data = new double[100];
for (int i = 0; i < 50; i++) {
data[i] = 1 / ((double) i + 1);
}
for (int i = 51; i < 100; i++) {
data[i] = 1 - 1 / (100 - (double) i + 2);
}
EmpiricalDistribution dist = EmpiricalDistribution.from(10, data);
ContinuousDistribution.Sampler sampler
= dist.createSampler(RandomSource.WELL_19937_C.create(1000));
for (int i = 0; i < 1000; i++) {
final double dev = sampler.sample();
Assert.assertTrue(dev < 1);
Assert.assertTrue(dev > 0);
}
}
/**
* MATH-1203, MATH-1208
*/
@Test
public void testNoBinVariance() {
final double[] data = {0, 0, 1, 1};
EmpiricalDistribution dist = EmpiricalDistribution.from(2, data);
ContinuousDistribution.Sampler sampler
= dist.createSampler(RandomSource.WELL_19937_C.create(1000));
for (int i = 0; i < 1000; i++) {
final double dev = sampler.sample();
Assert.assertTrue(dev == 0 || dev == 1);
}
Assert.assertEquals(0.5, dist.cumulativeProbability(0), Double.MIN_VALUE);
Assert.assertEquals(1.0, dist.cumulativeProbability(1), Double.MIN_VALUE);
Assert.assertEquals(0.5, dist.cumulativeProbability(0.5), Double.MIN_VALUE);
Assert.assertEquals(0.5, dist.cumulativeProbability(0.7), Double.MIN_VALUE);
}
/**
* Find the bin that x belongs (relative to {@link #makeDistribution()}).
*/
private int findBin(double x) {
// Number of bins below x should be trunc(x/10)
final double nMinus = AccurateMath.floor(x / 10);
final int bin = (int) AccurateMath.round(nMinus);
// If x falls on a bin boundary, it is in the lower bin
return AccurateMath.floor(x / 10) == x / 10 ? bin - 1 : bin;
}
/**
* Find the within-bin kernel for the bin with lower bound lower
* and upper bound upper. All bins other than the first contain 10 points
* exclusive of the lower bound and are centered at (lower + upper + 1) / 2.
* The first bin includes its lower bound, 0, so has different mean and
* standard deviation.
*/
private ContinuousDistribution findKernel(double lower, double upper) {
if (lower < 1) {
return new NormalDistribution(5d, 3.3166247903554);
} else {
return new NormalDistribution((upper + lower + 1) / 2d, 3.0276503540974917);
}
}
@Test
public void testKernelOverrideUniform() {
final double[] data = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};
final EmpiricalDistribution dist =
EmpiricalDistribution.from(5, data,
s -> new UniformContinuousDistribution(s.getMin(), s.getMax()));
final ContinuousDistribution.Sampler sampler
= dist.createSampler(RandomSource.WELL_19937_C.create(1000));
// Kernels are uniform distributions on [1,3], [4,6], [7,9], [10,12], [13,15]
final double bounds[] = {3d, 6d, 9d, 12d};
final double tol = 10E-12;
for (int i = 0; i < 20; i++) {
final double v = sampler.sample();
// Make sure v is not in the excluded range between bins - that is (bounds[i], bounds[i] + 1)
for (int j = 0; j < bounds.length; j++) {
Assert.assertFalse(v > bounds[j] + tol && v < bounds[j] + 1 - tol);
}
}
Assert.assertEquals(0.0, dist.cumulativeProbability(1), tol);
Assert.assertEquals(0.1, dist.cumulativeProbability(2), tol);
Assert.assertEquals(0.6, dist.cumulativeProbability(10), tol);
Assert.assertEquals(0.8, dist.cumulativeProbability(12), tol);
Assert.assertEquals(0.8, dist.cumulativeProbability(13), tol);
Assert.assertEquals(1.0, dist.cumulativeProbability(15), tol);
Assert.assertEquals(2.0, dist.inverseCumulativeProbability(0.1), tol);
Assert.assertEquals(3.0, dist.inverseCumulativeProbability(0.2), tol);
Assert.assertEquals(5.0, dist.inverseCumulativeProbability(0.3), tol);
Assert.assertEquals(6.0, dist.inverseCumulativeProbability(0.4), tol);
Assert.assertEquals(8.0, dist.inverseCumulativeProbability(0.5), tol);
Assert.assertEquals(9.0, dist.inverseCumulativeProbability(0.6), tol);
}
@Test
public void testMath1431() {
final UniformRandomProvider rng = RandomSource.WELL_19937_C.create(1000);
final ContinuousDistribution.Sampler exponentialDistributionSampler
= new ExponentialDistribution(0.05).createSampler(rng);
final double[] empiricalDataPoints = new double[3000];
for (int i = 0; i < empiricalDataPoints.length; i++) {
empiricalDataPoints[i] = exponentialDistributionSampler.sample();
}
final EmpiricalDistribution testDistribution = EmpiricalDistribution.from(100, empiricalDataPoints);
for (int i = 0; i < 1000; i++) {
final double point = rng.nextDouble();
final double cdf = testDistribution.cumulativeProbability(point);
Assert.assertFalse("point: " + point, Double.isNaN(cdf));
}
}
@Test
public void testMath1462() {
final double[] data = {
6464.0205, 6449.1328, 6489.4569, 6497.5533, 6251.6487,
6252.6513, 6339.7883, 6356.2622, 6222.1251, 6157.3813,
6242.4741, 6332.5347, 6468.0633, 6471.2319, 6473.9929,
6589.1322, 6511.2191, 6339.4349, 6307.7735, 6288.0915,
6354.0572, 6385.8283, 6325.3756, 6433.1699, 6433.6507,
6424.6806, 6380.5268, 6407.6705, 6241.2198, 6230.3681,
6367.5943, 6358.4817, 6272.8039, 6269.0211, 6312.9027,
6349.5926, 6404.0775, 6326.986, 6283.8685, 6309.9021,
6336.8554, 6389.1598, 6281.0372, 6304.8852, 6359.2651,
6426.519, 6400.3926, 6440.6798, 6292.5812, 6398.4911,
6307.0002, 6284.2111, 6271.371, 6368.6377, 6323.3372,
6276.2155, 6335.0117, 6319.2466, 6252.9969, 6445.2074,
6461.3944, 6384.1345
};
final EmpiricalDistribution ed = EmpiricalDistribution.from(data.length / 10, data);
double v;
double p;
p = 0.49999;
v = ed.inverseCumulativeProbability(p);
Assert.assertTrue("p=" + p + " => v=" + v, v < 6344);
p = 0.5;
v = ed.inverseCumulativeProbability(p);
Assert.assertTrue("p=" + p + " => v=" + v, v < 7000);
p = 0.51111;
v = ed.inverseCumulativeProbability(p);
Assert.assertTrue("p=" + p + " => v=" + v, v < 6350);
}
@Test
public void testMath1462InfiniteQuantile() {
final double[] data = {
18054, 17548, 17350, 17860, 17827, 17653, 18113, 18405, 17746,
17647, 18160, 17955, 17705, 17890, 17974, 17857, 13287, 18645,
17775, 17730, 17996, 18263, 17861, 17161, 17717, 18134, 18669,
18340, 17221, 18292, 18146, 17520, 18207, 17829, 18206, 13301,
18257, 17626, 18358, 18340, 18320, 17852, 17804, 17577, 17718,
18099, 13395, 17763, 17911, 17978, 12935, 17519, 17550, 18728,
18518, 17698, 18739, 18553, 17982, 18113, 17974, 17961, 17645,
17867, 17890, 17498, 18718, 18191, 18177, 17923, 18164, 18155,
6212, 5961, 711
};
final EmpiricalDistribution ed = EmpiricalDistribution.from(1000, data);
double v;
double p;
p = 0.32;
v = ed.inverseCumulativeProbability(p);
Assert.assertTrue("p=" + p + " => v=" + v, Double.isFinite(v));
}
}