I looked forever on the internet for a java implementation of the Fisher's Exact test, but couldn't find any, although I did find some pages with dead links on them...
So I sat down and did it.
Here's what I came up with:
public class Fisher
{
/**
* Gets the p-value for the given table
* pos neg
* in a b
* out c d n = a+b+c+d
*
* p = (a+b)!(c+d)!(a+c)!(b+d)!
* ------------------------
* n! a! b! c! d! (from http://en.wikipedia.org/wiki/Fisher%27s_exact_test)
*
* p = (a+b)!(b+d)!(c+a)!(d+c)!
* ------------------------
* n! a! b! c! d! (Rearrangement of sums and products)
*
* notation x@y = ((x+1)*(x+2)*...*(x+y)), e.g. 0@y = y!
*
* p = (a@b)(b@d)(c@a)(d@c) (x+y)!
* ------------------------ ( ------ = x@y )
* n! x!
*
*
*
* notation L@(x,y) = (ln(x+1)+ln(x+2)+...+ln(x+y))
*
* ln(p) = L@(a,b) + L@(b,d) + L@(c,a) + L@(d,c) - ( L@(0,n) )
*
* p = e ^ [L@(a,b) + L@(b,d) + L@(c,a) + L@(d,c) - ( L@(0,n) )]
*
* @return the p-value for this table
*/
private static double getPValue(int a, int b, int c, int d)
{
int n = a + b + c + d;
return Math.exp(getLogSum(a,b) + getLogSum(b,d) + getLogSum(c,a) + getLogSum(d,c) - getLogSum(0,n));
}
/**
* Gets the fisher's p-value cutoff for a point
*/
private static double getFishersExactPValue(int a, int b, int c, int d)
{
double p_cutoff = getPValue(a,b,c,d);
System.out.println("Got for: " + printTable(a,b,c,d) + p_cutoff);
double leftSide = getLeftSidePs(a, b, c, d, p_cutoff) + p_cutoff;
double rightSide = getRightSidePs(a, b, c, d, p_cutoff) + p_cutoff;
double twoSided = p_cutoff + leftSide + rightSide;
System.out.println("sides: left: " + leftSide + " right: " + rightSide);
return twoSided;
}
/**
* Gets the sum of the p-values for tables on the left side of the given table
* that are smaller than the cutoff value
*/
private static double getLeftSidePs(int a, int b, int c, int d,
double p_cutoff)
{
double sumOfLeftTables = 0;
while(c > 0 && b > 0)
{
a++;
b--;
c--;
d++;
double thisP = getPValue(a,b,c,d);
if (thisP <= p_cutoff)
sumOfLeftTables += thisP;
System.out.println("left tried: " + printTable(a,b,c,d) + getPValue(a,b,c,d));
}
return sumOfLeftTables;
}
/**
* Gets the sum of the p-values for tables on the right side of the given table
* that are smaller than the cutoff value
*/
private static double getRightSidePs(int a, int b, int c, int d,
double p_cutoff)
{
double sumOfRightTables = 0;
while(a > 0 && d > 0)
{
a--;
b++;
c++;
d--;
double thisP = getPValue(a,b,c,d);
if (thisP <= p_cutoff)
sumOfRightTables += thisP;
System.out.println("right tried: " + printTable(a,b,c,d) + getPValue(a,b,c,d));
}
return sumOfRightTables;
}
/**
* Prints the given table
*/
private static String printTable(int a, int b, int c, int d)
{
String retString = "\n|" + a + "\t" + b + "\t|\n";
retString += "|" + c + "\t" + d + "\t|\n";
return retString;
}
/**
* Returns (a+1)+(a+2)..+(a+b) = factorial in log land
* @return the sum of the log of numbers from a to a+b
*/
private static double getLogSum(int a, int b)
{
double sum = 0;
for (int i = a + 1; i <= a + b; i++)
{
sum += Math.log(i);
}
return sum;
}
public static void main(String[] args)
{
double val = getFishersExactPValue(2,3,4,5);
System.out.println("val: " + val);
}
}
And the best part, is that Wolfram agrees with my calculations, as does this online calculator.