diff --git a/commons-math-legacy/src/main/java/org/apache/commons/math4/legacy/stat/regression/AbstractMultipleLinearRegression.java b/commons-math-legacy/src/main/java/org/apache/commons/math4/legacy/stat/regression/AbstractMultipleLinearRegression.java index 727551e10c..b6d832cef5 100644 --- a/commons-math-legacy/src/main/java/org/apache/commons/math4/legacy/stat/regression/AbstractMultipleLinearRegression.java +++ b/commons-math-legacy/src/main/java/org/apache/commons/math4/legacy/stat/regression/AbstractMultipleLinearRegression.java @@ -99,7 +99,11 @@ public void setNoIntercept(boolean noIntercept) { *
Throws IllegalArgumentException if any of the following preconditions fail: *
data cannot be nulldata.length = nobs * (nvars + 1)Note that when the number of observations equals the number of design columns + * ({@code nobs == nvars} with {@code noIntercept} set) the error variance cannot be + * estimated.
* * @param data input data array * @param nobs number of observations (rows) @@ -107,8 +111,9 @@ public void setNoIntercept(boolean noIntercept) { * @throws NullArgumentException if the data array is null * @throws DimensionMismatchException if the length of the data array is not equal * tonobs * (nvars + 1)
- * @throws InsufficientDataException if nobs is less than
- * nvars + 1
+ * @throws InsufficientDataException if nobs is not positive, or if it is
+ * less than nvars when {@code noIntercept} is set, or if it is not
+ * larger than nvars otherwise
*/
public void newSampleData(double[] data, int nobs, int nvars) {
if (data == null) {
@@ -117,8 +122,9 @@ public void newSampleData(double[] data, int nobs, int nvars) {
if (data.length != nobs * (nvars + 1)) {
throw new DimensionMismatchException(data.length, nobs * (nvars + 1));
}
- if (nobs <= nvars) {
- throw new InsufficientDataException(LocalizedFormats.INSUFFICIENT_OBSERVED_POINTS_IN_SAMPLE, nobs, nvars + 1);
+ if (noIntercept ? (nobs < nvars || nobs == 0) : nobs <= nvars) {
+ throw new InsufficientDataException(LocalizedFormats.INSUFFICIENT_OBSERVED_POINTS_IN_SAMPLE, nobs,
+ noIntercept ? nvars : nvars + 1);
}
double[] y = new double[nobs];
final int cols = noIntercept ? nvars: nvars + 1;
@@ -205,10 +211,12 @@ protected void newXSampleData(double[][] x) {
* Validates sample data. Checks that
* Note that when the number of observations equals the number of design + * columns the error variance cannot be estimated.
* * @param x the [n,k] array representing the x data * @param y the [n,1] array representing the y data @@ -217,7 +225,8 @@ protected void newXSampleData(double[][] x) { * have the same length * @throws NoDataException if {@code x} or {@code y} are zero-length * @throws MathIllegalArgumentException if the number of rows of {@code x} - * is not larger than the number of columns + 1 + * is less than the number of design columns (an equal number of rows is + * accepted) */ protected void validateSampleData(double[][] x, double[] y) throws MathIllegalArgumentException { if (x == null || y == null) { @@ -229,7 +238,8 @@ protected void validateSampleData(double[][] x, double[] y) throws MathIllegalAr if (x.length == 0) { // Must be no y data either throw new NoDataException(); } - if (x[0].length + 1 > x.length) { + final int requiredColumns = x[0].length + (noIntercept ? 0 : 1); + if (requiredColumns > x.length) { throw new MathIllegalArgumentException( LocalizedFormats.NOT_ENOUGH_DATA_FOR_NUMBER_OF_PREDICTORS, x.length, x[0].length); diff --git a/commons-math-legacy/src/test/java/org/apache/commons/math4/legacy/stat/regression/GLSMultipleLinearRegressionTest.java b/commons-math-legacy/src/test/java/org/apache/commons/math4/legacy/stat/regression/GLSMultipleLinearRegressionTest.java index 7878c002fb..66e4a46dff 100644 --- a/commons-math-legacy/src/test/java/org/apache/commons/math4/legacy/stat/regression/GLSMultipleLinearRegressionTest.java +++ b/commons-math-legacy/src/test/java/org/apache/commons/math4/legacy/stat/regression/GLSMultipleLinearRegressionTest.java @@ -27,11 +27,13 @@ import org.apache.commons.statistics.distribution.ContinuousDistribution; import org.apache.commons.statistics.distribution.NormalDistribution; import org.apache.commons.math4.legacy.TestUtils; +import org.apache.commons.math4.legacy.exception.DimensionMismatchException; import org.apache.commons.math4.legacy.exception.MathIllegalArgumentException; import org.apache.commons.math4.legacy.exception.NullArgumentException; import org.apache.commons.math4.legacy.linear.MatrixUtils; import org.apache.commons.math4.legacy.linear.RealMatrix; import org.apache.commons.math4.legacy.linear.RealVector; +import org.apache.commons.math4.legacy.linear.SingularMatrixException; import org.apache.commons.math4.legacy.random.CorrelatedVectorFactory; import org.apache.commons.math4.legacy.stat.correlation.Covariance; import org.apache.commons.math4.legacy.stat.descriptive.DescriptiveStatistics; @@ -91,6 +93,48 @@ public void cannotAddNullYSampleData() { createRegression().newSampleData(null, new double[][]{}, null); } + @Test + public void testGlsNoInterceptMatrixExactFit() { + final GLSMultipleLinearRegression regression = new GLSMultipleLinearRegression(); + regression.setNoIntercept(true); + final double[][] x = {{1d, 2d}, {3d, 4d}}; + final double[] y = {5d, 11d}; + final double[][] covariance = {{2d, 0d}, {0d, 4d}}; + regression.newSampleData(y, x, covariance); + final double[] params = regression.estimateRegressionParameters(); + Assert.assertArrayEquals(new double[] {1d, 2d}, params, 1e-10); + } + + @Test(expected=MathIllegalArgumentException.class) + public void testGlsNoInterceptUnderdeterminedSampleDataRejected() { + final GLSMultipleLinearRegression regression = new GLSMultipleLinearRegression(); + regression.setNoIntercept(true); + final double[][] x = {{1d, 2d, 3d}, {4d, 5d, 6d}}; + final double[] y = {5d, 11d}; + final double[][] covariance = {{2d, 0d}, {0d, 4d}}; + regression.newSampleData(y, x, covariance); + } + + @Test(expected=DimensionMismatchException.class) + public void testGlsCovarianceRowCountMismatchRejected() { + final GLSMultipleLinearRegression regression = new GLSMultipleLinearRegression(); + regression.setNoIntercept(true); + final double[][] x = {{1d, 2d}, {3d, 4d}, {5d, 7d}}; + final double[] y = {5d, 11d, 17d}; + final double[][] covariance = {{2d, 0d}, {0d, 4d}}; + regression.newSampleData(y, x, covariance); + } + + @Test(expected=SingularMatrixException.class) + public void testGlsSingularCovarianceEstimateRejected() { + final GLSMultipleLinearRegression regression = new GLSMultipleLinearRegression(); + final double[][] x = {{1d}, {2d}, {3d}}; + final double[] y = {5d, 11d, 17d}; + final double[][] covariance = {{1d, 1d, 0d}, {1d, 1d, 0d}, {0d, 0d, 1d}}; + regression.newSampleData(y, x, covariance); + regression.estimateRegressionParameters(); + } + @Test(expected=MathIllegalArgumentException.class) public void cannotAddSampleDataWithSizeMismatch() { double[] y = new double[]{1.0, 2.0}; diff --git a/commons-math-legacy/src/test/java/org/apache/commons/math4/legacy/stat/regression/OLSMultipleLinearRegressionTest.java b/commons-math-legacy/src/test/java/org/apache/commons/math4/legacy/stat/regression/OLSMultipleLinearRegressionTest.java index 3444c8e1e1..a44b296d38 100644 --- a/commons-math-legacy/src/test/java/org/apache/commons/math4/legacy/stat/regression/OLSMultipleLinearRegressionTest.java +++ b/commons-math-legacy/src/test/java/org/apache/commons/math4/legacy/stat/regression/OLSMultipleLinearRegressionTest.java @@ -66,6 +66,66 @@ protected int getSampleSize() { return y.length; } + @Test(expected = org.apache.commons.math4.legacy.exception.InsufficientDataException.class) + public void testNewSampleDataFlatWithInterceptInsufficientObservations() { + // nobs = 2 and nvars = 2 need 3 design columns with the default intercept. + final double[] data = { 1, 2, 3, 4, 5, 6 }; + new OLSMultipleLinearRegression().newSampleData(data, 2, 2); + } + + @Test(expected = org.apache.commons.math4.legacy.exception.InsufficientDataException.class) + public void testNewSampleDataFlatNoInterceptEmptyData() { + final OLSMultipleLinearRegression regression = new OLSMultipleLinearRegression(); + regression.setNoIntercept(true); + regression.newSampleData(new double[0], 0, 0); + } + + @Test + public void testOlsNoInterceptMatrixExactFit() { + final OLSMultipleLinearRegression regression = new OLSMultipleLinearRegression(); + regression.setNoIntercept(true); + final double[][] x = {{1d, 2d}, {3d, 4d}}; + final double[] y = {5d, 11d}; + regression.newSampleData(y, x); + final double[] params = regression.estimateRegressionParameters(); + Assert.assertArrayEquals(new double[] {1d, 2d}, params, 1e-12); + } + + @Test + public void testOlsNoInterceptFlatExactFit() { + final OLSMultipleLinearRegression regression = new OLSMultipleLinearRegression(); + regression.setNoIntercept(true); + final double[] data = {5d, 1d, 2d, 11d, 3d, 4d}; + regression.newSampleData(data, 2, 2); + final double[] params = regression.estimateRegressionParameters(); + Assert.assertArrayEquals(new double[] {1d, 2d}, params, 1e-12); + } + + @Test(expected=MathIllegalArgumentException.class) + public void testOlsNoInterceptMatrixUnderdeterminedSampleDataRejected() { + final OLSMultipleLinearRegression regression = new OLSMultipleLinearRegression(); + regression.setNoIntercept(true); + final double[][] x = {{1d, 2d, 3d}, {4d, 5d, 6d}}; + final double[] y = {5d, 11d}; + regression.newSampleData(y, x); + } + + @Test(expected=MathIllegalArgumentException.class) + public void testOlsNoInterceptFlatUnderdeterminedSampleDataRejected() { + final OLSMultipleLinearRegression regression = new OLSMultipleLinearRegression(); + regression.setNoIntercept(true); + final double[] data = {5d, 1d, 2d, 3d, 11d, 4d, 5d, 6d}; + regression.newSampleData(data, 2, 3); + } + + @Test(expected=MathIllegalArgumentException.class) + public void testOlsDefaultInterceptSquareSampleDataRejected() { + final OLSMultipleLinearRegression regression = new OLSMultipleLinearRegression(); + final double[][] x = {{1d, 2d}, {3d, 4d}}; + final double[] y = {5d, 11d}; + regression.newSampleData(y, x); + } + @Test(expected=MathIllegalArgumentException.class) public void cannotAddSampleDataWithSizeMismatch() { double[] y = new double[]{1.0, 2.0}; diff --git a/src/changes/changes.xml b/src/changes/changes.xml index b111a20cc7..1fd4bb1833 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -104,6 +104,12 @@ Caveat: