package com.github.lindenb.jvarkit.math.stats; import java.util.function.DoubleSupplier; /* http://lh3lh3.users.sourceforge.net/fisher.shtml * https://github.com/molgenis/systemsgenetics/blob/master/genetica-libraries/src/main/java/umcg/genetica/math/stats/FisherExactTest.java * * * */ public class FisherExactTest implements DoubleSupplier,Comparable<FisherExactTest> { private double left; private double right; private double twotail; private double sleft; private double sright; private double sless; private double slarg; private int sn11; private int sn1_; private int sn_1; private int sn; private double sprob; private int n11_; private int n12_; private int n21_; private int n22_; private FisherExactTest() { } public static FisherExactTest compute(final int array[]) { if(array==null || array.length!=4) throw new IllegalArgumentException("array null or length!=4"); return compute(array[0],array[1],array[2],array[3]); } public static FisherExactTest compute(int n11, int n12, int n21, int n22) { final FisherExactTest batondecolin =new FisherExactTest(); batondecolin._fisher(n11, n12, n21, n22); return batondecolin; } private double _fisher(int n11, int n12, int n21, int n22) { n11_ = n11; n12_ = n12; n21_ = n21; n22_ = n22; if(n11_ < 0) n11_ *= -1; if(n12_ < 0) n12_ *= -1; if(n21_ < 0) n21_ *= -1; if(n22_ < 0) n22_ *= -1; int n1_ = n11_ + n12_; int n_1 = n11_ + n21_; int n = n11_ + n12_ + n21_ + n22_; exact(n11_, n1_, n_1, n); left = sless; right = slarg; twotail = sleft + sright; if(twotail > 1.0D) twotail = 1.0D; return twotail; } public double getFisherLeftTail() { return left; } public double getFisherRightTail() { return right; } public double calculateFisherTwoTail() { return twotail; } private static double lngamm(int z) { double x = 0.0D; x += 1.6594701874084621E-07D / (double)(z + 7); x += 9.9349371139307475E-06D / (double)(z + 6); x -= 0.1385710331296526D / (double)(z + 5); x += 12.50734324009056D / (double)(z + 4); x -= 176.61502914983859D / (double)(z + 3); x += 771.32342877576741D / (double)(z + 2); x -= 1259.1392167222889D / (double)(z + 1); x += 676.52036812188351D / (double)z; x += 0.99999999999951827D; return (Math.log(x) - 5.5810614667953278D - (double)z) + ((double)z - 0.5D) * Math.log((double)z + 6.5D); } private static double lnfact(int n) { if(n <= 1) return 0.0D; else return lngamm(n + 1); } private static double lnbico(int n, int k) { return lnfact(n) - lnfact(k) - lnfact(n - k); } private static double hyper_323(int n11, int n1_, int n_1, int n) { return Math.exp((lnbico(n1_, n11) + lnbico(n - n1_, n_1 - n11)) - lnbico(n, n_1)); } private double hyper(int n11) { return hyper0(n11, 0, 0, 0); } private double hyper0(int n11i, int n1_i, int n_1i, int ni) { if((n1_i == 0) & (n_1i == 0) & (ni == 0)) { if(n11i % 10 != 0) { if(n11i == sn11 + 1) { sprob = sprob * (((double)sn1_ - (double)sn11) / (double)n11i) * (((double)sn_1 - (double)sn11) / (((double)n11i + (double)sn) - (double)sn1_ - (double)sn_1)); sn11 = n11i; return sprob; } if(n11i == sn11 - 1) { sprob = sprob * ((double)sn11 / ((double)sn1_ - (double)n11i)) * ((((double)sn11 + (double)sn) - (double)sn1_ - (double)sn_1) / ((double)sn_1 - (double)n11i)); sn11 = n11i; return sprob; } } sn11 = n11i; } else { sn11 = n11i; sn1_ = n1_i; sn_1 = n_1i; sn = ni; } sprob = hyper_323(sn11, sn1_, sn_1, sn); return sprob; } private double exact(int n11, int n1_, int n_1, int n) { int max = n1_; if(n_1 < max) max = n_1; int min = (n1_ + n_1) - n; if(min < 0) min = 0; if(min == max) { sless = 1.0D; sright = 1.0D; sleft = 1.0D; slarg = 1.0D; return 1.0D; } double prob = hyper0(n11, n1_, n_1, n); sleft = 0.0D; double p = hyper(min); int i; for(i = min + 1; p < 0.99999998999999995D * prob; i++) { sleft += p; p = hyper(i); } i--; if(p < 1.0000000099999999D * prob) sleft += p; else i--; sright = 0.0D; p = hyper(max); int j; for(j = max - 1; p < 0.99999998999999995D * prob; j--) { sright += p; p = hyper(j); } j++; if(p < 1.0000000099999999D * prob) sright += p; else j++; if(Math.abs(i - n11) < Math.abs(j - n11)) { sless = sleft; slarg = (1.0D - sleft) + prob; } else { sless = (1.0D - sright) + prob; slarg = sright; } return prob; } @Override public double getAsDouble() { return this.calculateFisherTwoTail(); } @Override public String toString() { return String.valueOf(getAsDouble()); } @Override public int compareTo(final FisherExactTest o) { return new Double( this.getAsDouble()).compareTo(new Double(o.getAsDouble())); } public static void main(String[] args) { if(args.length!=4) { System.err.println("Fisher: A1 B1 A2 B2"); return; } System.out.println( FisherExactTest.compute( Integer.parseInt(args[0]), Integer.parseInt(args[1]), Integer.parseInt(args[2]), Integer.parseInt(args[3]) )); } }