1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
//! Fisher's exact test from the [R Project](https://www.r-project.org/) in a Rust crate.

extern "C" {
    fn r_fishers_exact(table: *mut i32, tails: i32) -> f64;
}

/// Passed to the fishers_exact function to express whether to perform a
/// one or two tailed statistical test.
#[derive(Clone, Copy)]
pub enum TestTails {
    One,
    Two
}

/// Computes the [Fisher's exact test](https://en.wikipedia.org/wiki/Fisher%27s_exact_test)
/// to determine if there are nonrandom associations between two
/// categorical variables, in a two by two contingency table.
///
/// The test is computed using the Fisher's exact test code copied from
/// the [R statistical computing package](https://www.r-project.org/).
///
/// Given a one dimensional representation of a two by two contingency,
/// table [a,b,c,d], the test computes:
///
/// p = ((a + b)!(c + d)!(a + c)!(b + d)!) / (a! b! c! d! (a+b+c+d)!)
///
/// Either a one tailed or two tailed test can be computed.
///
/// ```
/// use fishers_exact::{fishers_exact,TestTails};
///
/// let p = fishers_exact(&[1,9,11,3], TestTails::Two);
///
/// assert_eq!(0.001346 * 1e6, (p * 1e6).round())
/// ```
pub fn fishers_exact(table: &[i32;4], test_tails: TestTails) -> f64 {
    let mut matrix = table.clone();
    let num_tails: i32 = match test_tails {
        TestTails::One => 1,
        TestTails::Two => 2,
    };
    unsafe { r_fishers_exact(matrix.as_mut_ptr(), num_tails) }
}

#[cfg(test)]
mod tests {
    use super::fishers_exact;
    use super::TestTails;

    #[test]
    fn it_works() {

        // The examples from:
        // https://en.wikipedia.org/wiki/Fisher%27s_exact_test

        let cases = [
            ([1, 9, 11, 3], TestTails::Two, 0.001346 * 1e6),
            ([1, 9, 11, 3], TestTails::One, 0.002759 * 1e6),
            ([1, 11, 9, 3], TestTails::Two, 0.001346 * 1e6),
            ([1, 11, 9, 3], TestTails::One, 0.002759 * 1e6),
            ([0, 12, 10, 2], TestTails::Two, 0.000034 * 1e6),
        ];

        for &(table, tails, expected) in cases.iter() {
            let p: f64 = fishers_exact(&table, tails);
            assert_eq!(expected, (p * 1e6).round());
        }
    }
}