Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -99,16 +99,21 @@ public void setNoIntercept(boolean noIntercept) {
* <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)
* @param nvars number of independent variables (columns, not counting y)
* @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) {
Expand All @@ -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;
Expand Down Expand Up @@ -205,10 +211,12 @@ protected void newXSampleData(double[][] x) {
* 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
Expand All @@ -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) {
Expand All @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down
6 changes: 6 additions & 0 deletions src/changes/changes.xml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading