From 33790c01b05dfe5c87e990d834630f02b855e74b Mon Sep 17 00:00:00 2001 From: noam teyssier <22600644+noamteyssier@users.noreply.github.com> Date: Wed, 3 May 2023 09:11:36 -0700 Subject: [PATCH 01/13] use product --- src/inc.rs | 11 ++++++++--- src/utils.rs | 8 ++++++++ 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/src/inc.rs b/src/inc.rs index ec0a9c2..f6f9a62 100644 --- a/src/inc.rs +++ b/src/inc.rs @@ -2,7 +2,7 @@ use crate::{ encode::EncodeIndex, rank_test::{pseudo_rank_test, rank_test}, result::IncResult, - utils::{build_pseudo_names, reconstruct_names, select_ranks, validate_token}, mwu::Alternative, + utils::{build_pseudo_names, reconstruct_names, select_ranks, validate_token, diagonal_product}, mwu::Alternative, }; use anyhow::Result; use ndarray::Array1; @@ -10,6 +10,7 @@ use ndarray::Array1; #[derive(Debug)] pub struct Inc<'a> { pvalues: &'a Array1, + log2_fold_changes: &'a Array1, genes: &'a [String], token: &'a str, n_pseudo: usize, @@ -22,6 +23,7 @@ pub struct Inc<'a> { impl<'a> Inc<'a> { pub fn new( pvalues: &'a Array1, + log2_fold_changes: &'a Array1, genes: &'a [String], token: &'a str, n_pseudo: usize, @@ -32,6 +34,7 @@ impl<'a> Inc<'a> { ) -> Inc<'a> { Inc { pvalues, + log2_fold_changes, genes, token, n_pseudo, @@ -44,8 +47,9 @@ impl<'a> Inc<'a> { pub fn fit(&self) -> Result { let encoding = EncodeIndex::new(self.genes); + let product = diagonal_product(self.log2_fold_changes, self.pvalues); let ntc_index = validate_token(&encoding.map, self.token)?; - let ntc_values = select_ranks(ntc_index, encoding.encoding(), self.pvalues); + let ntc_values = select_ranks(ntc_index, encoding.encoding(), &product); let n_genes = encoding.map.len() - 1; // run the rank test on all genes @@ -53,7 +57,8 @@ impl<'a> Inc<'a> { n_genes, ntc_index, encoding.encoding(), - self.pvalues, + // self.pvalues, + &product, &ntc_values, self.alternative, self.continuity, diff --git a/src/utils.rs b/src/utils.rs index 7f2323d..8a12f64 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -60,6 +60,14 @@ where indices } +/// Calculates the diagonal product of fold changes and pvalues +pub fn diagonal_product( + log2_fold_changes: &Array1, + pvalues: &Array1, +) -> Array1 { + log2_fold_changes * pvalues.mapv(|x| x.exp2()) +} + #[cfg(test)] mod testing { use super::{argsort, argsort_vec}; From 162c9548d42a84c1f4046666401ff139ad37c534 Mon Sep 17 00:00:00 2001 From: noam teyssier <22600644+noamteyssier@users.noreply.github.com> Date: Mon, 8 May 2023 09:10:55 -0700 Subject: [PATCH 02/13] rename select_ranks to select_values --- src/inc.rs | 2 +- src/rank_test.rs | 4 ++-- src/utils.rs | 10 +++++----- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/inc.rs b/src/inc.rs index f6f9a62..3eb39b1 100644 --- a/src/inc.rs +++ b/src/inc.rs @@ -2,7 +2,7 @@ use crate::{ encode::EncodeIndex, rank_test::{pseudo_rank_test, rank_test}, result::IncResult, - utils::{build_pseudo_names, reconstruct_names, select_ranks, validate_token, diagonal_product}, mwu::Alternative, + utils::{build_pseudo_names, reconstruct_names, select_values, validate_token, diagonal_product, aggregate_fold_changes}, mwu::Alternative, }; use anyhow::Result; use ndarray::Array1; diff --git a/src/rank_test.rs b/src/rank_test.rs index 3e16c8d..d223ad7 100644 --- a/src/rank_test.rs +++ b/src/rank_test.rs @@ -8,14 +8,14 @@ pub fn rank_test( n_genes: usize, ntc_index: usize, encoding: &[usize], - pvalues: &Array1, + test_values: &Array1, ntc_values: &Array1, alternative: Alternative, continuity: bool, ) -> (Vec, Vec) { (0..=n_genes) .filter(|x| *x != ntc_index) - .map(|x| select_ranks(x, encoding, pvalues)) + .map(|x| select_values(x, encoding, test_values)) .map(|x| mann_whitney_u(&x, ntc_values, alternative, continuity)) .unzip() } diff --git a/src/utils.rs b/src/utils.rs index 8a12f64..729a4ca 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -18,12 +18,12 @@ pub fn validate_token(encode_map: &HashMap, token: &str) -> Result< /// Select the ranks for a provided embedding. Applies a filter which selects all ranks /// for the current gene index -pub fn select_ranks(current_idx: usize, encodings: &[usize], ranks: &Array1) -> Array1 { +pub fn select_values(current_idx: usize, encodings: &[usize], values: &Array1) -> Array1 { encodings .iter() - .zip(ranks.iter()) + .zip(values.iter()) .filter(|(idx, _ranks)| **idx == current_idx) - .map(|(_, ranks)| *ranks) + .map(|(_, value)| *value) .collect() } @@ -111,10 +111,10 @@ mod testing { } #[test] - fn test_select_ranks() { + fn test_select_values() { let encodings = vec![0, 0, 1, 1, 2, 2]; let ranks = array![0.1, 0.2, 0.3, 0.4, 0.5, 0.6]; - let selected = super::select_ranks(1, &encodings, &ranks); + let selected = super::select_values(1, &encodings, &ranks); assert_eq!(selected, array![0.3, 0.4]); } From 1355a52cf8204315dfd0d2eab3bb0661edb13f57 Mon Sep 17 00:00:00 2001 From: noam teyssier <22600644+noamteyssier@users.noreply.github.com> Date: Mon, 8 May 2023 09:11:25 -0700 Subject: [PATCH 03/13] rename log2_fold_change to logfc --- src/inc.rs | 8 +++++--- src/result.rs | 15 +++++++++++++++ 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/src/inc.rs b/src/inc.rs index 3eb39b1..ed4a57d 100644 --- a/src/inc.rs +++ b/src/inc.rs @@ -10,7 +10,7 @@ use ndarray::Array1; #[derive(Debug)] pub struct Inc<'a> { pvalues: &'a Array1, - log2_fold_changes: &'a Array1, + logfc: &'a Array1, genes: &'a [String], token: &'a str, n_pseudo: usize, @@ -18,12 +18,13 @@ pub struct Inc<'a> { alpha: f64, alternative: Alternative, continuity: bool, + seed: u64, } impl<'a> Inc<'a> { pub fn new( pvalues: &'a Array1, - log2_fold_changes: &'a Array1, + logfc: &'a Array1, genes: &'a [String], token: &'a str, n_pseudo: usize, @@ -31,10 +32,11 @@ impl<'a> Inc<'a> { alpha: f64, alternative: Alternative, continuity: bool, + seed: Option, ) -> Inc<'a> { Inc { pvalues, - log2_fold_changes, + logfc, genes, token, n_pseudo, diff --git a/src/result.rs b/src/result.rs index a218f1b..14fb89f 100644 --- a/src/result.rs +++ b/src/result.rs @@ -7,6 +7,7 @@ pub struct IncResult { genes: Vec, u_scores: Array1, u_pvalues: Array1, + logfc: Array1, fdr: FdrResult, } impl IncResult { @@ -15,20 +16,24 @@ impl IncResult { pseudo_genes: Vec, gene_scores: Vec, gene_pvalues: Vec, + gene_logfc: Vec, pseudo_scores: Vec, pseudo_pvalues: Vec, + pseudo_logfc: Vec, alpha: f64, ) -> Self { let n_pseudo = pseudo_genes.len(); let genes = vec![genes, pseudo_genes].concat(); let u_scores = Array1::from_vec(vec![gene_scores, pseudo_scores].concat()); let u_pvalues = Array1::from_vec(vec![gene_pvalues, pseudo_pvalues].concat()); + let logfc = Array1::from_vec(vec![gene_logfc, pseudo_logfc].concat()); let ntc_indices = Self::create_ntc_indices(n_pseudo, genes.len()); let fdr = Fdr::new(&u_pvalues, &ntc_indices, alpha).fit(); Self { genes, u_scores, u_pvalues, + logfc, fdr, } } @@ -59,6 +64,11 @@ impl IncResult { self.fdr.fdr() } + /// Get the log fold changes + pub fn logfc(&self) -> &Array1 { + &self.logfc + } + /// Get the p-value threshold pub fn threshold(&self) -> f64 { self.fdr.threshold() @@ -81,21 +91,26 @@ mod testing { .collect::>(); let gene_scores = vec![0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0]; let gene_pvalues = vec![0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0]; + let gene_logfc = vec![0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0]; let pseudo_scores = vec![0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0]; let pseudo_pvalues = vec![0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0]; + let pseudo_logfc = vec![0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0]; let alpha = 0.05; let result = IncResult::new( genes, pseudo_genes, gene_scores, gene_pvalues, + gene_logfc, pseudo_scores, pseudo_pvalues, + pseudo_logfc, alpha, ); assert_eq!(result.genes().len(), 20); assert_eq!(result.u_scores().len(), 20); assert_eq!(result.u_pvalues().len(), 20); + assert_eq!(result.logfc().len(), 20); assert_eq!(result.fdr().len(), 20); assert!(result.threshold() >= 0.); } From 0cd802cf7d6ad0099a182e857b16db81b9989847 Mon Sep 17 00:00:00 2001 From: noam teyssier <22600644+noamteyssier@users.noreply.github.com> Date: Mon, 8 May 2023 09:12:09 -0700 Subject: [PATCH 04/13] perform mann whitnet u test with products + calculated weighted mean + introduce seeded rng for seeding masks --- src/inc.rs | 39 +++++++++++++++++++++++----- src/rank_test.rs | 67 ++++++++++++++++++++++++++++++++++++------------ src/utils.rs | 61 ++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 144 insertions(+), 23 deletions(-) diff --git a/src/inc.rs b/src/inc.rs index ed4a57d..2933016 100644 --- a/src/inc.rs +++ b/src/inc.rs @@ -44,41 +44,68 @@ impl<'a> Inc<'a> { alpha, alternative, continuity, + seed: seed.unwrap_or(0), } } pub fn fit(&self) -> Result { let encoding = EncodeIndex::new(self.genes); - let product = diagonal_product(self.log2_fold_changes, self.pvalues); + let product = diagonal_product(self.logfc, self.pvalues); let ntc_index = validate_token(&encoding.map, self.token)?; - let ntc_values = select_ranks(ntc_index, encoding.encoding(), &product); + let ntc_pvalues = select_values(ntc_index, encoding.encoding(), self.pvalues); + let ntc_logfcs = select_values(ntc_index, encoding.encoding(), self.logfc); + let ntc_product = diagonal_product(&ntc_logfcs, &ntc_pvalues); let n_genes = encoding.map.len() - 1; + let gene_fc_map = aggregate_fold_changes( + self.genes, + self.logfc, + self.pvalues, + ); + // run the rank test on all genes let (mwu_scores, mwu_pvalues) = rank_test( n_genes, ntc_index, encoding.encoding(), - // self.pvalues, &product, - &ntc_values, + &ntc_product, self.alternative, self.continuity, ); - let (pseudo_scores, pseudo_pvalues) = - pseudo_rank_test(self.n_pseudo, self.s_pseudo, &ntc_values, self.alternative, self.continuity); + + // run the rank test on pseudo genes + let (pseudo_scores, pseudo_pvalues, pseudo_logfc) = + pseudo_rank_test( + self.n_pseudo, + self.s_pseudo, + &ntc_pvalues, + &ntc_logfcs, + self.alternative, + self.continuity, + self.seed, + ); // reconstruct the gene names let gene_names = reconstruct_names(encoding.map(), ntc_index); let pseudo_names = build_pseudo_names(self.n_pseudo); + // collect the gene fold changes + let gene_logfc = gene_names + .iter() + .map(|gene| gene_fc_map.get(gene).unwrap()) + .copied() + .collect::>(); + Ok(IncResult::new( gene_names, pseudo_names, mwu_scores, mwu_pvalues, + gene_logfc, pseudo_scores, pseudo_pvalues, + pseudo_logfc, self.alpha, )) } diff --git a/src/rank_test.rs b/src/rank_test.rs index d223ad7..c1a9dfa 100644 --- a/src/rank_test.rs +++ b/src/rank_test.rs @@ -1,6 +1,6 @@ -use crate::{mwu::{mann_whitney_u, Alternative}, utils::select_ranks}; +use crate::{mwu::{mann_whitney_u, Alternative}, utils::{select_values, diagonal_product}}; use ndarray::{Array1, Axis}; -use ndarray_rand::{rand_distr::Uniform, RandomExt}; +use ndarray_rand::{rand_distr::Uniform, RandomExt, rand::{SeedableRng, rngs::StdRng}}; /// Performs a rank test for each gene in the dataset. /// Returns a tuple of vectors containing the U and p-values for each gene. @@ -25,23 +25,56 @@ pub fn rank_test( pub fn pseudo_rank_test( n_pseudo: usize, s_pseudo: usize, - ntc_values: &Array1, + ntc_pvalues: &Array1, + ntc_logfcs: &Array1, alternative: Alternative, continuity: bool, -) -> (Vec, Vec) { + seed: u64, +) -> (Vec, Vec, Vec) { + + let mut pseudo_scores = Vec::with_capacity(n_pseudo); + let mut pseudo_pvalues = Vec::with_capacity(n_pseudo); + let mut pseudo_logfc = Vec::with_capacity(n_pseudo); + let num_ntc = ntc_pvalues.len(); + let mut rng = StdRng::seed_from_u64(seed); + (0..n_pseudo) - .map(|_| Array1::random(s_pseudo, Uniform::new(0, ntc_values.len()))) + + // generate array of random indices considered "in" the test group + .map(|_| Array1::random_using(s_pseudo, Uniform::new(0, num_ntc), &mut rng)) + + // generate the complement list of indices considered "out" of the test group .map(|mask| { - let slice_mask = mask.as_slice().unwrap(); - let out_mask = (0..ntc_values.len()) + let slice_mask = mask.as_slice().unwrap().to_owned(); + let out_mask = (0..num_ntc) .filter(|x| !slice_mask.contains(x)) .collect::>(); - let in_group = ntc_values.select(Axis(0), slice_mask); - let out_group = ntc_values.select(Axis(0), &out_mask); - (in_group, out_group) + (slice_mask, out_mask) }) - .map(|(in_group, out_group)| mann_whitney_u(&in_group, &out_group, alternative, continuity)) - .unzip() + + // subset the pvalues and logfc to the "in" and "out" groups + .map(|(in_mask, out_mask)| { + let in_group_pvalues = ntc_pvalues.select(Axis(0), &in_mask); + let in_group_logfcs = ntc_logfcs.select(Axis(0), &in_mask); + let out_group_pvalues = ntc_pvalues.select(Axis(0), &out_mask); + let out_group_logfcs = ntc_logfcs.select(Axis(0), &out_mask); + (in_group_pvalues, in_group_logfcs, out_group_pvalues, out_group_logfcs) + }) + + // calculate the U, p-values, and aggregate logfc for each pseudo gene + .for_each(|(ig_pvalues, ig_logfcs, og_pvalues, og_logfcs)| { + let ig_product = diagonal_product(&ig_logfcs, &ig_pvalues); + let og_product = diagonal_product(&og_logfcs, &og_pvalues); + + let (score, pvalue) = mann_whitney_u(&ig_product, &og_product, alternative, continuity); + let logfc = ig_logfcs.mean().unwrap_or(0.0); + + pseudo_scores.push(score); + pseudo_pvalues.push(pvalue); + pseudo_logfc.push(logfc); + }); + + (pseudo_scores, pseudo_pvalues, pseudo_logfc) } #[cfg(test)] @@ -55,10 +88,10 @@ mod testing { let n_genes = 2; let ntc_index = 0; let encoding = vec![0, 1, 2, 0, 1, 2, 0, 1, 2, 0]; - let pvalues = array![0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 0.0]; + let test_values = array![0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 0.0]; let ntc_values = array![0.7, 0.8, 0.9, 0.7, 0.8]; let alternative = Alternative::TwoSided; - let (u, p) = rank_test(n_genes, ntc_index, &encoding, &pvalues, &ntc_values, alternative, false); + let (u, p) = rank_test(n_genes, ntc_index, &encoding, &test_values, &ntc_values, alternative, false); assert_eq!(u, vec![3.0, 4.5]); assert_eq!(p, vec![0.17971249488715443, 0.37109336955630695]); } @@ -67,9 +100,11 @@ mod testing { fn test_pseudo_rank_test() { let n_pseudo = 2; let s_pseudo = 3; - let ntc_values = array![0.7, 0.7, 0.7, 0.7, 0.7, 0.7]; + let ntc_pvalues = array![0.7, 0.7, 0.7, 0.7, 0.7, 0.7]; + let ntc_logfcs = array![0.7, 0.7, 0.7, 0.7, 0.7, 0.7]; let alternative = Alternative::TwoSided; - let (_u, p) = super::pseudo_rank_test(n_pseudo, s_pseudo, &ntc_values, alternative, false); + let seed = 0; + let (_u, p, _l) = super::pseudo_rank_test(n_pseudo, s_pseudo, &ntc_pvalues, &ntc_logfcs, alternative, false, seed); assert_eq!(p, vec![1.0, 1.0]); } } diff --git a/src/utils.rs b/src/utils.rs index 729a4ca..954be85 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,6 +1,7 @@ use anyhow::{bail, Result}; use hashbrown::HashMap; -use ndarray::Array1; +use ndarray::{Array1, Axis}; +use std::hash::Hash; /// Validates the provided token is found one and only once in the gene set pub fn validate_token(encode_map: &HashMap, token: &str) -> Result { @@ -68,6 +69,64 @@ pub fn diagonal_product( log2_fold_changes * pvalues.mapv(|x| x.exp2()) } +/// recovers the indices of all unique values in a vector and returns a hashmap of the unique values and their indices +/// # Arguments +/// * `vec` - the vector to be searched and hashed +/// ``` +pub fn unique_indices(vec: &[T]) -> HashMap> { + let mut map = HashMap::new(); + for (i, x) in vec.iter().enumerate() { + map.entry(x.clone()).or_insert(Vec::new()).push(i); + } + map +} + +/// calculates the weighted fold change for each unique value in a vector +/// # Arguments +/// * `fold_change` - the fold change values +/// * `pvalues` - the pvalues corresponding to the fold change values to be inversely weighted +/// * `map` - a hashmap of the unique values and their indices +pub fn weighted_fold_change( + fold_change: &Array1, + pvalues: &Array1, + map: &HashMap>, +) -> HashMap { + map.iter() + .map(|(k, v)| { + let fc = fold_change.select(Axis(0), v); + let weights = 1.0 - pvalues.select(Axis(0), v); + let weighted_fc = weighted_mean(&fc, &weights); + (k.clone(), weighted_fc) + }) + .collect() +} + +pub fn aggregate_fold_changes( + gene_names: &[String], + fold_changes: &Array1, + pvalues: &Array1, +) -> HashMap { + let map = unique_indices(gene_names); + weighted_fold_change(fold_changes, pvalues, &map) +} + + +/// Weighted mean of the provided array +/// +/// The weighted arithmetic mean is calculated as: +/// ```text +/// m = (x @ w) / w.sum() +/// ``` +/// +/// # Arguments +/// * `array` - the array to be averaged +/// * `weights` - the weights to be applied to each element of the array +pub fn weighted_mean(array: &Array1, weights: &Array1) -> f64 { + array.dot(weights) / weights.sum() +} + + + #[cfg(test)] mod testing { use super::{argsort, argsort_vec}; From 22c9aeea39f4a2d5ccb8a4778dd7090d45e5a4c9 Mon Sep 17 00:00:00 2001 From: noam teyssier <22600644+noamteyssier@users.noreply.github.com> Date: Tue, 9 May 2023 11:57:11 -0700 Subject: [PATCH 05/13] introduce direction, calculate fdr on product --- src/fdr.rs | 55 ++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 43 insertions(+), 12 deletions(-) diff --git a/src/fdr.rs b/src/fdr.rs index a4a49a8..0386b86 100644 --- a/src/fdr.rs +++ b/src/fdr.rs @@ -1,6 +1,12 @@ -use crate::utils::{argsort, argsort_vec}; +use crate::utils::{argsort, argsort_vec, diagonal_product}; use ndarray::{Array1, Axis}; +#[derive(Clone, Copy, Debug)] +pub enum Direction { + Less, + Greater, +} + #[derive(Debug)] /// Result struct for False Discovery Rate and p-value threshold pub struct FdrResult { @@ -35,29 +41,50 @@ impl FdrResult { /// False Discovery Rate pub struct Fdr<'a> { pvalues: &'a Array1, + logfc: &'a Array1, + product: Array1, ntc_indices: &'a [usize], alpha: f64, + use_product: Option, } impl<'a> Fdr<'a> { /// Create a new FDR struct - pub fn new(pvalues: &'a Array1, ntc_indices: &'a [usize], alpha: f64) -> Self { + pub fn new( + pvalues: &'a Array1, + logfc: &'a Array1, + ntc_indices: &'a [usize], + alpha: f64, + use_product: Option, + ) -> Self { Self { pvalues, + logfc, ntc_indices, alpha, + use_product, + product: diagonal_product(logfc, pvalues), } } /// Fit the FDR pub fn fit(&self) -> FdrResult { - let order = argsort(self.pvalues); + let values = match self.use_product { + Some(Direction::Less) => &self.product, + Some(Direction::Greater) => &self.product, + None => &self.logfc, + }; + let order = match self.use_product { + Some(Direction::Less) => argsort(&self.product, true), + Some(Direction::Greater) => argsort(&self.product, false), + None => argsort(&self.pvalues, true), + }; let reorder = argsort_vec(&order); let is_ntc = Self::ntc_mask(self.ntc_indices, self.pvalues.len()); - let sorted_pvalues = self.pvalues.select(Axis(0), &order); + let sorted_values = values.select(Axis(0), &order); let sorted_ntc = is_ntc.select(Axis(0), &order); let sorted_fdr = Self::empirical_fdr(&sorted_ntc); - let threshold = Self::threshold(&sorted_pvalues, &sorted_fdr, self.alpha); + let threshold = Self::threshold(&sorted_values, &sorted_fdr, self.alpha); let unsorted_fdr = sorted_fdr.select(Axis(0), &reorder); FdrResult::new(unsorted_fdr, threshold) } @@ -87,11 +114,11 @@ impl<'a> Fdr<'a> { } /// Calculate the p-value threshold - fn threshold(pvalues: &Array1, fdr: &Array1, alpha: f64) -> f64 { + fn threshold(values: &Array1, fdr: &Array1, alpha: f64) -> f64 { let fdr_pval = fdr .iter() - .zip(pvalues.iter()) - .take_while(|(fdr, _pvalue)| *fdr <= &alpha) + .zip(values.iter()) + .take_while(|(fdr, _value)| *fdr <= &alpha) .reduce(|_x, y| y); if let Some(fp) = fdr_pval { *fp.1 @@ -109,27 +136,30 @@ mod testing { #[test] fn test_fdr() { let pvalues = array![0.1, 0.2, 0.3]; + let logfc = array![0.1, 0.2, 0.3]; let ntc_indices = vec![1]; let alpha = 0.1; - let fdr = Fdr::new(&pvalues, &ntc_indices, alpha).fit(); + let fdr = Fdr::new(&pvalues, &logfc, &ntc_indices, alpha, None).fit(); assert_eq!(fdr.fdr(), array![0.0, 0.5, 1. / 3.]); } #[test] fn test_fdr_unsorted() { let pvalues = array![0.2, 0.1, 0.3]; + let logfc = array![0.1, 0.2, 0.3]; let ntc_indices = vec![1]; let alpha = 0.1; - let fdr = Fdr::new(&pvalues, &ntc_indices, alpha).fit(); + let fdr = Fdr::new(&pvalues, &logfc, &ntc_indices, alpha, None).fit(); assert_eq!(fdr.fdr(), array![0.5, 1.0, 1. / 3.]); } #[test] fn test_fdr_unsorted_larger() { let pvalues = array![0.5, 0.1, 0.3, 0.4, 0.2, 0.6]; + let logfc = array![0.1, 0.2, 0.3]; let ntc_indices = vec![3, 5]; let alpha = 0.1; - let fdr = Fdr::new(&pvalues, &ntc_indices, alpha).fit(); + let fdr = Fdr::new(&pvalues, &logfc, &ntc_indices, alpha, None).fit(); assert_eq!(fdr.fdr(), array![0.2, 0.0, 0.0, 0.25, 0.0, 1. / 3.]); } @@ -144,9 +174,10 @@ mod testing { fn test_fdr_threshold() { let m = 10; let pvalues = Array1::linspace(0.0, 1.0, m); + let logfc = Array1::linspace(0.0, 1.0, m); let ntc_indices = vec![4, 6, 9]; let alpha = 0.1; - let fdr = Fdr::new(&pvalues, &ntc_indices, alpha).fit(); + let fdr = Fdr::new(&pvalues, &logfc, &ntc_indices, alpha, None).fit(); assert_eq!(fdr.threshold(), 1. / 3.); } } From 73f7944fb5fd86c13f30df96281a0fb6088c956b Mon Sep 17 00:00:00 2001 From: noam teyssier <22600644+noamteyssier@users.noreply.github.com> Date: Tue, 9 May 2023 11:57:30 -0700 Subject: [PATCH 06/13] introduce direction and use_product argument --- src/inc.rs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/inc.rs b/src/inc.rs index 2933016..621bcad 100644 --- a/src/inc.rs +++ b/src/inc.rs @@ -2,7 +2,7 @@ use crate::{ encode::EncodeIndex, rank_test::{pseudo_rank_test, rank_test}, result::IncResult, - utils::{build_pseudo_names, reconstruct_names, select_values, validate_token, diagonal_product, aggregate_fold_changes}, mwu::Alternative, + utils::{build_pseudo_names, reconstruct_names, select_values, validate_token, aggregate_fold_changes}, mwu::Alternative, fdr::Direction, }; use anyhow::Result; use ndarray::Array1; @@ -18,6 +18,7 @@ pub struct Inc<'a> { alpha: f64, alternative: Alternative, continuity: bool, + use_product: Option, seed: u64, } @@ -32,6 +33,7 @@ impl<'a> Inc<'a> { alpha: f64, alternative: Alternative, continuity: bool, + use_product: Option, seed: Option, ) -> Inc<'a> { Inc { @@ -44,23 +46,21 @@ impl<'a> Inc<'a> { alpha, alternative, continuity, + use_product, seed: seed.unwrap_or(0), } } pub fn fit(&self) -> Result { let encoding = EncodeIndex::new(self.genes); - let product = diagonal_product(self.logfc, self.pvalues); let ntc_index = validate_token(&encoding.map, self.token)?; let ntc_pvalues = select_values(ntc_index, encoding.encoding(), self.pvalues); let ntc_logfcs = select_values(ntc_index, encoding.encoding(), self.logfc); - let ntc_product = diagonal_product(&ntc_logfcs, &ntc_pvalues); let n_genes = encoding.map.len() - 1; let gene_fc_map = aggregate_fold_changes( self.genes, self.logfc, - self.pvalues, ); // run the rank test on all genes @@ -68,9 +68,9 @@ impl<'a> Inc<'a> { n_genes, ntc_index, encoding.encoding(), - &product, - &ntc_product, - self.alternative, + self.pvalues, + &ntc_pvalues, + Alternative::Less, self.continuity, ); @@ -107,6 +107,7 @@ impl<'a> Inc<'a> { pseudo_pvalues, pseudo_logfc, self.alpha, + self.use_product, )) } } From d91cfb433f8b4a06ea89dd6f1378b1a5d88e50fa Mon Sep 17 00:00:00 2001 From: noam teyssier <22600644+noamteyssier@users.noreply.github.com> Date: Tue, 9 May 2023 11:57:51 -0700 Subject: [PATCH 07/13] calculate rank testing using the pvalues only --- src/rank_test.rs | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/rank_test.rs b/src/rank_test.rs index c1a9dfa..725105c 100644 --- a/src/rank_test.rs +++ b/src/rank_test.rs @@ -1,4 +1,4 @@ -use crate::{mwu::{mann_whitney_u, Alternative}, utils::{select_values, diagonal_product}}; +use crate::{mwu::{mann_whitney_u, Alternative}, utils::select_values}; use ndarray::{Array1, Axis}; use ndarray_rand::{rand_distr::Uniform, RandomExt, rand::{SeedableRng, rngs::StdRng}}; @@ -57,16 +57,12 @@ pub fn pseudo_rank_test( let in_group_pvalues = ntc_pvalues.select(Axis(0), &in_mask); let in_group_logfcs = ntc_logfcs.select(Axis(0), &in_mask); let out_group_pvalues = ntc_pvalues.select(Axis(0), &out_mask); - let out_group_logfcs = ntc_logfcs.select(Axis(0), &out_mask); - (in_group_pvalues, in_group_logfcs, out_group_pvalues, out_group_logfcs) + (in_group_pvalues, in_group_logfcs, out_group_pvalues) }) // calculate the U, p-values, and aggregate logfc for each pseudo gene - .for_each(|(ig_pvalues, ig_logfcs, og_pvalues, og_logfcs)| { - let ig_product = diagonal_product(&ig_logfcs, &ig_pvalues); - let og_product = diagonal_product(&og_logfcs, &og_pvalues); - - let (score, pvalue) = mann_whitney_u(&ig_product, &og_product, alternative, continuity); + .for_each(|(ig_pvalues, ig_logfcs, og_pvalues)| { + let (score, pvalue) = mann_whitney_u(&ig_pvalues, &og_pvalues, alternative, continuity); let logfc = ig_logfcs.mean().unwrap_or(0.0); pseudo_scores.push(score); From 15735cdc63877b03b4e121d09d9f18d86b105516 Mon Sep 17 00:00:00 2001 From: noam teyssier <22600644+noamteyssier@users.noreply.github.com> Date: Tue, 9 May 2023 11:58:08 -0700 Subject: [PATCH 08/13] introduce the direction argument for calculating the fdr --- src/result.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/result.rs b/src/result.rs index 14fb89f..dac79d2 100644 --- a/src/result.rs +++ b/src/result.rs @@ -1,4 +1,4 @@ -use crate::fdr::{Fdr, FdrResult}; +use crate::fdr::{Fdr, FdrResult, Direction}; use ndarray::Array1; #[derive(Debug)] @@ -21,6 +21,7 @@ impl IncResult { pseudo_pvalues: Vec, pseudo_logfc: Vec, alpha: f64, + use_product: Option, ) -> Self { let n_pseudo = pseudo_genes.len(); let genes = vec![genes, pseudo_genes].concat(); @@ -28,7 +29,7 @@ impl IncResult { let u_pvalues = Array1::from_vec(vec![gene_pvalues, pseudo_pvalues].concat()); let logfc = Array1::from_vec(vec![gene_logfc, pseudo_logfc].concat()); let ntc_indices = Self::create_ntc_indices(n_pseudo, genes.len()); - let fdr = Fdr::new(&u_pvalues, &ntc_indices, alpha).fit(); + let fdr = Fdr::new(&u_pvalues, &logfc, &ntc_indices, alpha, use_product).fit(); Self { genes, u_scores, @@ -106,6 +107,7 @@ mod testing { pseudo_pvalues, pseudo_logfc, alpha, + None, ); assert_eq!(result.genes().len(), 20); assert_eq!(result.u_scores().len(), 20); From 5ce711eb4d38c42992cc1b82f96e6ed622b1a434 Mon Sep 17 00:00:00 2001 From: noam teyssier <22600644+noamteyssier@users.noreply.github.com> Date: Tue, 9 May 2023 11:58:32 -0700 Subject: [PATCH 09/13] add ascending argument to argsort and remove weighted fold change --- src/utils.rs | 64 +++++++++++++++------------------------------------- 1 file changed, 18 insertions(+), 46 deletions(-) diff --git a/src/utils.rs b/src/utils.rs index 954be85..6e725e9 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -42,12 +42,16 @@ pub fn build_pseudo_names(n_pseudo: usize) -> Vec { } /// Performs an argsort on a 1D ndarray and returns an array of indices -pub fn argsort(array: &Array1) -> Vec +pub fn argsort(array: &Array1, ascending: bool) -> Vec where T: PartialOrd, { let mut indices: Vec = (0..array.len()).collect(); - indices.sort_by(|&a, &b| array[a].partial_cmp(&array[b]).unwrap()); + if ascending { + indices.sort_by(|&a, &b| array[a].partial_cmp(&array[b]).unwrap()); + } else { + indices.sort_by(|&a, &b| array[b].partial_cmp(&array[a]).unwrap()); + } indices } @@ -66,7 +70,7 @@ pub fn diagonal_product( log2_fold_changes: &Array1, pvalues: &Array1, ) -> Array1 { - log2_fold_changes * pvalues.mapv(|x| x.exp2()) + log2_fold_changes * pvalues.mapv(|x| -x.log10()) } /// recovers the indices of all unique values in a vector and returns a hashmap of the unique values and their indices @@ -81,52 +85,20 @@ pub fn unique_indices(vec: &[T]) -> HashMap> map } -/// calculates the weighted fold change for each unique value in a vector -/// # Arguments -/// * `fold_change` - the fold change values -/// * `pvalues` - the pvalues corresponding to the fold change values to be inversely weighted -/// * `map` - a hashmap of the unique values and their indices -pub fn weighted_fold_change( - fold_change: &Array1, - pvalues: &Array1, - map: &HashMap>, -) -> HashMap { - map.iter() - .map(|(k, v)| { - let fc = fold_change.select(Axis(0), v); - let weights = 1.0 - pvalues.select(Axis(0), v); - let weighted_fc = weighted_mean(&fc, &weights); - (k.clone(), weighted_fc) - }) - .collect() -} - pub fn aggregate_fold_changes( gene_names: &[String], fold_changes: &Array1, - pvalues: &Array1, ) -> HashMap { - let map = unique_indices(gene_names); - weighted_fold_change(fold_changes, pvalues, &map) -} - - -/// Weighted mean of the provided array -/// -/// The weighted arithmetic mean is calculated as: -/// ```text -/// m = (x @ w) / w.sum() -/// ``` -/// -/// # Arguments -/// * `array` - the array to be averaged -/// * `weights` - the weights to be applied to each element of the array -pub fn weighted_mean(array: &Array1, weights: &Array1) -> f64 { - array.dot(weights) / weights.sum() + let idx_map = unique_indices(gene_names); + idx_map + .iter() + .map(|(k, v)| { + let fc = fold_changes.select(Axis(0), v).mean().unwrap(); + (k.clone(), fc) + }) + .collect() } - - #[cfg(test)] mod testing { use super::{argsort, argsort_vec}; @@ -137,7 +109,7 @@ mod testing { #[test] fn test_argsort_forward() { let array = array![1.0, 2.0, 3.0, 4.0, 5.0]; - let sorted = argsort(&array); + let sorted = argsort(&array, true); assert_eq!(sorted, vec![0, 1, 2, 3, 4]); assert_eq!( array.select(Axis(0), &sorted), @@ -148,7 +120,7 @@ mod testing { #[test] fn test_argsort_reverse() { let array = array![5.0, 4.0, 3.0, 2.0, 1.0]; - let sorted = argsort(&array); + let sorted = argsort(&array, true); assert_eq!(sorted, vec![4, 3, 2, 1, 0]); assert_eq!( array.select(Axis(0), &sorted), @@ -159,7 +131,7 @@ mod testing { #[test] fn test_reordering() { let pvalues = Array1::random(100, Uniform::new(0.0, 1.0)); - let order = argsort(&pvalues); + let order = argsort(&pvalues, true); let reorder = argsort_vec(&order); let sorted_pvalues = pvalues.select(Axis(0), &order); From e478bf121f08b6ce202890b12e905cb22b5507a8 Mon Sep 17 00:00:00 2001 From: noam teyssier <22600644+noamteyssier@users.noreply.github.com> Date: Tue, 9 May 2023 11:58:41 -0700 Subject: [PATCH 10/13] ran cargo formatting --- src/inc.rs | 31 ++++++++++++++++--------------- src/mwu.rs | 25 +++++++++++++++---------- src/rank_test.rs | 40 +++++++++++++++++++++++++++++----------- src/result.rs | 2 +- src/utils.rs | 5 +---- 5 files changed, 62 insertions(+), 41 deletions(-) diff --git a/src/inc.rs b/src/inc.rs index 621bcad..1e5a16c 100644 --- a/src/inc.rs +++ b/src/inc.rs @@ -1,8 +1,13 @@ use crate::{ encode::EncodeIndex, + fdr::Direction, + mwu::Alternative, rank_test::{pseudo_rank_test, rank_test}, result::IncResult, - utils::{build_pseudo_names, reconstruct_names, select_values, validate_token, aggregate_fold_changes}, mwu::Alternative, fdr::Direction, + utils::{ + aggregate_fold_changes, build_pseudo_names, reconstruct_names, select_values, + validate_token, + }, }; use anyhow::Result; use ndarray::Array1; @@ -58,10 +63,7 @@ impl<'a> Inc<'a> { let ntc_logfcs = select_values(ntc_index, encoding.encoding(), self.logfc); let n_genes = encoding.map.len() - 1; - let gene_fc_map = aggregate_fold_changes( - self.genes, - self.logfc, - ); + let gene_fc_map = aggregate_fold_changes(self.genes, self.logfc); // run the rank test on all genes let (mwu_scores, mwu_pvalues) = rank_test( @@ -75,16 +77,15 @@ impl<'a> Inc<'a> { ); // run the rank test on pseudo genes - let (pseudo_scores, pseudo_pvalues, pseudo_logfc) = - pseudo_rank_test( - self.n_pseudo, - self.s_pseudo, - &ntc_pvalues, - &ntc_logfcs, - self.alternative, - self.continuity, - self.seed, - ); + let (pseudo_scores, pseudo_pvalues, pseudo_logfc) = pseudo_rank_test( + self.n_pseudo, + self.s_pseudo, + &ntc_pvalues, + &ntc_logfcs, + self.alternative, + self.continuity, + self.seed, + ); // reconstruct the gene names let gene_names = reconstruct_names(encoding.map(), ntc_index); diff --git a/src/mwu.rs b/src/mwu.rs index fbc60e4..7757559 100644 --- a/src/mwu.rs +++ b/src/mwu.rs @@ -5,10 +5,9 @@ use statrs::{ }; use std::ops::Div; -/// Defines the alternative hypothesis +/// Defines the alternative hypothesis #[derive(Clone, Copy, Default, Debug)] pub enum Alternative { - /// The alternative hypothesis is that the first array is greater than the second array Greater, @@ -150,11 +149,11 @@ fn p_value(z_u: f64, alternative: Alternative) -> f64 { /// * `alternative` - The alternative hypothesis to test against /// * `use_continuity` - Whether to use continuity correction pub fn mann_whitney_u( - x: &Array1, - y: &Array1, - alternative: Alternative, - use_continuity: bool, - ) -> (f64, f64) { + x: &Array1, + y: &Array1, + alternative: Alternative, + use_continuity: bool, +) -> (f64, f64) { let (ranks_x, ranks_y) = merged_ranks(x, y); let nx = x.len() as f64; @@ -169,8 +168,8 @@ pub fn mann_whitney_u( #[cfg(test)] mod testing { - use crate::mwu::mann_whitney_u; use super::merged_ranks; + use crate::mwu::mann_whitney_u; use ndarray::{array, Array}; const EPSILON: f64 = 1e-10; @@ -236,14 +235,20 @@ mod testing { fn test_alt_u_statistic_greater() { let x = array![1., 2., 4.]; let y = array![3., 5., 6.]; - assert_eq!(super::alt_u_statistic(&x, &y, super::Alternative::Greater), 8.); + assert_eq!( + super::alt_u_statistic(&x, &y, super::Alternative::Greater), + 8. + ); } #[test] fn test_alt_u_statistic_two_sided() { let x = array![1., 2., 4.]; let y = array![3., 5., 6.]; - assert_eq!(super::alt_u_statistic(&x, &y, super::Alternative::TwoSided), 1.); + assert_eq!( + super::alt_u_statistic(&x, &y, super::Alternative::TwoSided), + 1. + ); } #[test] diff --git a/src/rank_test.rs b/src/rank_test.rs index 725105c..c648dfe 100644 --- a/src/rank_test.rs +++ b/src/rank_test.rs @@ -1,6 +1,13 @@ -use crate::{mwu::{mann_whitney_u, Alternative}, utils::select_values}; +use crate::{ + mwu::{mann_whitney_u, Alternative}, + utils::select_values, +}; use ndarray::{Array1, Axis}; -use ndarray_rand::{rand_distr::Uniform, RandomExt, rand::{SeedableRng, rngs::StdRng}}; +use ndarray_rand::{ + rand::{rngs::StdRng, SeedableRng}, + rand_distr::Uniform, + RandomExt, +}; /// Performs a rank test for each gene in the dataset. /// Returns a tuple of vectors containing the U and p-values for each gene. @@ -31,7 +38,6 @@ pub fn pseudo_rank_test( continuity: bool, seed: u64, ) -> (Vec, Vec, Vec) { - let mut pseudo_scores = Vec::with_capacity(n_pseudo); let mut pseudo_pvalues = Vec::with_capacity(n_pseudo); let mut pseudo_logfc = Vec::with_capacity(n_pseudo); @@ -39,10 +45,8 @@ pub fn pseudo_rank_test( let mut rng = StdRng::seed_from_u64(seed); (0..n_pseudo) - // generate array of random indices considered "in" the test group .map(|_| Array1::random_using(s_pseudo, Uniform::new(0, num_ntc), &mut rng)) - // generate the complement list of indices considered "out" of the test group .map(|mask| { let slice_mask = mask.as_slice().unwrap().to_owned(); @@ -51,7 +55,6 @@ pub fn pseudo_rank_test( .collect::>(); (slice_mask, out_mask) }) - // subset the pvalues and logfc to the "in" and "out" groups .map(|(in_mask, out_mask)| { let in_group_pvalues = ntc_pvalues.select(Axis(0), &in_mask); @@ -59,7 +62,6 @@ pub fn pseudo_rank_test( let out_group_pvalues = ntc_pvalues.select(Axis(0), &out_mask); (in_group_pvalues, in_group_logfcs, out_group_pvalues) }) - // calculate the U, p-values, and aggregate logfc for each pseudo gene .for_each(|(ig_pvalues, ig_logfcs, og_pvalues)| { let (score, pvalue) = mann_whitney_u(&ig_pvalues, &og_pvalues, alternative, continuity); @@ -70,14 +72,14 @@ pub fn pseudo_rank_test( pseudo_logfc.push(logfc); }); - (pseudo_scores, pseudo_pvalues, pseudo_logfc) + (pseudo_scores, pseudo_pvalues, pseudo_logfc) } #[cfg(test)] mod testing { use super::rank_test; - use ndarray::array; use crate::mwu::Alternative; + use ndarray::array; #[test] fn test_rank_test() { @@ -87,7 +89,15 @@ mod testing { let test_values = array![0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 0.0]; let ntc_values = array![0.7, 0.8, 0.9, 0.7, 0.8]; let alternative = Alternative::TwoSided; - let (u, p) = rank_test(n_genes, ntc_index, &encoding, &test_values, &ntc_values, alternative, false); + let (u, p) = rank_test( + n_genes, + ntc_index, + &encoding, + &test_values, + &ntc_values, + alternative, + false, + ); assert_eq!(u, vec![3.0, 4.5]); assert_eq!(p, vec![0.17971249488715443, 0.37109336955630695]); } @@ -100,7 +110,15 @@ mod testing { let ntc_logfcs = array![0.7, 0.7, 0.7, 0.7, 0.7, 0.7]; let alternative = Alternative::TwoSided; let seed = 0; - let (_u, p, _l) = super::pseudo_rank_test(n_pseudo, s_pseudo, &ntc_pvalues, &ntc_logfcs, alternative, false, seed); + let (_u, p, _l) = super::pseudo_rank_test( + n_pseudo, + s_pseudo, + &ntc_pvalues, + &ntc_logfcs, + alternative, + false, + seed, + ); assert_eq!(p, vec![1.0, 1.0]); } } diff --git a/src/result.rs b/src/result.rs index dac79d2..859cb71 100644 --- a/src/result.rs +++ b/src/result.rs @@ -1,4 +1,4 @@ -use crate::fdr::{Fdr, FdrResult, Direction}; +use crate::fdr::{Direction, Fdr, FdrResult}; use ndarray::Array1; #[derive(Debug)] diff --git a/src/utils.rs b/src/utils.rs index 6e725e9..a769528 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -66,10 +66,7 @@ where } /// Calculates the diagonal product of fold changes and pvalues -pub fn diagonal_product( - log2_fold_changes: &Array1, - pvalues: &Array1, -) -> Array1 { +pub fn diagonal_product(log2_fold_changes: &Array1, pvalues: &Array1) -> Array1 { log2_fold_changes * pvalues.mapv(|x| -x.log10()) } From 4e77b63963e0c8b11414a9e7655e52252b829e1c Mon Sep 17 00:00:00 2001 From: noam teyssier <22600644+noamteyssier@users.noreply.github.com> Date: Tue, 9 May 2023 12:39:15 -0700 Subject: [PATCH 11/13] remove logfc from fdr + fix bug and select pvalues --- src/fdr.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/fdr.rs b/src/fdr.rs index 0386b86..8d6ef25 100644 --- a/src/fdr.rs +++ b/src/fdr.rs @@ -41,7 +41,6 @@ impl FdrResult { /// False Discovery Rate pub struct Fdr<'a> { pvalues: &'a Array1, - logfc: &'a Array1, product: Array1, ntc_indices: &'a [usize], alpha: f64, @@ -59,7 +58,6 @@ impl<'a> Fdr<'a> { ) -> Self { Self { pvalues, - logfc, ntc_indices, alpha, use_product, @@ -72,7 +70,7 @@ impl<'a> Fdr<'a> { let values = match self.use_product { Some(Direction::Less) => &self.product, Some(Direction::Greater) => &self.product, - None => &self.logfc, + None => &self.pvalues, }; let order = match self.use_product { Some(Direction::Less) => argsort(&self.product, true), From 5b06fa9bdade6b901a7523e424aab83bcda9eca0 Mon Sep 17 00:00:00 2001 From: noam teyssier <22600644+noamteyssier@users.noreply.github.com> Date: Tue, 9 May 2023 13:19:19 -0700 Subject: [PATCH 12/13] fix malformed test --- src/fdr.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/fdr.rs b/src/fdr.rs index 8d6ef25..9c0440b 100644 --- a/src/fdr.rs +++ b/src/fdr.rs @@ -154,7 +154,7 @@ mod testing { #[test] fn test_fdr_unsorted_larger() { let pvalues = array![0.5, 0.1, 0.3, 0.4, 0.2, 0.6]; - let logfc = array![0.1, 0.2, 0.3]; + let logfc = array![0.5, 0.1, 0.3, 0.4, 0.2, 0.6]; let ntc_indices = vec![3, 5]; let alpha = 0.1; let fdr = Fdr::new(&pvalues, &logfc, &ntc_indices, alpha, None).fit(); From b227e654e1a75ed50466448a4d53834148b94f92 Mon Sep 17 00:00:00 2001 From: noam teyssier <22600644+noamteyssier@users.noreply.github.com> Date: Tue, 9 May 2023 13:24:58 -0700 Subject: [PATCH 13/13] bump version --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index aa869ad..a0dbad6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "intc" -version = "0.1.4" +version = "0.2.0" edition = "2021" description = "An implementation of the *-INC method to calculate an empirical FDR for non-targeting controls in CRISPR screens " license = "MIT"