View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.math4.legacy.analysis.interpolation;
18  
19  import org.apache.commons.math4.legacy.exception.DimensionMismatchException;
20  import org.apache.commons.math4.legacy.exception.NoDataException;
21  import org.apache.commons.math4.legacy.exception.NonMonotonicSequenceException;
22  import org.apache.commons.math4.legacy.exception.NotFiniteNumberException;
23  import org.apache.commons.math4.legacy.exception.NumberIsTooSmallException;
24  import org.apache.commons.math4.legacy.exception.OutOfRangeException;
25  import org.apache.commons.math4.core.jdkmath.JdkMath;
26  import org.junit.Assert;
27  import org.junit.Test;
28  
29  /**
30   * Test of the LoessInterpolator class.
31   */
32  public class LoessInterpolatorTest {
33  
34      @Test
35      public void testOnOnePoint() {
36          double[] xval = {0.5};
37          double[] yval = {0.7};
38          double[] res = new LoessInterpolator().smooth(xval, yval);
39          Assert.assertEquals(1, res.length);
40          Assert.assertEquals(0.7, res[0], 0.0);
41      }
42  
43      @Test
44      public void testOnTwoPoints() {
45          double[] xval = {0.5, 0.6};
46          double[] yval = {0.7, 0.8};
47          double[] res = new LoessInterpolator().smooth(xval, yval);
48          Assert.assertEquals(2, res.length);
49          Assert.assertEquals(0.7, res[0], 0.0);
50          Assert.assertEquals(0.8, res[1], 0.0);
51      }
52  
53      @Test
54      public void testOnStraightLine() {
55          double[] xval = {1,2,3,4,5};
56          double[] yval = {2,4,6,8,10};
57          LoessInterpolator li = new LoessInterpolator(0.6, 2, 1e-12);
58          double[] res = li.smooth(xval, yval);
59          Assert.assertEquals(5, res.length);
60          for(int i = 0; i < 5; ++i) {
61              Assert.assertEquals(yval[i], res[i], 1e-8);
62          }
63      }
64  
65      @Test
66      public void testOnDistortedSine() {
67          int numPoints = 100;
68          double[] xval = new double[numPoints];
69          double[] yval = new double[numPoints];
70          double xnoise = 0.1;
71          double ynoise = 0.2;
72  
73          generateSineData(xval, yval, xnoise, ynoise);
74  
75          LoessInterpolator li = new LoessInterpolator(0.3, 4, 1e-12);
76  
77          double[] res = li.smooth(xval, yval);
78  
79          // Check that the resulting curve differs from
80          // the "real" sine less than the jittered one
81  
82          double noisyResidualSum = 0;
83          double fitResidualSum = 0;
84  
85          for(int i = 0; i < numPoints; ++i) {
86              double expected = JdkMath.sin(xval[i]);
87              double noisy = yval[i];
88              double fit = res[i];
89  
90              noisyResidualSum += JdkMath.pow(noisy - expected, 2);
91              fitResidualSum += JdkMath.pow(fit - expected, 2);
92          }
93  
94          Assert.assertTrue(fitResidualSum < noisyResidualSum);
95      }
96  
97      @Test
98      public void testIncreasingBandwidthIncreasesSmoothness() {
99          int numPoints = 100;
100         double[] xval = new double[numPoints];
101         double[] yval = new double[numPoints];
102         double xnoise = 0.1;
103         double ynoise = 0.1;
104 
105         generateSineData(xval, yval, xnoise, ynoise);
106 
107         // Check that variance decreases as bandwidth increases
108 
109         double[] bandwidths = {0.1, 0.5, 1.0};
110         double[] variances = new double[bandwidths.length];
111         for (int i = 0; i < bandwidths.length; i++) {
112             double bw = bandwidths[i];
113 
114             LoessInterpolator li = new LoessInterpolator(bw, 4, 1e-12);
115 
116             double[] res = li.smooth(xval, yval);
117 
118             for (int j = 1; j < res.length; ++j) {
119                 variances[i] += JdkMath.pow(res[j] - res[j-1], 2);
120             }
121         }
122 
123         for(int i = 1; i < variances.length; ++i) {
124             Assert.assertTrue(variances[i] < variances[i-1]);
125         }
126     }
127 
128     @Test
129     public void testIncreasingRobustnessItersIncreasesSmoothnessWithOutliers() {
130         int numPoints = 100;
131         double[] xval = new double[numPoints];
132         double[] yval = new double[numPoints];
133         double xnoise = 0.1;
134         double ynoise = 0.1;
135 
136         generateSineData(xval, yval, xnoise, ynoise);
137 
138         // Introduce a couple of outliers
139         yval[numPoints/3] *= 100;
140         yval[2 * numPoints/3] *= -100;
141 
142         // Check that variance decreases as the number of robustness
143         // iterations increases
144 
145         double[] variances = new double[4];
146         for (int i = 0; i < 4; i++) {
147             LoessInterpolator li = new LoessInterpolator(0.3, i, 1e-12);
148 
149             double[] res = li.smooth(xval, yval);
150 
151             for (int j = 1; j < res.length; ++j) {
152                 variances[i] += JdkMath.abs(res[j] - res[j-1]);
153             }
154         }
155 
156         for(int i = 1; i < variances.length; ++i) {
157             Assert.assertTrue(variances[i] < variances[i-1]);
158         }
159     }
160 
161     @Test(expected=DimensionMismatchException.class)
162     public void testUnequalSizeArguments() {
163         new LoessInterpolator().smooth(new double[] {1,2,3}, new double[] {1,2,3,4});
164     }
165 
166     @Test(expected=NoDataException.class)
167     public void testEmptyData() {
168         new LoessInterpolator().smooth(new double[] {}, new double[] {});
169     }
170 
171     @Test(expected=NonMonotonicSequenceException.class)
172     public void testNonStrictlyIncreasing1() {
173         new LoessInterpolator().smooth(new double[] {4,3,1,2}, new double[] {3,4,5,6});
174     }
175 
176     @Test(expected=NonMonotonicSequenceException.class)
177     public void testNonStrictlyIncreasing2() {
178         new LoessInterpolator().smooth(new double[] {1,2,2,3}, new double[] {3,4,5,6});
179     }
180 
181     @Test(expected=NotFiniteNumberException.class)
182     public void testNotAllFiniteReal1() {
183         new LoessInterpolator().smooth(new double[] {1,2,Double.NaN}, new double[] {3,4,5});
184     }
185 
186     @Test(expected=NotFiniteNumberException.class)
187     public void testNotAllFiniteReal2() {
188         new LoessInterpolator().smooth(new double[] {1,2,Double.POSITIVE_INFINITY}, new double[] {3,4,5});
189     }
190 
191     @Test(expected=NotFiniteNumberException.class)
192     public void testNotAllFiniteReal3() {
193         new LoessInterpolator().smooth(new double[] {1,2,Double.NEGATIVE_INFINITY}, new double[] {3,4,5});
194     }
195 
196     @Test(expected=NotFiniteNumberException.class)
197     public void testNotAllFiniteReal4() {
198         new LoessInterpolator().smooth(new double[] {3,4,5}, new double[] {1,2,Double.NaN});
199     }
200 
201     @Test(expected=NotFiniteNumberException.class)
202     public void testNotAllFiniteReal5() {
203         new LoessInterpolator().smooth(new double[] {3,4,5}, new double[] {1,2,Double.POSITIVE_INFINITY});
204     }
205 
206     @Test(expected=NotFiniteNumberException.class)
207     public void testNotAllFiniteReal6() {
208         new LoessInterpolator().smooth(new double[] {3,4,5}, new double[] {1,2,Double.NEGATIVE_INFINITY});
209     }
210 
211     @Test(expected=NumberIsTooSmallException.class)
212     public void testInsufficientBandwidth() {
213         LoessInterpolator li = new LoessInterpolator(0.1, 3, 1e-12);
214         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});
215     }
216 
217     @Test(expected=OutOfRangeException.class)
218     public void testCompletelyIncorrectBandwidth1() {
219         new LoessInterpolator(-0.2, 3, 1e-12);
220     }
221 
222     @Test(expected=OutOfRangeException.class)
223     public void testCompletelyIncorrectBandwidth2() {
224         new LoessInterpolator(1.1, 3, 1e-12);
225     }
226 
227     @Test
228     public void testMath296withoutWeights() {
229         double[] xval = {
230                 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0,
231                  1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9, 2.0};
232         double[] yval = {
233                 0.47, 0.48, 0.55, 0.56, -0.08, -0.04, -0.07, -0.07,
234                 -0.56, -0.46, -0.56, -0.52, -3.03, -3.08, -3.09,
235                 -3.04, 3.54, 3.46, 3.36, 3.35};
236         // Output from R, rounded to .001
237         double[] yref = {
238                 0.461, 0.499, 0.541, 0.308, 0.175, -0.042, -0.072,
239                 -0.196, -0.311, -0.446, -0.557, -1.497, -2.133,
240                 -3.08, -3.09, -0.621, 0.982, 3.449, 3.389, 3.336
241         };
242         LoessInterpolator li = new LoessInterpolator(0.3, 4, 1e-12);
243         double[] res = li.smooth(xval, yval);
244         Assert.assertEquals(xval.length, res.length);
245         for(int i = 0; i < res.length; ++i) {
246             Assert.assertEquals(yref[i], res[i], 0.02);
247         }
248     }
249 
250     // MATH-1379
251     @Test
252     public void testFitWithUnevenXSpacing() {
253         final double[] xval = {
254             0.1, 0.12, 0.23, 0.4, 0.57,
255             0.7, 0.87, 1.3, 1.9, 2.2,
256             2.3, 2.65, 3.0, 3.1, 3.5,
257             4.6, 4.7, 5.8, 5.95, 6.1
258         };
259         final double[] yval = {
260             0.47, 0.48, 0.55, 0.56, -0.08,
261             -0.04, -0.07, -0.07, -0.56, -0.46,
262             -0.56, -0.52, -3.03, -3.08, -3.09,
263             -3.04, 3.54, 3.46, 3.36, 3.35
264         };
265 
266         // Compare with output from R.
267         // predict(loess(y ~ x, data.frame(x=xval, y=yval), span=0.35, degree=1, family="symmetric", control=loess.control(iterations=1, surface="direct")))
268         final double[] yref = {
269             0.556184894, 0.541907126, 0.455059334, 0.303681477, 0.142126445,
270             0.002615653, -0.031178445, -0.187124310, -0.405235207, -0.535023851,
271             -0.706801740, -1.466740294, -2.349248503, -2.596576469, -3.354222419,
272             0.086206868, 0.320251370, 3.064778450, 3.426179479, 3.783500164
273         };
274 
275         final double delta = 1e-8;
276         // Note R counts all iterations whereas LoessInterpolator robustness iterations are
277         // in addition to the initial fit.
278         final LoessInterpolator li = new LoessInterpolator(0.35, 0, 1e-12);
279         final double[] res = li.smooth(xval, yval);
280         Assert.assertEquals(xval.length, res.length);
281         for (int i = 0; i < res.length; ++i) {
282             Assert.assertEquals(yref[i], res[i], delta);
283         }
284     }
285 
286     // MATH-1379
287     @Test
288     public void testFitWithVaryingWeightsAndUnevenXSpacing() {
289         final double[] xval = {
290             0.1, 0.12, 0.23, 0.4, 0.57,
291             0.7, 0.87, 1.3, 1.9, 2.2,
292             2.3, 2.65, 3.0, 3.1, 3.5,
293             4.6, 4.7, 5.8, 5.95, 6.1
294         };
295         final double[] yval = {
296             0.47, 0.48, 0.55, 0.56, -0.08,
297             -0.04, -0.07, -0.07, -0.56, -0.46,
298             -0.56, -0.52, -3.03, -3.08, -3.09,
299             -3.04, 3.54, 3.46, 3.36, 3.35};
300         final double[] weights = {
301             1, 1, 1, 1, 1,
302             1, 1, 1, 1, 1,
303             1, 0.8, 0.5, 0.5, 0.8,
304             0.8, 0.5, 0.5, 0.9, 1
305         };
306 
307         // Compare with output from R.
308         // 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")))
309         final double[] yref = {
310             0.556184894, 0.541907126, 0.455059334, 0.303681477, 0.142126445,
311             0.002615653, -0.031178445, -0.187124310, -0.406403569, -0.531957113,
312             -0.669978426, -1.411039850, -2.225022609, -2.463874517, -3.298767758,
313             -0.506116921, -0.231242991, 2.873755984, 3.295897106, 3.715495321};
314 
315         final double delta = 1e-8;
316         // Note R counts all iterations whereas LoessInterpolator robustness iterations are
317         // in addition to the initial fit.
318         final LoessInterpolator li = new LoessInterpolator(0.35, 0, 1e-12);
319         final double[] res = li.smooth(xval, yval, weights);
320         Assert.assertEquals(xval.length, res.length);
321         for (int i = 0; i < res.length; ++i) {
322             Assert.assertEquals(yref[i], res[i], delta);
323         }
324     }
325 
326     private void generateSineData(double[] xval, double[] yval, double xnoise, double ynoise) {
327         double dx = 2 * JdkMath.PI / xval.length;
328         double x = 0;
329         for(int i = 0; i < xval.length; ++i) {
330             xval[i] = x;
331             yval[i] = JdkMath.sin(x) + (2 * JdkMath.random() - 1) * ynoise;
332             x += dx * (1 + (2 * JdkMath.random() - 1) * xnoise);
333         }
334     }
335 }