From 22cad3d1a903a894b82f83b7bd6894ce2c05ca62 Mon Sep 17 00:00:00 2001 From: Phil Steitz Date: Mon, 30 Sep 2013 20:54:28 +0000 Subject: [PATCH] Added exact binomial test implementation. Contributed by Thorsten Schaefer JIRA: MATH-1034 git-svn-id: https://svn.apache.org/repos/asf/commons/proper/math/trunk@1527777 13f79535-47bb-0310-9956-ffa450edef68 --- src/changes/changes.xml | 3 + .../stat/inference/AlternativeHypothesis.java | 41 +++++ .../math3/stat/inference/BinomialTest.java | 165 ++++++++++++++++++ .../stat/inference/BinomialTestTest.java | 84 +++++++++ 4 files changed, 293 insertions(+) create mode 100644 src/main/java/org/apache/commons/math3/stat/inference/AlternativeHypothesis.java create mode 100644 src/main/java/org/apache/commons/math3/stat/inference/BinomialTest.java create mode 100644 src/test/java/org/apache/commons/math3/stat/inference/BinomialTestTest.java diff --git a/src/changes/changes.xml b/src/changes/changes.xml index 1a7b15ac9..065a02f1f 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -51,6 +51,9 @@ If the output is not quite correct, check for invisible trailing spaces! + + Added exact binomial test implementation. + Added overloaded constructors for subclasses of "RealDistribution" implementations which do not require an explicit "inverseCumulativeAccuracy". The default accuracy will diff --git a/src/main/java/org/apache/commons/math3/stat/inference/AlternativeHypothesis.java b/src/main/java/org/apache/commons/math3/stat/inference/AlternativeHypothesis.java new file mode 100644 index 000000000..6c7fcf02e --- /dev/null +++ b/src/main/java/org/apache/commons/math3/stat/inference/AlternativeHypothesis.java @@ -0,0 +1,41 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.math3.stat.inference; + +/** + * Represents an alternative hypothesis for a hypothesis test. + * + * @version $Id$ + * @since 3.3 + */ +public enum AlternativeHypothesis { + + /** + * Represents a two-sided test. H0: p=p0, H1: p ≠ p0 + */ + TWO_SIDED, + + /** + * Represents a right-sided test. H0: p ≤ p0, H1: p > p0. + */ + GREATER_THAN, + + /** + * Represents a left-sided test. H0: p ≥ p0, H1: p < p0. + */ + LESS_THAN +} diff --git a/src/main/java/org/apache/commons/math3/stat/inference/BinomialTest.java b/src/main/java/org/apache/commons/math3/stat/inference/BinomialTest.java new file mode 100644 index 000000000..0c2303033 --- /dev/null +++ b/src/main/java/org/apache/commons/math3/stat/inference/BinomialTest.java @@ -0,0 +1,165 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.math3.stat.inference; + +import org.apache.commons.math3.distribution.BinomialDistribution; +import org.apache.commons.math3.exception.MathIllegalArgumentException; +import org.apache.commons.math3.exception.MathInternalError; +import org.apache.commons.math3.exception.NotPositiveException; +import org.apache.commons.math3.exception.NullArgumentException; +import org.apache.commons.math3.exception.OutOfRangeException; +import org.apache.commons.math3.exception.util.LocalizedFormats; + +/** + * Implements binomial test statistics. + * + *

+ * Exact test for the statistical significance of deviations from a + * theoretically expected distribution of observations into two categories. + *

