This is an automated email from the ASF dual-hosted git repository.
asf-gitbox-commits pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-statistics.git
The following commit(s) were added to refs/heads/master by this push:
new d85d307c STATISTICS-97: Check for arguments that invalidate the test
statistic
d85d307c is described below
commit d85d307c8568e32c9727ecc250dc60ca60f36fb9
Author: Alex Herbert <[email protected]>
AuthorDate: Thu Aug 27 13:16:53 2026 +0100
STATISTICS-97: Check for arguments that invalidate the test statistic
---
.../commons/statistics/inference/Arguments.java | 32 ++++++++++++++++------
.../statistics/inference/InferenceException.java | 4 +++
.../commons/statistics/inference/OneWayAnova.java | 16 +++++++----
.../apache/commons/statistics/inference/TTest.java | 28 +++++++++++++------
.../statistics/inference/ArgumentsTest.java | 22 ++++++++++-----
.../statistics/inference/OneWayAnovaTest.java | 6 ++++
.../commons/statistics/inference/TTestTest.java | 32 ++++++++++++++++++++++
src/changes/changes.xml | 4 +++
8 files changed, 114 insertions(+), 30 deletions(-)
diff --git
a/commons-statistics-inference/src/main/java/org/apache/commons/statistics/inference/Arguments.java
b/commons-statistics-inference/src/main/java/org/apache/commons/statistics/inference/Arguments.java
index e7a38441..b15ddb4f 100644
---
a/commons-statistics-inference/src/main/java/org/apache/commons/statistics/inference/Arguments.java
+++
b/commons-statistics-inference/src/main/java/org/apache/commons/statistics/inference/Arguments.java
@@ -63,7 +63,7 @@ final class Arguments {
* Check that the value is {@code >= 0}.
*
* @param v Value to be tested.
- * @throws IllegalArgumentException if the value is less than 0.
+ * @throws IllegalArgumentException if the value is less than 0, or is NaN.
*/
static void checkNonNegative(double v) {
if (v >= 0) {
@@ -118,7 +118,7 @@ final class Arguments {
*
* @param v Value to be tested.
* @return the value
- * @throws IllegalArgumentException if the value is not strictly positive.
+ * @throws IllegalArgumentException if the value is not strictly positive
finite.
*/
static double checkStrictlyPositive(double v) {
if (v > 0) {
@@ -132,7 +132,7 @@ final class Arguments {
* Check that all values are {@code > 0}.
*
* @param values Values to be tested.
- * @throws IllegalArgumentException if any values are not strictly
positive.
+ * @throws IllegalArgumentException if any values are not strictly
positive finite.
*/
static void checkStrictlyPositive(double[] values) {
for (final double v : values) {
@@ -152,25 +152,39 @@ final class Arguments {
*/
static double checkFinite(double v) {
if (!Double.isFinite(v)) {
- throw new InferenceException("Non-finite input value: " + v);
+ throw new InferenceException(InferenceException.NOT_FINITE, v);
}
return v;
}
/**
- * Check that all values are not {@link Double#NaN}.
+ * Check that all the values are finite.
*
* @param values Values to be tested.
- * @throws IllegalArgumentException if any values are NaN.
+ * @throws IllegalArgumentException if any values are not finite.
*/
- static void checkNonNaN(double[] values) {
+ static void checkFinite(double[] values) {
for (final double v : values) {
- if (Double.isNaN(v)) {
- throw new InferenceException("NaN input value");
+ if (!Double.isFinite(v)) {
+ throw new InferenceException(InferenceException.NOT_FINITE, v);
}
}
}
+ /**
+ * Check that the value is not {@link Double#NaN}.
+ *
+ * @param v Value to be tested.
+ * @return the value
+ * @throws IllegalArgumentException if the value is NaN.
+ */
+ static double checkNonNaN(double v) {
+ if (Double.isNaN(v)) {
+ throw new InferenceException(InferenceException.NAN);
+ }
+ return v;
+ }
+
/**
* Checks if the input array is rectangular. It is assumed the array is
non-null
* and has a non-zero length.
diff --git
a/commons-statistics-inference/src/main/java/org/apache/commons/statistics/inference/InferenceException.java
b/commons-statistics-inference/src/main/java/org/apache/commons/statistics/inference/InferenceException.java
index c3dfd921..fb32f35e 100644
---
a/commons-statistics-inference/src/main/java/org/apache/commons/statistics/inference/InferenceException.java
+++
b/commons-statistics-inference/src/main/java/org/apache/commons/statistics/inference/InferenceException.java
@@ -48,6 +48,8 @@ class InferenceException extends IllegalArgumentException {
static final String INVALID_SIGNIFICANCE = "Not a significance: %s is out
of range (0, 0.5]";
/** Error message for "not strictly positive" condition when "{@code x <=
0}". */
static final String NOT_STRICTLY_POSITIVE = "Number %s is not greater than
0";
+ /** Error message for "not finite" condition when "{@code x}" is not
finite. */
+ static final String NOT_FINITE = "Number %s is not finite";
/** Error message for "no data" condition. */
static final String NO_DATA = "No data";
/** Error message for "too large" condition when "{@code x > y}". */
@@ -56,6 +58,8 @@ class InferenceException extends IllegalArgumentException {
static final String X_GTE_Y = "%s >= %s";
/** Error message for "too small" condition when "{@code x < y}". */
static final String X_LT_Y = "%s < %s";
+ /** Error message for a NaN value. */
+ static final String NAN = "NaN input value";
/** Serializable version identifier. */
private static final long serialVersionUID = 20221203L;
diff --git
a/commons-statistics-inference/src/main/java/org/apache/commons/statistics/inference/OneWayAnova.java
b/commons-statistics-inference/src/main/java/org/apache/commons/statistics/inference/OneWayAnova.java
index 6a9610e3..f3292733 100644
---
a/commons-statistics-inference/src/main/java/org/apache/commons/statistics/inference/OneWayAnova.java
+++
b/commons-statistics-inference/src/main/java/org/apache/commons/statistics/inference/OneWayAnova.java
@@ -208,8 +208,9 @@ public final class OneWayAnova {
* @param data Category summary data.
* @return F statistic
* @throws IllegalArgumentException if the number of categories is less
than
- * two; a contained category does not have at least one value; or all
- * categories have only one value (zero degrees of freedom within groups)
+ * two; a contained category does not have at least one value; all
+ * categories have only one value (zero degrees of freedom within groups);
+ * or any category contains non-finite values
*/
public double statistic(Collection<double[]> data) {
final double[] f = new double[1];
@@ -233,8 +234,9 @@ public final class OneWayAnova {
* @param data Category summary data.
* @return test result
* @throws IllegalArgumentException if the number of categories is less
than
- * two; a contained category does not have at least one value; or all
- * categories have only one value (zero degrees of freedom within groups)
+ * two; a contained category does not have at least one value; all
+ * categories have only one value (zero degrees of freedom within groups);
+ * or any category contains non-finite values
*/
public Result test(Collection<double[]> data) {
return aov(data, null);
@@ -252,8 +254,9 @@ public final class OneWayAnova {
* @param statistic Result for the F statistic (or null).
* @return test result (or null)
* @throws IllegalArgumentException if the number of categories is less
than two; a
- * contained category does not have at least one value; or all categories
have only
- * one value (zero degrees of freedom within groups)
+ * contained category does not have at least one value; all categories
have only
+ * one value (zero degrees of freedom within groups); or any category
contains
+ * non-finite values
*/
private static Result aov(Collection<double[]> data, double[] statistic) {
Arguments.checkCategoriesRequiredSize(data.size(), 2);
@@ -261,6 +264,7 @@ public final class OneWayAnova {
for (final double[] array : data) {
n += array.length;
Arguments.checkValuesRequiredSize(array.length, 1);
+ Arguments.checkFinite(array);
}
final long dfwg = n - data.size();
if (dfwg == 0) {
diff --git
a/commons-statistics-inference/src/main/java/org/apache/commons/statistics/inference/TTest.java
b/commons-statistics-inference/src/main/java/org/apache/commons/statistics/inference/TTest.java
index 285bbe78..d16e3823 100644
---
a/commons-statistics-inference/src/main/java/org/apache/commons/statistics/inference/TTest.java
+++
b/commons-statistics-inference/src/main/java/org/apache/commons/statistics/inference/TTest.java
@@ -160,11 +160,12 @@ public final class TTest {
* @param v Sample variance.
* @param n Sample size.
* @return t statistic
- * @throws IllegalArgumentException if the number of samples is {@code <
2}; or the
- * variance is negative
+ * @throws IllegalArgumentException if the number of samples is {@code <
2}; the
+ * variance is negative or NaN; or the mean is NaN
* @see #withMu(double)
*/
public double statistic(double m, double v, long n) {
+ Arguments.checkNonNaN(m);
Arguments.checkNonNegative(v);
checkSampleSize(n);
return computeT(m - mu, v, n);
@@ -175,12 +176,14 @@ public final class TTest {
*
* @param x Sample values.
* @return t statistic
- * @throws IllegalArgumentException if the number of samples is {@code < 2}
+ * @throws IllegalArgumentException if the number of samples is {@code <
2}; or the
+ * sample contains non-finite values
* @see #statistic(double, double, long)
* @see #withMu(double)
*/
public double statistic(double[] x) {
final long n = checkSampleSize(x.length);
+ Arguments.checkFinite(x);
final DoubleStatistics s = DoubleStatistics.of(
EnumSet.of(Statistic.MEAN, Statistic.VARIANCE), x);
final double m = s.getAsDouble(Statistic.MEAN);
@@ -200,12 +203,14 @@ public final class TTest {
* @param x First sample values.
* @param y Second sample values.
* @return t statistic
- * @throws IllegalArgumentException if the number of samples is {@code <
2}; or the
- * the size of the samples is not equal
+ * @throws IllegalArgumentException if the number of samples is {@code <
2}; the
+ * size of the samples is not equal; or the samples contain non-finite
values
* @see #withMu(double)
*/
public double pairedStatistic(double[] x, double[] y) {
final long n = checkSampleSize(x.length);
+ Arguments.checkFinite(x);
+ Arguments.checkFinite(y);
final double m = StatisticUtils.meanDifference(x, y);
final double v = StatisticUtils.varianceDifference(x, y, m);
return computeT(m - mu, v, n);
@@ -237,12 +242,14 @@ public final class TTest {
* @param n2 Second sample size.
* @return t statistic
* @throws IllegalArgumentException if the number of samples in either
dataset is
- * {@code < 2}; or the variances are negative.
+ * {@code < 2}; the variances are negative; or the means are NaN.
* @see #withMu(double)
* @see #with(DataDispersion)
*/
public double statistic(double m1, double v1, long n1,
double m2, double v2, long n2) {
+ Arguments.checkNonNaN(m1);
+ Arguments.checkNonNaN(m2);
Arguments.checkNonNegative(v1);
Arguments.checkNonNegative(v2);
checkSampleSize(n1);
@@ -261,13 +268,16 @@ public final class TTest {
* @param x First sample values.
* @param y Second sample values.
* @return t statistic
- * @throws IllegalArgumentException if the number of samples in either
dataset is {@code < 2}
+ * @throws IllegalArgumentException if the number of samples in either
dataset is
+ * {@code < 2}; or the samples contain non-finite values
* @see #withMu(double)
* @see #with(DataDispersion)
*/
public double statistic(double[] x, double[] y) {
final long n1 = checkSampleSize(x.length);
final long n2 = checkSampleSize(y.length);
+ Arguments.checkFinite(x);
+ Arguments.checkFinite(y);
final DoubleStatistics.Builder b =
DoubleStatistics.builder(Statistic.MEAN, Statistic.VARIANCE);
final DoubleStatistics s1 = b.build(x);
final double m1 = s1.getAsDouble(Statistic.MEAN);
@@ -386,7 +396,7 @@ public final class TTest {
* @param y Second sample values.
* @return the test result
* @throws IllegalArgumentException if the number of samples in either
dataset
- * is {@code < 2}
+ * is {@code < 2}; or the samples contain non-finite values
* @see #statistic(double[], double[])
* @see #test(double, double, long, double, double, long)
*/
@@ -395,6 +405,8 @@ public final class TTest {
// requires the variance. So repeat the computation and compute p.
final long n1 = checkSampleSize(x.length);
final long n2 = checkSampleSize(y.length);
+ Arguments.checkFinite(x);
+ Arguments.checkFinite(y);
final DoubleStatistics.Builder b =
DoubleStatistics.builder(Statistic.MEAN, Statistic.VARIANCE);
final DoubleStatistics s1 = b.build(x);
final double m1 = s1.getAsDouble(Statistic.MEAN);
diff --git
a/commons-statistics-inference/src/test/java/org/apache/commons/statistics/inference/ArgumentsTest.java
b/commons-statistics-inference/src/test/java/org/apache/commons/statistics/inference/ArgumentsTest.java
index 6b27358b..8dbc0a22 100644
---
a/commons-statistics-inference/src/test/java/org/apache/commons/statistics/inference/ArgumentsTest.java
+++
b/commons-statistics-inference/src/test/java/org/apache/commons/statistics/inference/ArgumentsTest.java
@@ -95,20 +95,28 @@ class ArgumentsTest {
Assertions.assertTrue(ex.getMessage().contains(Double.toString(v)));
}
- @Test
- void testCheckNonNanArrayThrows() {
- Assertions.assertDoesNotThrow(() -> Arguments.checkNonNaN(new
double[0]));
+ @ParameterizedTest
+ @ValueSource(doubles = {Double.NEGATIVE_INFINITY, Double.NaN,
Double.POSITIVE_INFINITY})
+ void testCheckFiniteArrayThrows(double v) {
+ Assertions.assertDoesNotThrow(() -> Arguments.checkFinite(new
double[0]));
final double[] a = new double[3];
- Assertions.assertDoesNotThrow(() -> Arguments.checkNonNaN(a));
+ Assertions.assertDoesNotThrow(() -> Arguments.checkFinite(a));
for (int i = 0; i < a.length; i++) {
- a[i] = Double.NaN;
+ a[i] = v;
final IllegalArgumentException ex =
Assertions.assertThrows(IllegalArgumentException.class,
- () -> Arguments.checkNonNaN(a));
- Assertions.assertTrue(ex.getMessage().contains("NaN"));
+ () -> Arguments.checkFinite(a));
+
Assertions.assertTrue(ex.getMessage().contains(Double.toString(v)));
a[i] = 0;
}
}
+ @Test
+ void testCheckNonNanThrows() {
+ final IllegalArgumentException ex =
Assertions.assertThrows(IllegalArgumentException.class,
+ () -> Arguments.checkNonNaN(Double.NaN));
+ Assertions.assertTrue(ex.getMessage().contains("NaN"));
+ }
+
@Test
void testCheckRectangular() {
// Input is assumed to be non-zero length: this test what happens
diff --git
a/commons-statistics-inference/src/test/java/org/apache/commons/statistics/inference/OneWayAnovaTest.java
b/commons-statistics-inference/src/test/java/org/apache/commons/statistics/inference/OneWayAnovaTest.java
index 6e3ec255..190c57d2 100644
---
a/commons-statistics-inference/src/test/java/org/apache/commons/statistics/inference/OneWayAnovaTest.java
+++
b/commons-statistics-inference/src/test/java/org/apache/commons/statistics/inference/OneWayAnovaTest.java
@@ -54,6 +54,12 @@ class OneWayAnovaTest {
final List<double[]> allLength1 = Arrays.asList(new double[] {1}, new
double[] {2}, new double[] {3});
TestUtils.assertThrowsWithMessage(IllegalArgumentException.class,
() -> action.accept(allLength1), "degrees", "freedom", "within",
"group", "zero");
+
+ for (final double v : new double[] {Double.NaN,
Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY}) {
+ final List<double[]> nanContents = Arrays.asList(new double[] {1,
2, 3}, new double[] {4, v});
+ TestUtils.assertThrowsWithMessage(IllegalArgumentException.class,
+ () -> action.accept(nanContents), "finite",
Double.toString(v));
+ }
}
@ParameterizedTest
diff --git
a/commons-statistics-inference/src/test/java/org/apache/commons/statistics/inference/TTestTest.java
b/commons-statistics-inference/src/test/java/org/apache/commons/statistics/inference/TTestTest.java
index bbb1dcaf..376e1a61 100644
---
a/commons-statistics-inference/src/test/java/org/apache/commons/statistics/inference/TTestTest.java
+++
b/commons-statistics-inference/src/test/java/org/apache/commons/statistics/inference/TTestTest.java
@@ -67,6 +67,10 @@ class TTestTest {
() -> action.accept(m, v, 1), "values", "size");
TestUtils.assertThrowsWithMessage(IllegalArgumentException.class,
() -> action.accept(m, -1, n), "negative");
+ TestUtils.assertThrowsWithMessage(IllegalArgumentException.class,
+ () -> action.accept(m, Double.NaN, n), "NaN");
+ TestUtils.assertThrowsWithMessage(IllegalArgumentException.class,
+ () -> action.accept(Double.NaN, v, n), "NaN");
}
@ParameterizedTest
@@ -120,6 +124,12 @@ class TTestTest {
private static void assertOneSampleThrows(Consumer<double[]> action) {
TestUtils.assertThrowsWithMessage(IllegalArgumentException.class,
() -> action.accept(new double[1]), "values", "size");
+
+ for (final double v : new double[] {Double.NaN,
Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY}) {
+ final double[] badSample = {1, 2, 3, v};
+ TestUtils.assertThrowsWithMessage(IllegalArgumentException.class,
+ () -> action.accept(badSample), "finite", Double.toString(v));
+ }
}
@ParameterizedTest
@@ -182,6 +192,13 @@ class TTestTest {
() -> action.accept(sample, unequalSize), "values", "size",
"mismatch");
TestUtils.assertThrowsWithMessage(IllegalArgumentException.class,
() -> action.accept(unequalSize, sample), "values", "size",
"mismatch");
+ for (final double v : new double[] {Double.NaN,
Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY}) {
+ final double[] badSample = {1, 2, 3, v};
+ TestUtils.assertThrowsWithMessage(IllegalArgumentException.class,
+ () -> action.accept(badSample, sample), "finite",
Double.toString(v));
+ TestUtils.assertThrowsWithMessage(IllegalArgumentException.class,
+ () -> action.accept(sample, badSample), "finite",
Double.toString(v));
+ }
}
@ParameterizedTest
@@ -254,6 +271,14 @@ class TTestTest {
() -> action.accept(m, -1, n, m, v, n), "negative");
TestUtils.assertThrowsWithMessage(IllegalArgumentException.class,
() -> action.accept(m, v, n, m, -1, n), "negative");
+ TestUtils.assertThrowsWithMessage(IllegalArgumentException.class,
+ () -> action.accept(Double.NaN, v, n, m, v, n), "NaN");
+ TestUtils.assertThrowsWithMessage(IllegalArgumentException.class,
+ () -> action.accept(m, Double.NaN, n, m, v, n), "NaN");
+ TestUtils.assertThrowsWithMessage(IllegalArgumentException.class,
+ () -> action.accept(m, v, n, Double.NaN, v, n), "NaN");
+ TestUtils.assertThrowsWithMessage(IllegalArgumentException.class,
+ () -> action.accept(m, v, n, m, Double.NaN, n), "NaN");
}
@ParameterizedTest
@@ -344,6 +369,13 @@ class TTestTest {
() -> action.accept(sample, tooSmall), "values", "size");
TestUtils.assertThrowsWithMessage(IllegalArgumentException.class,
() -> action.accept(tooSmall, sample), "values", "size");
+ for (final double v : new double[] {Double.NaN,
Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY}) {
+ final double[] badSample = {1, 2, 3, v};
+ TestUtils.assertThrowsWithMessage(IllegalArgumentException.class,
+ () -> action.accept(badSample, sample), "finite",
Double.toString(v));
+ TestUtils.assertThrowsWithMessage(IllegalArgumentException.class,
+ () -> action.accept(sample, badSample), "finite",
Double.toString(v));
+ }
}
@ParameterizedTest
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index d8aa96ad..390ba746 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -53,6 +53,10 @@ If the output is not quite correct, check for invisible
trailing spaces!
</properties>
<body>
<release version="1.4" date="TBD" description="Adds new features (requires
Java 8).">
+ <action dev="aherbert" type="fix" due-to="Security scan, Aex Herbert"
issue="STATISTICS-97">
+ "TTest/OneWayAnova": Checks added for arguments that invalidate the
test
+ statistic: non-finite values in input arrays; and NaN mean arguments.
+ </action>
<action dev="aherbert" type="update" due-to="Security scan, Aex Herbert">
"FisherExactTest/HypergeometricDistribution": Update documentation
on possible long runtime when the hypergeometric distribution