From 3526b9e8e06d8d3855b05a664be85de852c3bebe Mon Sep 17 00:00:00 2001 From: Orion Yeung <11580988+orionyeung001@users.noreply.github.com> Date: Thu, 18 Jul 2024 11:13:37 -0500 Subject: [PATCH 1/5] fix: typo in `stats_tests::fisher` doctests --- src/stats_tests/fisher.rs | 11 +++++------ src/stats_tests/mod.rs | 3 +++ 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/stats_tests/fisher.rs b/src/stats_tests/fisher.rs index 7b3075f5..8456196d 100644 --- a/src/stats_tests/fisher.rs +++ b/src/stats_tests/fisher.rs @@ -109,8 +109,8 @@ fn binary_search( /// # Examples /// /// ``` -/// use statrs::statis_tests::fishers_exact; -/// use statrs::statis_tests::Alternative; +/// use statrs::stats_tests::fishers_exact_with_odds_ratio; +/// use statrs::stats_tests::Alternative; /// let table = [3, 5, 4, 50]; /// let (odds_ratio, p_value) = fishers_exact_with_odds_ratio(&table, Alternative::Less).unwrap(); /// ``` @@ -142,8 +142,8 @@ pub fn fishers_exact_with_odds_ratio( /// # Examples /// /// ``` -/// use statrs::statis_tests::fishers_exact; -/// use statrs::statis_tests::Alternative; +/// use statrs::stats_tests::fishers_exact; +/// use statrs::stats_tests::Alternative; /// let table = [3, 5, 4, 50]; /// let p_value = fishers_exact(&table, Alternative::Less).unwrap(); /// ``` @@ -209,9 +209,8 @@ pub fn fishers_exact(table: &[u64; 4], alternative: Alternative) -> Result Date: Thu, 18 Jul 2024 11:15:19 -0500 Subject: [PATCH 2/5] refactor: re-exports for `stats_tests` --- src/stats_tests/fisher.rs | 8 +------- src/stats_tests/mod.rs | 8 +++++++- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/stats_tests/fisher.rs b/src/stats_tests/fisher.rs index 8456196d..94826d00 100644 --- a/src/stats_tests/fisher.rs +++ b/src/stats_tests/fisher.rs @@ -1,13 +1,7 @@ +use super::Alternative; use crate::distribution::{Discrete, DiscreteCDF, Hypergeometric}; use crate::StatsError; -#[derive(Debug, Copy, Clone)] -pub enum Alternative { - TwoSided, - Less, - Greater, -} - const EPSILON: f64 = 1.0 - 1e-4; /// Binary search in two-sided test with starting bound as argument diff --git a/src/stats_tests/mod.rs b/src/stats_tests/mod.rs index 1e292518..4435fb3a 100644 --- a/src/stats_tests/mod.rs +++ b/src/stats_tests/mod.rs @@ -1,4 +1,10 @@ pub mod fisher; -pub use fisher::Alternative; +#[derive(Debug, Copy, Clone)] +pub enum Alternative { + TwoSided, + Less, + Greater, +} + pub use fisher::{fishers_exact, fishers_exact_with_odds_ratio}; From 98daa8435b5a290698be10df031659e7008719c6 Mon Sep 17 00:00:00 2001 From: Orion Yeung <11580988+orionyeung001@users.noreply.github.com> Date: Thu, 18 Jul 2024 12:03:59 -0500 Subject: [PATCH 3/5] doc(fisher test): basic docs --- src/stats_tests/fisher.rs | 4 +++- src/stats_tests/mod.rs | 7 +++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/stats_tests/fisher.rs b/src/stats_tests/fisher.rs index 94826d00..0af5fa11 100644 --- a/src/stats_tests/fisher.rs +++ b/src/stats_tests/fisher.rs @@ -99,7 +99,8 @@ fn binary_search( /// Perform a Fisher exact test on a 2x2 contingency table. /// Based on scipy's fisher test: https://docs.scipy.org/doc/scipy/reference/generated/scipy.stats.fisher_exact.html#scipy-stats-fisher-exact -/// Returns the odds ratio and p_value +/// Expects a table in row-major order +/// Returns the [odds ratio](https://en.wikipedia.org/wiki/Odds_ratio) and p_value /// # Examples /// /// ``` @@ -132,6 +133,7 @@ pub fn fishers_exact_with_odds_ratio( /// Perform a Fisher exact test on a 2x2 contingency table. /// Based on scipy's fisher test: https://docs.scipy.org/doc/scipy/reference/generated/scipy.stats.fisher_exact.html#scipy-stats-fisher-exact +/// Expects a table in row-major order /// Returns only the p_value /// # Examples /// diff --git a/src/stats_tests/mod.rs b/src/stats_tests/mod.rs index 4435fb3a..84a01fc7 100644 --- a/src/stats_tests/mod.rs +++ b/src/stats_tests/mod.rs @@ -1,9 +1,16 @@ pub mod fisher; +/// Specifies an [alternative hypothesis](https://en.wikipedia.org/wiki/Alternative_hypothesis) #[derive(Debug, Copy, Clone)] pub enum Alternative { + #[doc(alias = "two-tailed")] + #[doc(alias = "two tailed")] TwoSided, + #[doc(alias = "one-tailed")] + #[doc(alias = "one tailed")] Less, + #[doc(alias = "one-tailed")] + #[doc(alias = "one tailed")] Greater, } From 094a75037ed107cfd61f82b00ae5f328f0bea147 Mon Sep 17 00:00:00 2001 From: Orion Yeung <11580988+orionyeung001@users.noreply.github.com> Date: Thu, 18 Jul 2024 12:05:44 -0500 Subject: [PATCH 4/5] test(fisher test): test short circuit evaluation of all 0 row/columns --- src/stats_tests/fisher.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/stats_tests/fisher.rs b/src/stats_tests/fisher.rs index 0af5fa11..96b6ac9d 100644 --- a/src/stats_tests/fisher.rs +++ b/src/stats_tests/fisher.rs @@ -342,6 +342,16 @@ mod tests { } } } + + #[test] + fn test_fishers_exact_for_trivial() { + let cases = [[0, 0, 1, 2], [1, 2, 0, 0], [1, 0, 2, 0], [0, 1, 0, 2]]; + + for table in cases.iter() { + assert_eq!(fishers_exact(table, Alternative::Less).unwrap(), 1.0) + } + } + #[test] fn test_fishers_exact_with_odds() { let table = [3, 5, 4, 50]; From 56eb7a98146a5ba1f0eb410da6ff98a245991477 Mon Sep 17 00:00:00 2001 From: Orion Yeung <11580988+orionyeung001@users.noreply.github.com> Date: Thu, 18 Jul 2024 11:45:09 -0500 Subject: [PATCH 5/5] fix(fisher test): check rows for all 0's --- src/stats_tests/fisher.rs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/stats_tests/fisher.rs b/src/stats_tests/fisher.rs index 96b6ac9d..a18c821d 100644 --- a/src/stats_tests/fisher.rs +++ b/src/stats_tests/fisher.rs @@ -113,10 +113,11 @@ pub fn fishers_exact_with_odds_ratio( table: &[u64; 4], alternative: Alternative, ) -> Result<(f64, f64), StatsError> { - // Calculate fisher's exact test with the odds ratio - if (table[0] == 0 && table[2] == 0) || (table[1] == 0 && table[3] == 0) { - // If both values in a row or column are zero, p-value is 1 and odds ratio is NaN. - return Ok((f64::NAN, 1.0)); + // If both values in a row or column are zero, p-value is 1 and odds ratio is NaN. + match table { + [0, _, 0, _] | [_, 0, _, 0] => return Ok((f64::NAN, 1.0)), // both 0 in a row + [0, 0, _, _] | [_, _, 0, 0] => return Ok((f64::NAN, 1.0)), // both 0 in a column + _ => (), // continue } let odds_ratio = { @@ -145,8 +146,10 @@ pub fn fishers_exact_with_odds_ratio( /// ``` pub fn fishers_exact(table: &[u64; 4], alternative: Alternative) -> Result { // If both values in a row or column are zero, the p-value is 1 and the odds ratio is NaN. - if (table[0] == 0 && table[2] == 0) || (table[1] == 0 && table[3] == 0) { - return Ok(1.0); + match table { + [0, _, 0, _] | [_, 0, _, 0] => return Ok(1.0), // both 0 in a row + [0, 0, _, _] | [_, _, 0, 0] => return Ok(1.0), // both 0 in a column + _ => (), // continue } let n1 = table[0] + table[1];