LoessSmoother { public double[] smooth() { if (fData.length == 1) { fSmoothed[0] = fData[0]; return fSmoothed; } int left = -1, right = -1; if (fWidth >= fData.length) { left = 0; right = fData.length - 1; for (int i = 0; i < fData.length; i += fJump) { final Double y = fInterpolator.smoothOnePoint(i, left, right); fSmoothed[i] = y == null ? fData[i] : y; } } else if (fJump == 1) { final int halfWidth = (fWidth + 1) / 2; left = 0; right = fWidth - 1; for (int i = 0; i < fData.length; ++i) { if (i >= halfWidth && right != fData.length - 1) { ++left; ++right; } final Double y = fInterpolator.smoothOnePoint(i, left, right); fSmoothed[i] = y == null ? fData[i] : y; } } else { final int halfWidth = (fWidth + 1) / 2; for (int i = 0; i < fData.length; i += fJump) { if (i < halfWidth - 1) { left = 0; } else if (i >= fData.length - halfWidth) { left = fData.length - fWidth; } else { left = i - halfWidth + 1; } right = left + fWidth - 1; final Double y = fInterpolator.smoothOnePoint(i, left, right); fSmoothed[i] = y == null ? fData[i] : y; } } if (fJump != 1) { for (int i = 0; i < fData.length - fJump; i += fJump) { final double slope = (fSmoothed[i + fJump] - fSmoothed[i]) / (double) fJump; for (int j = i + 1; j < i + fJump; ++j) { fSmoothed[j] = fSmoothed[i] + slope * (j - i); } } final int last = fData.length - 1; int lastSmoothedPos = (last / fJump) * fJump; if (lastSmoothedPos != last) { final Double y = fInterpolator.smoothOnePoint(last, left, right); fSmoothed[last] = y == null ? fData[last] : y; if (lastSmoothedPos != last - 1) { final double slope = (fSmoothed[last] - fSmoothed[lastSmoothedPos]) / (last - lastSmoothedPos); for (int j = lastSmoothedPos + 1; j < last; ++j) { fSmoothed[j] = fSmoothed[lastSmoothedPos] + slope * (j - lastSmoothedPos); } } } } return fSmoothed; } private  LoessSmoother(int width, int jump, int degree, double[] data, double[] externalWeights); LoessInterpolator getInterpolator(); double[] smooth();  }
@Test public void smoothingWithLargeWidthGivesLinearRegressionFit() { final double[] scatter100 = { 45.0641826945, 69.6998783993, 9.81903951235, -75.4079441854, 53.7430205615, 12.1359388898, 84.972441255, 194.467452805, 182.276035711, 128.161856616, 147.021732433, -40.6773185264, 41.1575417261, 111.04115761, 75.0179056538, 278.946359666, 93.3453251262, 103.779785975, 252.750915429, 252.636103208, 457.859165335, 143.021758047, 79.343240193, 280.969547174, 35.650257308, 157.656673765, 29.6984404613, 141.980264706, 263.465758806, 346.309482972, 330.044915761, 135.019120067, 211.801092316, 198.186646037, 206.088498967, 510.89412974, 332.076915483, 530.524264511, 298.21175481, 234.317252809, 573.836352739, 382.708235416, 340.090947574, 452.475239395, 576.134135134, 536.703405146, 545.033194307, 479.525083559, 368.551750848, 588.429801268, 528.672000843, 507.301073925, 432.749370682, 600.239380863, 567.328853536, 481.544306962, 510.42118889, 456.519971302, 565.839651322, 510.505759788, 503.2514057, 491.279917041, 642.319449309, 573.019058995, 574.709858012, 597.316826688, 602.361341448, 622.312708681, 506.669245531, 640.120714982, 699.793133288, 672.969830555, 656.645808774, 901.375994679, 573.903581507, 906.472771298, 719.604429516, 759.262994619, 786.970584025, 717.422383977, 899.007418786, 745.516032607, 748.049043698, 876.99080793, 810.985707949, 888.762045358, 947.030030816, 1007.48402395, 830.251382179, 921.078927761, 810.212273661, 926.740829016, 787.965498372, 944.230542154, 808.215987256, 1044.74526488, 866.568085766, 1068.6479395, 776.566771785, 1190.32090194 }; final double testSlope = 9.9564197212156671; final double testIntercept = -12.894457726954045; final LoessSmoother.Builder builder = new LoessSmoother.Builder().setWidth(1000000).setData(scatter100); for (int j = 1; j < scatter100.length - 1; ++j) { LoessSmoother loess = builder.setJump(j).build(); double[] smoothedPoints = loess.smooth(); for (int i = 0; i < smoothedPoints.length; ++i) { double y = testSlope * i + testIntercept; assertEquals("Smoothed points lie on regression line", y, smoothedPoints[i], 1.0e-8); } } }
@Test public void smoothedNoisySinusoid() { int width = noisySinusoid.length / 3; LoessSmoother loess = new LoessSmoother.Builder().setWidth(width).setData(noisySinusoid).build(); double[] y = loess.smooth(); for (int i = 0; i < y.length; ++i) assertEquals("Smoothed points match test values", smoothedNoisySinusoid[i], y[i], 1.0e-8); }
@Test public void smoothedNoisySinusoidQuadratic() { int width = noisySinusoid.length / 3; LoessSmoother loess = new LoessSmoother.Builder().setWidth(width).setDegree(2).setData(noisySinusoid).build(); double[] y = loess.smooth(); for (int i = 0; i < y.length; ++i) assertEquals("Smoothed points match test values", smoothedNoisySinusoidQuadratic[i], y[i], 1.0e-8); }
@Test public void smoothedNoisySinusoidWithShortJump() { int width = noisySinusoid.length / 3; LoessSmoother loess = new LoessSmoother.Builder().setWidth(width).setJump(2).setData(noisySinusoid).build(); double[] y = loess.smooth(); for (int i = 0; i < y.length; ++i) assertEquals("Smoothed points match test values", smoothedNoisySinusoid[i], y[i], 5.0e-3); }
@Test public void smoothingSinglePointReturnsThatPoint() { double[] data = { Math.PI }; LoessSmoother loess = new LoessSmoother.Builder().setWidth(3).setData(data).build(); double[] smoothed = loess.smooth(); assertEquals(1, smoothed.length); assertEquals(Math.PI, smoothed[0], Math.ulp(Math.PI)); }