1
0
mirror of https://github.com/apache/commons-math.git synced 2025-03-08 01:20:01 +00:00

fixed wrong results in Loess interpolator

also added a way to set weights for smoothing
JIRA: MATH-296

git-svn-id: https://svn.apache.org/repos/asf/commons/proper/math/trunk@818942 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Luc Maisonobe 2009-09-25 18:33:34 +00:00
parent 97fb25eb35
commit d98e430b04
4 changed files with 143 additions and 18 deletions
src
main/java/org/apache/commons/math
site/xdoc
test/java/org/apache/commons/math/analysis/interpolation

@ -188,6 +188,8 @@ public class MessagesResources_fr
"toutes les abscisses doivent \u00eatre des nombres r\u00e9els finis, mais l''abscisse {0} vaut {1}" },
{ "all ordinatae must be finite real numbers, but {0}-th is {1}",
"toutes les ordonn\u00e9es doivent \u00eatre des nombres r\u00e9els finis, mais l''ordonn\u00e9e {0} vaut {1}" },
{ "all weights must be finite real numbers, but {0}-th is {1}",
"tous les poids doivent \u00eatre des nombres r\u00e9els finis, mais le poids {0} vaut {1}" },
{ "the abscissae array must be sorted in a strictly increasing order, " +
"but the {0}-th element is {1} whereas {2}-th is {3}",
"les abscisses doivent \u00eatre en ordre strictement croissant, " +

@ -47,6 +47,9 @@ public class LoessInterpolator
/** Default value of the number of robustness iterations. */
public static final int DEFAULT_ROBUSTNESS_ITERS = 2;
/** Default value for accuracy. */
public static final double DEFAULT_ACCURACY = 1e-12;
/** serializable version identifier. */
private static final long serialVersionUID = 5204927143605193821L;
@ -69,21 +72,34 @@ public class LoessInterpolator
*/
private final int robustnessIters;
/**
* If the median residual at a certain robustness iteration
* is less than this amount, no more iterations are done.
*/
private final double accuracy;
/**
* Constructs a new {@link LoessInterpolator}
* with a bandwidth of {@link #DEFAULT_BANDWIDTH} and
* {@link #DEFAULT_ROBUSTNESS_ITERS} robustness iterations.
* See {@link #LoessInterpolator(double, int)} for an explanation of
* with a bandwidth of {@link #DEFAULT_BANDWIDTH},
* {@link #DEFAULT_ROBUSTNESS_ITERS} robustness iterations
* and an accuracy of {#link #DEFAULT_ACCURACY}.
* See {@link #LoessInterpolator(double, int, double)} for an explanation of
* the parameters.
*/
public LoessInterpolator() {
this.bandwidth = DEFAULT_BANDWIDTH;
this.robustnessIters = DEFAULT_ROBUSTNESS_ITERS;
this.accuracy = DEFAULT_ACCURACY;
}
/**
* Constructs a new {@link LoessInterpolator}
* with given bandwidth and number of robustness iterations.
* <p>
* Calling this constructor is equivalent to calling {link {@link
* #LoessInterpolator(double, int, double) LoessInterpolator(bandwidth,
* robustnessIters, LoessInterpolator.DEFAULT_ACCURACY)}
* </p>
*
* @param bandwidth when computing the loess fit at
* a particular point, this fraction of source points closest
@ -97,8 +113,34 @@ public class LoessInterpolator
* {@link #DEFAULT_ROBUSTNESS_ITERS}.
* @throws MathException if bandwidth does not lie in the interval [0,1]
* or if robustnessIters is negative.
* @see #LoessInterpolator(double, int, double)
*/
public LoessInterpolator(double bandwidth, int robustnessIters) throws MathException {
this(bandwidth, robustnessIters, DEFAULT_ACCURACY);
}
/**
* Constructs a new {@link LoessInterpolator}
* with given bandwidth, number of robustness iterations and accuracy.
*
* @param bandwidth when computing the loess fit at
* a particular point, this fraction of source points closest
* to the current point is taken into account for computing
* a least-squares regression.</br>
* A sensible value is usually 0.25 to 0.5, the default value is
* {@link #DEFAULT_BANDWIDTH}.
* @param robustnessIters This many robustness iterations are done.</br>
* A sensible value is usually 0 (just the initial fit without any
* robustness iterations) to 4, the default value is
* {@link #DEFAULT_ROBUSTNESS_ITERS}.
* @param accuracy If the median residual at a certain robustness iteration
* is less than this amount, no more iterations are done.
* @throws MathException if bandwidth does not lie in the interval [0,1]
* or if robustnessIters is negative.
* @see #LoessInterpolator(double, int)
* @since 2.1
*/
public LoessInterpolator(double bandwidth, int robustnessIters, double accuracy) throws MathException {
if (bandwidth < 0 || bandwidth > 1) {
throw new MathException("bandwidth must be in the interval [0,1], but got {0}",
bandwidth);
@ -110,6 +152,7 @@ public class LoessInterpolator
robustnessIters);
}
this.robustnessIters = robustnessIters;
this.accuracy = accuracy;
}
/**
@ -135,10 +178,11 @@ public class LoessInterpolator
}
/**
* Compute a loess fit on the data at the original abscissae.
* Compute a weighted loess fit on the data at the original abscissae.
*
* @param xval the arguments for the interpolation points
* @param yval the values for the interpolation points
* @param weights point weights: coefficients by which the robustness weight of a point is multiplied
* @return values of the loess fit at corresponding original abscissae
* @throws MathException if some of the following conditions are false:
* <ul>
@ -146,8 +190,9 @@ public class LoessInterpolator
* <li> The arguments are in a strictly increasing order</li>
* <li> All arguments and values are finite real numbers</li>
* </ul>
* @since 2.1
*/
public final double[] smooth(final double[] xval, final double[] yval)
public final double[] smooth(final double[] xval, final double[] yval, final double[] weights)
throws MathException {
if (xval.length != yval.length) {
throw new MathException(
@ -163,8 +208,9 @@ public class LoessInterpolator
throw new MathException("Loess expects at least 1 point");
}
checkAllFiniteReal(xval, true);
checkAllFiniteReal(yval, false);
checkAllFiniteReal(xval, "all abscissae must be finite real numbers, but {0}-th is {1}");
checkAllFiniteReal(yval, "all ordinatae must be finite real numbers, but {0}-th is {1}");
checkAllFiniteReal(weights, "all weights must be finite real numbers, but {0}-th is {1}");
checkStrictlyIncreasing(xval);
@ -237,7 +283,7 @@ public class LoessInterpolator
final double xk = xval[k];
final double yk = yval[k];
final double dist = (k < i) ? x - xk : xk - x;
final double w = tricube(dist * denom) * robustnessWeights[k];
final double w = tricube(dist * denom) * robustnessWeights[k] * weights[k];
final double xkw = xk * w;
sumWeights += w;
sumX += xkw;
@ -252,7 +298,7 @@ public class LoessInterpolator
final double meanXSquared = sumXSquared / sumWeights;
final double beta;
if (meanXSquared == meanX * meanX) {
if (Math.sqrt(Math.abs(meanXSquared - meanX * meanX)) < accuracy) {
beta = 0;
} else {
beta = (meanXY - meanX * meanY) / (meanXSquared - meanX * meanX);
@ -279,19 +325,48 @@ public class LoessInterpolator
Arrays.sort(sortedResiduals);
final double medianResidual = sortedResiduals[n / 2];
if (medianResidual == 0) {
if (Math.abs(medianResidual) < accuracy) {
break;
}
for (int i = 0; i < n; ++i) {
final double arg = residuals[i] / (6 * medianResidual);
robustnessWeights[i] = (arg >= 1) ? 0 : Math.pow(1 - arg * arg, 2);
if (arg >= 1) {
robustnessWeights[i] = 0;
} else {
final double w = 1 - arg * arg;
robustnessWeights[i] = w * w;
}
}
}
return res;
}
/**
* Compute a loess fit on the data at the original abscissae.
*
* @param xval the arguments for the interpolation points
* @param yval the values for the interpolation points
* @return values of the loess fit at corresponding original abscissae
* @throws MathException if some of the following conditions are false:
* <ul>
* <li> Arguments and values are of the same size that is greater than zero</li>
* <li> The arguments are in a strictly increasing order</li>
* <li> All arguments and values are finite real numbers</li>
* </ul>
*/
public final double[] smooth(final double[] xval, final double[] yval)
throws MathException {
final double[] unitWeights = new double[xval.length];
Arrays.fill(unitWeights, 1.0);
return smooth(xval, yval, unitWeights);
}
/**
* Given an index interval into xval that embraces a certain number of
* points closest to xval[i-1], update the interval so that it embraces
@ -336,18 +411,14 @@ public class LoessInterpolator
* Check that all elements of an array are finite real numbers.
*
* @param values the values array
* @param isAbscissae if true, elements are abscissae otherwise they are ordinatae
* @throws MathException if one of the values is not
* a finite real number
* @param pattern pattern of the error message
* @throws MathException if one of the values is not a finite real number
*/
private static void checkAllFiniteReal(final double[] values, final boolean isAbscissae)
private static void checkAllFiniteReal(final double[] values, final String pattern)
throws MathException {
for (int i = 0; i < values.length; i++) {
final double x = values[i];
if (Double.isInfinite(x) || Double.isNaN(x)) {
final String pattern = isAbscissae ?
"all abscissae must be finite real numbers, but {0}-th is {1}" :
"all ordinatae must be finite real numbers, but {0}-th is {1}";
throw new MathException(pattern, i, x);
}
}

@ -44,6 +44,10 @@ The <action> type attribute can be add,update,fix,remove.
interface contract. Added getGeneratorUpperBounds method to
EmpiricalDistributionImpl providing previous behavior.
</action>
<action dev="luc" tyoe="fix" issue="MATH-296" due-to="Eugene Kirpichov">
Fixed wrong results on Loess interpolation, also added a way to set weights
for smoothing
</action>
<action dev="luc" type="fix" issue="MATH-293" due-to="Benjamin McCann">
Fixed a OutOfBoundException in simplex solver when some constraints are tight.
</action>

@ -219,6 +219,54 @@ public class LoessInterpolatorTest {
new LoessInterpolator(1.1, 3);
}
@Test
public void testMath296withoutWeights() throws MathException {
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);
}
}
@Test
public void testMath296withWeights() throws MathException {
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};
double[] weights = {
1,1,1,1,1,1,1,1,1,1,
1,1,0,0,1,1,0,0,1,1};
// Output from R, rounded to .001
double[] yref = {
0.478, 0.492, 0.484, 0.320, 0.179, -0.003, -0.088, -0.209,
-0.327, -0.455, -0.518, -0.537, -1.492, -2.115, -3.09, -3.04,
-3.0, 0.155, 1.752, 3.35};
LoessInterpolator li = new LoessInterpolator(0.3, 4, 1e-12);
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], 0.05);
}
}
private void generateSineData(double[] xval, double[] yval, double xnoise, double ynoise) {
double dx = 2 * Math.PI / xval.length;
double x = 0;