blob: 61aa3f1b833588b078323a92e26364d5817143c1 [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.analysis.interpolation;
import org.apache.commons.math4.legacy.exception.DimensionMismatchException;
import org.apache.commons.math4.legacy.exception.NoDataException;
import org.apache.commons.math4.legacy.exception.NonMonotonicSequenceException;
import org.apache.commons.math4.legacy.exception.NotFiniteNumberException;
import org.apache.commons.math4.legacy.exception.NumberIsTooSmallException;
import org.apache.commons.math4.legacy.exception.OutOfRangeException;
import org.apache.commons.math4.core.jdkmath.JdkMath;
import org.junit.Assert;
import org.junit.Test;
/**
* Test of the LoessInterpolator class.
*/
public class LoessInterpolatorTest {
@Test
public void testOnOnePoint() {
double[] xval = {0.5};
double[] yval = {0.7};
double[] res = new LoessInterpolator().smooth(xval, yval);
Assert.assertEquals(1, res.length);
Assert.assertEquals(0.7, res[0], 0.0);
}
@Test
public void testOnTwoPoints() {
double[] xval = {0.5, 0.6};
double[] yval = {0.7, 0.8};
double[] res = new LoessInterpolator().smooth(xval, yval);
Assert.assertEquals(2, res.length);
Assert.assertEquals(0.7, res[0], 0.0);
Assert.assertEquals(0.8, res[1], 0.0);
}
@Test
public void testOnStraightLine() {
double[] xval = {1,2,3,4,5};
double[] yval = {2,4,6,8,10};
LoessInterpolator li = new LoessInterpolator(0.6, 2, 1e-12);
double[] res = li.smooth(xval, yval);
Assert.assertEquals(5, res.length);
for(int i = 0; i < 5; ++i) {
Assert.assertEquals(yval[i], res[i], 1e-8);
}
}
@Test
public void testOnDistortedSine() {
int numPoints = 100;
double[] xval = new double[numPoints];
double[] yval = new double[numPoints];
double xnoise = 0.1;
double ynoise = 0.2;
generateSineData(xval, yval, xnoise, ynoise);
LoessInterpolator li = new LoessInterpolator(0.3, 4, 1e-12);
double[] res = li.smooth(xval, yval);
// Check that the resulting curve differs from
// the "real" sine less than the jittered one
double noisyResidualSum = 0;
double fitResidualSum = 0;
for(int i = 0; i < numPoints; ++i) {
double expected = JdkMath.sin(xval[i]);
double noisy = yval[i];
double fit = res[i];
noisyResidualSum += JdkMath.pow(noisy - expected, 2);
fitResidualSum += JdkMath.pow(fit - expected, 2);
}
Assert.assertTrue(fitResidualSum < noisyResidualSum);
}
@Test
public void testIncreasingBandwidthIncreasesSmoothness() {
int numPoints = 100;
double[] xval = new double[numPoints];
double[] yval = new double[numPoints];
double xnoise = 0.1;
double ynoise = 0.1;
generateSineData(xval, yval, xnoise, ynoise);
// Check that variance decreases as bandwidth increases
double[] bandwidths = {0.1, 0.5, 1.0};
double[] variances = new double[bandwidths.length];
for (int i = 0; i < bandwidths.length; i++) {
double bw = bandwidths[i];
LoessInterpolator li = new LoessInterpolator(bw, 4, 1e-12);
double[] res = li.smooth(xval, yval);
for (int j = 1; j < res.length; ++j) {
variances[i] += JdkMath.pow(res[j] - res[j-1], 2);
}
}
for(int i = 1; i < variances.length; ++i) {
Assert.assertTrue(variances[i] < variances[i-1]);
}
}
@Test
public void testIncreasingRobustnessItersIncreasesSmoothnessWithOutliers() {
int numPoints = 100;
double[] xval = new double[numPoints];
double[] yval = new double[numPoints];
double xnoise = 0.1;
double ynoise = 0.1;
generateSineData(xval, yval, xnoise, ynoise);
// Introduce a couple of outliers
yval[numPoints/3] *= 100;
yval[2 * numPoints/3] *= -100;
// Check that variance decreases as the number of robustness
// iterations increases
double[] variances = new double[4];
for (int i = 0; i < 4; i++) {
LoessInterpolator li = new LoessInterpolator(0.3, i, 1e-12);
double[] res = li.smooth(xval, yval);
for (int j = 1; j < res.length; ++j) {
variances[i] += JdkMath.abs(res[j] - res[j-1]);
}
}
for(int i = 1; i < variances.length; ++i) {
Assert.assertTrue(variances[i] < variances[i-1]);
}
}
@Test(expected=DimensionMismatchException.class)
public void testUnequalSizeArguments() {
new LoessInterpolator().smooth(new double[] {1,2,3}, new double[] {1,2,3,4});
}
@Test(expected=NoDataException.class)
public void testEmptyData() {
new LoessInterpolator().smooth(new double[] {}, new double[] {});
}
@Test(expected=NonMonotonicSequenceException.class)
public void testNonStrictlyIncreasing1() {
new LoessInterpolator().smooth(new double[] {4,3,1,2}, new double[] {3,4,5,6});
}
@Test(expected=NonMonotonicSequenceException.class)
public void testNonStrictlyIncreasing2() {
new LoessInterpolator().smooth(new double[] {1,2,2,3}, new double[] {3,4,5,6});
}
@Test(expected=NotFiniteNumberException.class)
public void testNotAllFiniteReal1() {
new LoessInterpolator().smooth(new double[] {1,2,Double.NaN}, new double[] {3,4,5});
}
@Test(expected=NotFiniteNumberException.class)
public void testNotAllFiniteReal2() {
new LoessInterpolator().smooth(new double[] {1,2,Double.POSITIVE_INFINITY}, new double[] {3,4,5});
}
@Test(expected=NotFiniteNumberException.class)
public void testNotAllFiniteReal3() {
new LoessInterpolator().smooth(new double[] {1,2,Double.NEGATIVE_INFINITY}, new double[] {3,4,5});
}
@Test(expected=NotFiniteNumberException.class)
public void testNotAllFiniteReal4() {
new LoessInterpolator().smooth(new double[] {3,4,5}, new double[] {1,2,Double.NaN});
}
@Test(expected=NotFiniteNumberException.class)
public void testNotAllFiniteReal5() {
new LoessInterpolator().smooth(new double[] {3,4,5}, new double[] {1,2,Double.POSITIVE_INFINITY});
}
@Test(expected=NotFiniteNumberException.class)
public void testNotAllFiniteReal6() {
new LoessInterpolator().smooth(new double[] {3,4,5}, new double[] {1,2,Double.NEGATIVE_INFINITY});
}
@Test(expected=NumberIsTooSmallException.class)
public void testInsufficientBandwidth() {
LoessInterpolator li = new LoessInterpolator(0.1, 3, 1e-12);
li.smooth(new double[] {1,2,3,4,5,6,7,8,9,10,11,12}, new double[] {1,2,3,4,5,6,7,8,9,10,11,12});
}
@Test(expected=OutOfRangeException.class)
public void testCompletelyIncorrectBandwidth1() {
new LoessInterpolator(-0.2, 3, 1e-12);
}
@Test(expected=OutOfRangeException.class)
public void testCompletelyIncorrectBandwidth2() {
new LoessInterpolator(1.1, 3, 1e-12);
}
@Test
public void testMath296withoutWeights() {
double[] xval = {
0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0,
1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9, 2.0};
double[] yval = {
0.47, 0.48, 0.55, 0.56, -0.08, -0.04, -0.07, -0.07,
-0.56, -0.46, -0.56, -0.52, -3.03, -3.08, -3.09,
-3.04, 3.54, 3.46, 3.36, 3.35};
// Output from R, rounded to .001
double[] yref = {
0.461, 0.499, 0.541, 0.308, 0.175, -0.042, -0.072,
-0.196, -0.311, -0.446, -0.557, -1.497, -2.133,
-3.08, -3.09, -0.621, 0.982, 3.449, 3.389, 3.336
};
LoessInterpolator li = new LoessInterpolator(0.3, 4, 1e-12);
double[] res = li.smooth(xval, yval);
Assert.assertEquals(xval.length, res.length);
for(int i = 0; i < res.length; ++i) {
Assert.assertEquals(yref[i], res[i], 0.02);
}
}
// MATH-1379
@Test
public void testFitWithUnevenXSpacing() {
final double[] xval = {
0.1, 0.12, 0.23, 0.4, 0.57,
0.7, 0.87, 1.3, 1.9, 2.2,
2.3, 2.65, 3.0, 3.1, 3.5,
4.6, 4.7, 5.8, 5.95, 6.1
};
final double[] yval = {
0.47, 0.48, 0.55, 0.56, -0.08,
-0.04, -0.07, -0.07, -0.56, -0.46,
-0.56, -0.52, -3.03, -3.08, -3.09,
-3.04, 3.54, 3.46, 3.36, 3.35
};
// Compare with output from R.
// predict(loess(y ~ x, data.frame(x=xval, y=yval), span=0.35, degree=1, family="symmetric", control=loess.control(iterations=1, surface="direct")))
final double[] yref = {
0.556184894, 0.541907126, 0.455059334, 0.303681477, 0.142126445,
0.002615653, -0.031178445, -0.187124310, -0.405235207, -0.535023851,
-0.706801740, -1.466740294, -2.349248503, -2.596576469, -3.354222419,
0.086206868, 0.320251370, 3.064778450, 3.426179479, 3.783500164
};
final double delta = 1e-8;
// Note R counts all iterations whereas LoessInterpolator robustness iterations are
// in addition to the initial fit.
final LoessInterpolator li = new LoessInterpolator(0.35, 0, 1e-12);
final double[] res = li.smooth(xval, yval);
Assert.assertEquals(xval.length, res.length);
for (int i = 0; i < res.length; ++i) {
Assert.assertEquals(yref[i], res[i], delta);
}
}
// MATH-1379
@Test
public void testFitWithVaryingWeightsAndUnevenXSpacing() {
final double[] xval = {
0.1, 0.12, 0.23, 0.4, 0.57,
0.7, 0.87, 1.3, 1.9, 2.2,
2.3, 2.65, 3.0, 3.1, 3.5,
4.6, 4.7, 5.8, 5.95, 6.1
};
final double[] yval = {
0.47, 0.48, 0.55, 0.56, -0.08,
-0.04, -0.07, -0.07, -0.56, -0.46,
-0.56, -0.52, -3.03, -3.08, -3.09,
-3.04, 3.54, 3.46, 3.36, 3.35};
final double[] weights = {
1, 1, 1, 1, 1,
1, 1, 1, 1, 1,
1, 0.8, 0.5, 0.5, 0.8,
0.8, 0.5, 0.5, 0.9, 1
};
// Compare with output from R.
// predict(loess(y ~ x, data.frame(x=xval, y=yval), weights, span=0.35, degree=1, family="symmetric", control=loess.control(iterations=1, surface="direct")))
final double[] yref = {
0.556184894, 0.541907126, 0.455059334, 0.303681477, 0.142126445,
0.002615653, -0.031178445, -0.187124310, -0.406403569, -0.531957113,
-0.669978426, -1.411039850, -2.225022609, -2.463874517, -3.298767758,
-0.506116921, -0.231242991, 2.873755984, 3.295897106, 3.715495321};
final double delta = 1e-8;
// Note R counts all iterations whereas LoessInterpolator robustness iterations are
// in addition to the initial fit.
final LoessInterpolator li = new LoessInterpolator(0.35, 0, 1e-12);
final double[] res = li.smooth(xval, yval, weights);
Assert.assertEquals(xval.length, res.length);
for (int i = 0; i < res.length; ++i) {
Assert.assertEquals(yref[i], res[i], delta);
}
}
private void generateSineData(double[] xval, double[] yval, double xnoise, double ynoise) {
double dx = 2 * JdkMath.PI / xval.length;
double x = 0;
for(int i = 0; i < xval.length; ++i) {
xval[i] = x;
yval[i] = JdkMath.sin(x) + (2 * JdkMath.random() - 1) * ynoise;
x += dx * (1 + (2 * JdkMath.random() - 1) * xnoise);
}
}
}