This is an automated email from the ASF dual-hosted git repository.

aherbert pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-math.git


The following commit(s) were added to refs/heads/master by this push:
     new b3a8489de MATH-1392: Respect no-intercept models in sample size 
validation (#337)
b3a8489de is described below

commit b3a8489de7a5127dcda900ee9cb6d9975c065ef2
Author: hutiefang76 <[email protected]>
AuthorDate: Sat Oct 24 07:15:55 2026 -0700

    MATH-1392: Respect no-intercept models in sample size validation (#337)
    
    Permit square full-rank OLS and GLS designs without an intercept while
    preserving default-intercept and underdetermined-input validation.
    Document that error variance remains unavailable at zero residual DOF.
    
    Generated-by: DeepSeek
    Generated-by: Codex
---
 .../AbstractMultipleLinearRegression.java          | 30 +++++++----
 .../GLSMultipleLinearRegressionTest.java           | 44 ++++++++++++++++
 .../OLSMultipleLinearRegressionTest.java           | 60 ++++++++++++++++++++++
 src/changes/changes.xml                            |  6 +++
 4 files changed, 130 insertions(+), 10 deletions(-)

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 727551e10..b6d832cef 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 abstract class AbstractMultipleLinearRegression 
implements
      * <p>Throws IllegalArgumentException if any of the following 
preconditions fail:
      * <ul><li><code>data</code> cannot be null</li>
      * <li><code>data.length = nobs * (nvars + 1)</code></li>
-     * <li>{@code nobs > nvars}</li></ul>
+     * <li>{@code nobs > 0} and either {@code nobs >= nvars} when {@code 
noIntercept} is
+     * set, or {@code nobs > nvars} otherwise</li></ul>
+     * <p>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.</p>
      *
      * @param data input data array
      * @param nobs number of observations (rows)
@@ -107,8 +111,9 @@ public abstract class AbstractMultipleLinearRegression 
implements
      * @throws NullArgumentException if the data array is null
      * @throws DimensionMismatchException if the length of the data array is 
not equal
      * to <code>nobs * (nvars + 1)</code>
-     * @throws InsufficientDataException if <code>nobs</code> is less than
-     * <code>nvars + 1</code>
+     * @throws InsufficientDataException if <code>nobs</code> is not positive, 
or if it is
+     * less than <code>nvars</code> when {@code noIntercept} is set, or if it 
is not
+     * larger than <code>nvars</code> otherwise
      */
     public void newSampleData(double[] data, int nobs, int nvars) {
         if (data == null) {
@@ -117,8 +122,9 @@ public abstract class AbstractMultipleLinearRegression 
implements
         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 @@ public abstract class AbstractMultipleLinearRegression 
implements
      * Validates sample data.  Checks that
      * <ul><li>Neither x nor y is null or empty;</li>
      * <li>The length (i.e. number of rows) of x equals the length of y</li>
-     * <li>x has at least one more row than it has columns (i.e. there is
-     * sufficient data to estimate regression coefficients for each of the
-     * columns in x plus an intercept.</li>
+     * <li>x has at least as many rows as required design columns, i.e. the
+     * number of columns in x plus an intercept column when {@code noIntercept}
+     * is not set.</li>
      * </ul>
+     * <p>Note that when the number of observations equals the number of design
+     * columns the error variance cannot be estimated.</p>
      *
      * @param x the [n,k] array representing the x data
      * @param y the [n,1] array representing the y data
@@ -217,7 +225,8 @@ public abstract class AbstractMultipleLinearRegression 
implements
      * 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 @@ public abstract class AbstractMultipleLinearRegression 
implements
         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 7878c002f..66e4a46df 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.rng.simple.RandomSource;
 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 class GLSMultipleLinearRegressionTest extends 
MultipleLinearRegressionAbs
         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 3444c8e1e..a44b296d3 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 @@ public class OLSMultipleLinearRegressionTest extends 
MultipleLinearRegressionAbs
         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 b111a20cc..1fd4bb183 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -104,6 +104,12 @@ Caveat:
       <action type="fix" issue="MATH-1678">
         "CanberraDistance": Avoid overflow for large finite coordinates.
       </action>
+      <action type="fix" issue="MATH-1392">
+        Sample size validation in multiple linear regression now counts the
+        design columns that are actually used, so that a model without an
+        intercept is no longer rejected when the number of observations
+        equals the number of independent variables.
+      </action>
       <action dev="aherbert" type="fix" due-to="Lyin-OTTF">
         "SynchronizedDescriptiveStatistics": Synchronize all mutators
         of the internal state. Updates previously unsynchronized methods

Reply via email to