Created
September 27, 2013 22:44
-
-
Save komiya-atsushi/6736271 to your computer and use it in GitHub Desktop.
#渋谷java 第3回で発表予定のコードです。 http://atnd.org/events/42501
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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