+ * + * @see Binomial test (Wikipedia) + * @version $Id$ + * @since 3.3 + */ +public class BinomialTest { + + /** + * Returns whether the null hypothesis can be rejected with the given + * confidence level. + * + *

+ * Preconditions: + *

    + *
  • Number of trials must be ≥ 0.
  • + *
  • Number of successes must be ≥ 0.
  • + *
  • Number of successes must be ≤ number of trials.
  • + *
  • Probability must be ≥ 0 and ≤ 1. + *
+ *

+ * + * @param numberOfTrials number of trials performed + * @param numberOfSuccesses number of successes observed + * @param probability assumed probability of a single trial under the null hypothesis + * @param alternativeHypothesis type of hypothesis being evaluated (one- or two-sided) + * @param confidenceLevel confidence level of the test + * @return true if the null hypothesis can be rejected with confidence {@code confidenceLevel} + * @throws NotPositiveException if {@code numberOfTrials} or {@code numberOfSuccesses} + * is negative + * @throws OutOfRangeException if {@code probability} is not between 0 and 1 + * @throws MathIllegalArgumentException if + * {@code numberOfTrials} < {@code numberOfSuccesses} or if {@code alternateHypothesis} + * is null. + * @see AlternativeHypothesis + */ + public boolean binomialTest(int numberOfTrials, int numberOfSuccesses, double probability, + AlternativeHypothesis alternativeHypothesis, double confidenceLevel) { + double pValue = binomialTest(numberOfTrials, numberOfSuccesses, probability, alternativeHypothesis); + return pValue < 1 - confidenceLevel; + } + + /** + * Returns the observed significance level, or + * p-value, associated with a Binomial test. + *

+ * The number returned is the smallest significance level at which one can + * reject the null hypothesis. The form of the hypothesis depends on + * {@code alternativeHypothesis}. + *

+ *

+ * Preconditions: + *

    + *
  • Number of trials must be ≥ 0.
  • + *
  • Number of successes must be ≥ 0.
  • + *
  • Number of successes must be ≤ number of trials.
  • + *
  • Probability must be ≥ 0 and ≤ 1. + *
+ *

+ * + * @param numberOfTrials number of trials performed + * @param numberOfSuccesses number of successes observed + * @param probability assumed probability of a single trial under the null hypothesis + * @param alternativeHypothesis type of hypothesis being evaluated (one- or two-sided) + * @return p-value + * @throws NotPositiveException if {@code numberOfTrials} or {@code numberOfSuccesses} + * is negative + * @throws OutOfRangeException if {@code probability} is not between 0 and 1 + * @throws MathIllegalArgumentException if + * {@code numberOfTrials} < {@code numberOfSuccesses} or if {@code alternateHypothesis} + * is null. + * @see AlternativeHypothesis + */ + public double binomialTest(int numberOfTrials, int numberOfSuccesses, double probability, + AlternativeHypothesis alternativeHypothesis) { + if (numberOfTrials < 0) { + throw new NotPositiveException(numberOfTrials); + } + if (numberOfSuccesses < 0) { + throw new NotPositiveException(numberOfSuccesses); + } + if (probability < 0 || probability > 1) { + throw new OutOfRangeException(probability, 0, 1); + } + if (numberOfTrials < numberOfSuccesses) { + throw new MathIllegalArgumentException( + LocalizedFormats.BINOMIAL_INVALID_PARAMETERS_ORDER, + numberOfTrials, numberOfSuccesses); + } + if (alternativeHypothesis == null) { + throw new NullArgumentException(); + } + + final BinomialDistribution distribution = new BinomialDistribution(numberOfTrials, probability); + switch (alternativeHypothesis) { + case GREATER_THAN: + return 1 - distribution.cumulativeProbability(numberOfSuccesses - 1); + case LESS_THAN: + return distribution.cumulativeProbability(numberOfSuccesses); + case TWO_SIDED: + int criticalValueLow = 0; + int criticalValueHigh = numberOfTrials; + double pTotal = 0; + + while (true) { + double pLow = distribution.probability(criticalValueLow); + double pHigh = distribution.probability(criticalValueHigh); + + if (pLow == pHigh) { + pTotal += 2 * pLow; + criticalValueLow++; + criticalValueHigh--; + } else if (pLow < pHigh) { + pTotal += pLow; + criticalValueLow++; + } else { + pTotal += pHigh; + criticalValueHigh--; + } + + if (criticalValueLow > numberOfSuccesses || criticalValueHigh < numberOfSuccesses) { + break; + } + } + return pTotal; + default: + throw new MathInternalError(LocalizedFormats. OUT_OF_RANGE_SIMPLE, alternativeHypothesis, + AlternativeHypothesis.TWO_SIDED, AlternativeHypothesis.LESS_THAN); + } + } +} diff --git a/src/test/java/org/apache/commons/math3/stat/inference/BinomialTestTest.java b/src/test/java/org/apache/commons/math3/stat/inference/BinomialTestTest.java new file mode 100644 index 000000000..ba7b357b1 --- /dev/null +++ b/src/test/java/org/apache/commons/math3/stat/inference/BinomialTestTest.java @@ -0,0 +1,84 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.math3.stat.inference; + +import org.apache.commons.math3.exception.MathIllegalArgumentException; +import org.apache.commons.math3.exception.NotPositiveException; +import org.junit.Assert; +import org.junit.Test; + +/** + * Test cases for the BinomialTest class. + */ + +public class BinomialTestTest { + + protected BinomialTest testStatistic = new BinomialTest(); + + private static int successes = 51; + private static int trials = 235; + private static double probability = 1.0 / 6.0; + + @Test + public void testBinomialTestPValues() { + Assert.assertEquals(0.04375, testStatistic.binomialTest( + trials, successes, probability, AlternativeHypothesis.TWO_SIDED), 1E-4); + Assert.assertEquals(0.02654, testStatistic.binomialTest( + trials, successes, probability, AlternativeHypothesis.GREATER_THAN), 1E-4); + Assert.assertEquals(0.982, testStatistic.binomialTest( + trials, successes, probability, AlternativeHypothesis.LESS_THAN), 1E-4); + } + + @Test + public void testBinomialTestExceptions() { + try { + testStatistic.binomialTest(10, -1, 0.5, AlternativeHypothesis.TWO_SIDED); + Assert.fail("Expected not positive exception"); + } catch (NotPositiveException e) { + // expected exception; + } + + try { + testStatistic.binomialTest(10, 11, 0.5, AlternativeHypothesis.TWO_SIDED); + Assert.fail("Expected illegal argument exception"); + } catch (MathIllegalArgumentException e) { + // expected exception; + } + try { + testStatistic.binomialTest(10, 11, 0.5, null); + Assert.fail("Expected illegal argument exception"); + } catch (MathIllegalArgumentException e) { + // expected exception; + } + + } + + @Test + public void testBinomialTestAcceptReject() { + double confidenceLevel95 = 0.95; + double confidenceLevel99 = 0.99; + + Assert.assertTrue(testStatistic.binomialTest(trials, successes, probability, AlternativeHypothesis.TWO_SIDED, confidenceLevel95)); + Assert.assertTrue(testStatistic.binomialTest(trials, successes, probability, AlternativeHypothesis.GREATER_THAN, confidenceLevel95)); + Assert.assertFalse(testStatistic.binomialTest(trials, successes, probability, AlternativeHypothesis.LESS_THAN, confidenceLevel95)); + + Assert.assertFalse(testStatistic.binomialTest(trials, successes, probability, AlternativeHypothesis.TWO_SIDED, confidenceLevel99)); + Assert.assertFalse(testStatistic.binomialTest(trials, successes, probability, AlternativeHypothesis.GREATER_THAN, confidenceLevel99)); + Assert.assertFalse(testStatistic.binomialTest(trials, successes, probability, AlternativeHypothesis.LESS_THAN, confidenceLevel95)); + + } +}