Skip to content

Instantly share code, notes, and snippets.

@komiya-atsushi
Created September 27, 2013 22:44
Show Gist options
  • Save komiya-atsushi/6736271 to your computer and use it in GitHub Desktop.
Save komiya-atsushi/6736271 to your computer and use it in GitHub Desktop.
#渋谷java 第3回で発表予定のコードです。 http://atnd.org/events/42501
import org.apache.commons.math3.distribution.ChiSquaredDistribution;
import org.apache.commons.math3.stat.inference.ChiSquareTest;
import org.hamcrest.BaseMatcher;
import org.hamcrest.Description;
import java.util.Arrays;
import java.util.List;
/**
* カイ二乗検定を用いた検証を行う BaseMatcher 継承クラスです。
* <p>
* {@link Math#random()} や {@link java.util.Random} などの乱数に基いて振る舞う機能に対して、
* off-by-one バグを検出するなどの目的に利用できます。
* </p>
*
* @author KOMIYA Atsushi
*/
public class ChiSquaredTestMatcher extends BaseMatcher<long[]> {
private static final ChiSquareTest TESTER = new ChiSquareTest();
/** 有意水準 */
private double significanceLevel = 0.05;
/** 各事象の期待される生起確率を保持します */
private final double[] expectedProbabilities;
/** 自由度 (事象の種類数 - 1) を保持します */
private final int degreesOfFreedom;
/** 期待される結果を表すメッセージです */
private String expectedMessage;
/** 実際に得られた結果に対するメッセージです */
private String actualMessage;
/**
* ChiSquaredTestMatcher オブジェクトを生成します。
*
* @param expectedProbabilities 各事象の期待される生起確率
*/
private ChiSquaredTestMatcher(double[] expectedProbabilities) {
this.expectedProbabilities = expectedProbabilities;
this.degreesOfFreedom = expectedProbabilities.length - 1;
}
@Override
public boolean matches(Object item) {
long[] observedCounts = (long[]) item;
if (observedCounts.length != expectedProbabilities.length) {
throw new IllegalArgumentException(
String.format("観測された頻度の自由度 %d と期待される生起確率の自由度 %d が異なります",
observedCounts.length - 1, degreesOfFreedom));
}
double chiSquare = TESTER.chiSquare(expectedProbabilities, observedCounts);
ChiSquaredDistribution distribution = new ChiSquaredDistribution(degreesOfFreedom);
double upperLimitOfChiSquareValue = distribution.inverseCumulativeProbability(1 - significanceLevel);
if (chiSquare <= upperLimitOfChiSquareValue) {
return true;
}
expectedMessage = String.format("自由度 %d, 有意水準 %f の場合、χ^2 の値は %f 以下となること",
expectedProbabilities.length - 1, significanceLevel, upperLimitOfChiSquareValue);
double pValue = TESTER.chiSquareTest(expectedProbabilities, observedCounts);
actualMessage = String.format("期待される各事象の確率 %s に対する\n各事象の観測頻度 %s の χ^2 が %f となった (p 値 : %f)",
Arrays.toString(expectedProbabilities), Arrays.toString(observedCounts), chiSquare, pValue);
return false;
}
@Override
public void describeTo(Description description) {
description.appendText(expectedMessage);
}
@Override
public void describeMismatch(Object item, Description description) {
description.appendText(actualMessage);
}
/**
* 検証対象の観測頻度が、各事象の期待される生起確率に従うことを検証する
* {@link ChiSquaredTestMatcher} オブジェクトを生成して返却します。
*
* @param expectedProbabilities 各事象の期待される生起確率
* @return
*/
public static ChiSquaredTestMatcher wellFit(double... expectedProbabilities) {
return new ChiSquaredTestMatcher(expectedProbabilities);
}
/**
* 有意水準を指定します。
* <p>
* 有意水準を指定しなかった場合の既定値は 0.05 (5%) です。
* </p>
*
* @param significanceLevel 有意水準
* @return 自身のオブジェクトを返却します。
*/
public ChiSquaredTestMatcher at(double significanceLevel) {
this.significanceLevel = significanceLevel;
return this;
}
/**
* {@link ChiSquaredTestMatcher#wellFit(double...)} で検証できるように
* 観測頻度を long[] に詰めて返却します。
* <p>
* このメソッドで得られたオブジェクトを {@link org.junit.Assert#assertThat(Object, org.hamcrest.Matcher)}
* メソッドの1つ目の引数 {@code actual} として指定します。
* </p>
*
* @param counts 各事象の観測された頻度を列挙して指定します
* @return {@code counts} で指定された頻度が詰められた long[] オブジェクト
*/
public static long[] observedCounts(long... counts) {
return counts;
}
/**
* {@link ChiSquaredTestMatcher#wellFit(double...)} で検証できるように
* 観測頻度を long[] に詰めて返却します。
* <p>
* このメソッドで得られたオブジェクトを {@link org.junit.Assert#assertThat(Object, org.hamcrest.Matcher)}
* メソッドの1つ目の引数 {@code actual} として指定します。
* </p>
*
* @param counts 各事象の観測された頻度を列挙して指定します
* @return {@code counts} で指定された頻度が詰められた long[] オブジェクト
*/
public static long[] observedCounts(int... counts) {
long[] result = new long[counts.length];
for (int i = 0; i < counts.length; i++) {
result[i] = counts[i];
}
return result;
}
/**
* {@link ChiSquaredTestMatcher#wellFit(double...)} で検証できるように
* 観測頻度を long[] に詰めて返却します。
* <p>
* このメソッドで得られたオブジェクトを {@link org.junit.Assert#assertThat(Object, org.hamcrest.Matcher)}
* メソッドの1つ目の引数 {@code actual} として指定します。
* </p>
*
* @param counts 各事象の観測された頻度を保持する {@link List} オブジェクト
* @return {@code counts} で指定された頻度が詰められた long[] オブジェクト
*/
public static long[] observedCounts(List<? extends Number> counts) {
long[] result = new long[counts.size()];
for (int i = 0; i < counts.size(); i++) {
result[i] = counts.get(i).longValue();
}
return result;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment