1use rayon::prelude::*;
30use rustc_hash::FxHashMap;
31
32use crate::core::dgelist::DgeList;
33use crate::glm::fit::{DEFAULT_PRIOR_COUNT, GlmFit, glm_fit, glm_fit_dge};
34use crate::glm::test::{GlmTestInput, Tested, glm_lrt};
35use crate::limma::squeeze_var::{SqueezeVarParams, squeeze_var};
36use crate::numeric::dist::{chisq_sf, f_sf};
37use crate::prelude::*;
38use crate::utils::design::contrast_as_coef;
39
40const GENE_LEVEL_DISPERSION: f64 = 0.05;
50
51#[derive(Clone, Copy, Debug)]
57pub struct DiffSpliceParams {
58 pub prior_count: f64,
62 pub robust: Option<bool>,
66}
67
68impl DiffSpliceParams {
69 pub fn new(prior_count: f64, robust: Option<bool>) -> Self {
80 Self {
81 prior_count,
82 robust,
83 }
84 }
85}
86
87impl Default for DiffSpliceParams {
88 fn default() -> Self {
90 Self {
91 prior_count: DEFAULT_PRIOR_COUNT,
92 robust: None,
93 }
94 }
95}
96
97#[derive(Clone, Copy, Debug)]
116pub struct DiffSpliceQl<'a> {
117 pub df_prior: &'a [f64],
120 pub df_residual: &'a [f64],
122 pub deviance: &'a [f64],
124 pub legacy_zeros: bool,
131 pub average_ql_dispersion: Option<f64>,
135}
136
137#[derive(Clone, Debug)]
147pub struct DiffSpliceResult {
148 pub exon_log_fc: Vec<f64>,
155 pub exon_statistic: Vec<f64>,
160 pub exon_p_value: Vec<f64>,
162 pub gene_id: Vec<usize>,
164 pub gene_simes_p: Vec<f64>,
169 pub gene_f_statistic: Vec<f64>,
172 pub gene_f_p_value: Vec<f64>,
174 pub gene_n_exons: Vec<usize>,
176}
177
178pub fn diff_splice<T: EdgeFloat>(
216 input: &GlmTestInput<'_, T>,
217 fit: &GlmFit,
218 gene_id: &[usize],
219 tested: &Tested,
220 ql: Option<&DiffSpliceQl<'_>>,
221 params: Option<DiffSpliceParams>,
222) -> Result<DiffSpliceResult, EdgeErrors> {
223 let params = params.unwrap_or_default();
224 validate(input, fit, gene_id, ql, ¶ms)?;
225
226 let n_exons = input.n_genes;
227 let n_samples = input.n_samples;
228 let n_coef = input.n_coef;
229
230 let resolved = resolve_tested(input, fit, tested)?;
231 let groups = group_by_gene(gene_id);
232
233 let kept_genes: Vec<usize> = (0..groups.ids.len())
234 .filter(|g| groups.members[*g].len() > 1)
235 .collect();
236 if kept_genes.is_empty() {
237 return Err(EdgeErrors::InvalidArgument(
238 "no gene has more than one exon, so there is no within-gene contrast to test"
239 .to_string(),
240 ));
241 }
242
243 let mut kept_exons: Vec<usize> = Vec::new();
244 let mut exon_gene: Vec<usize> = Vec::new();
245 let mut gene_start: Vec<usize> = Vec::with_capacity(kept_genes.len());
246 for (slot, &gene) in kept_genes.iter().enumerate() {
247 gene_start.push(kept_exons.len());
248 for &exon in &groups.members[gene] {
249 kept_exons.push(exon);
250 exon_gene.push(slot);
251 }
252 }
253 let n_kept_genes = kept_genes.len();
254 let n_kept_exons = kept_exons.len();
255 let gene_n_exons: Vec<usize> = kept_genes
256 .iter()
257 .map(|g| groups.members[*g].len())
258 .collect();
259
260 let mut gene_counts = vec![0.0_f64; n_kept_genes * n_samples];
261 for (exon, &gene) in kept_exons.iter().zip(exon_gene.iter()) {
262 let source = &input.counts[exon * n_samples..(exon + 1) * n_samples];
263 let target = &mut gene_counts[gene * n_samples..(gene + 1) * n_samples];
264 for (acc, v) in target.iter_mut().zip(source.iter()) {
265 *acc += v.to_f64().unwrap_or(f64::NAN);
266 }
267 }
268
269 let gene_offset: Vec<f64> = input.offset.row(kept_exons[0], n_samples).to_vec(n_samples);
270 let gene_fit = glm_fit(
271 &gene_counts,
272 n_kept_genes,
273 n_samples,
274 &resolved.design,
275 n_coef,
276 &Recycled::scalar(GENE_LEVEL_DISPERSION),
277 &Recycled::by_sample(gene_offset),
278 None,
279 params.prior_count,
280 )?;
281 let betabar: Vec<f64> = (0..n_kept_genes)
282 .map(|g| gene_fit.coefficients[g * n_coef + resolved.coef])
283 .collect();
284
285 let mut counts_kept: Vec<T> = Vec::with_capacity(n_kept_exons * n_samples);
286 let mut offset_new: Vec<f64> = Vec::with_capacity(n_kept_exons * n_samples);
287 for (exon, &gene) in kept_exons.iter().zip(exon_gene.iter()) {
288 counts_kept.extend_from_slice(&input.counts[exon * n_samples..(exon + 1) * n_samples]);
289 let row = input.offset.row(*exon, n_samples);
290 for sample in 0..n_samples {
291 offset_new.push(
292 row.get(sample) + betabar[gene] * resolved.design[sample * n_coef + resolved.coef],
293 );
294 }
295 }
296
297 let dispersion = input.dispersion.subset(&kept_exons, n_samples);
298 let dispersion = match ql.and_then(|q| q.average_ql_dispersion) {
299 Some(average) => dispersion.map(|v| v / average),
300 None => dispersion,
301 };
302 let weights = input.weights.map(|w| w.subset(&kept_exons, n_samples));
303
304 let design0 = drop_column(&resolved.design, n_samples, n_coef, resolved.coef);
305 let fit0 = glm_fit(
306 &counts_kept,
307 n_kept_exons,
308 n_samples,
309 &design0,
310 n_coef - 1,
311 &dispersion,
312 &Recycled::full(offset_new, n_kept_exons, n_samples)?,
313 weights.as_ref(),
314 0.0,
317 )?;
318
319 let exon_df_test = fit0.df_residual as f64 - fit.df_residual as f64;
320 let exon_lr: Vec<f64> = (0..n_kept_exons)
321 .map(|i| fit0.deviance[i] - fit.deviance[kept_exons[i]])
322 .collect();
323
324 let mut gene_lr = vec![0.0_f64; n_kept_genes];
325 for (lr, &gene) in exon_lr.iter().zip(exon_gene.iter()) {
326 gene_lr[gene] += lr;
327 }
328 let gene_df_test: Vec<f64> = gene_n_exons
329 .iter()
330 .map(|n| *n as f64 * exon_df_test)
331 .collect();
332
333 let tests = match ql {
334 None => lrt_tests(&exon_lr, exon_df_test, &gene_lr, &gene_df_test)?,
335 Some(q) => ql_tests(
336 &exon_lr,
337 exon_df_test,
338 &gene_lr,
339 &gene_df_test,
340 &kept_exons,
341 &exon_gene,
342 n_kept_genes,
343 q,
344 ¶ms,
345 )?,
346 };
347
348 let gene_simes_p: Vec<f64> = gene_n_exons
349 .par_iter()
350 .zip(gene_start.par_iter())
351 .map(|(n, start)| simes(&tests.exon_p[*start..start + n]))
352 .collect();
353
354 let mut exon_log_fc = vec![0.0_f64; n_exons];
355 let mut exon_statistic = vec![0.0_f64; n_exons];
356 let mut exon_p_value = vec![1.0_f64; n_exons];
357 for (i, &exon) in kept_exons.iter().enumerate() {
358 exon_log_fc[exon] = resolved.beta[exon] - betabar[exon_gene[i]];
359 exon_statistic[exon] = tests.exon_statistic[i];
360 exon_p_value[exon] = tests.exon_p[i];
361 }
362
363 Ok(DiffSpliceResult {
364 exon_log_fc,
365 exon_statistic,
366 exon_p_value,
367 gene_id: kept_genes.iter().map(|g| groups.ids[*g]).collect(),
368 gene_simes_p,
369 gene_f_statistic: tests.gene_statistic,
370 gene_f_p_value: tests.gene_p,
371 gene_n_exons,
372 })
373}
374
375pub fn diff_splice_dge<T: EdgeFloat>(
400 dge: &DgeList<T>,
401 design: &[f64],
402 n_coef: usize,
403 gene_id: &[usize],
404 tested: &Tested,
405 params: Option<DiffSpliceParams>,
406) -> Result<DiffSpliceResult, EdgeErrors> {
407 let (dispersion, _) = dge.dispersion().ok_or_else(|| {
408 EdgeErrors::InvalidArgument("no dispersion has been estimated for this DgeList".to_string())
409 })?;
410 let offset = dge.offset()?;
411 let fit = glm_fit_dge(dge, design, n_coef, None)?;
412
413 let input = GlmTestInput {
414 counts: &dge.counts,
415 n_genes: dge.n_genes,
416 n_samples: dge.n_samples,
417 design,
418 n_coef,
419 dispersion: &dispersion,
420 offset: &offset,
421 weights: dge.weights.as_ref(),
422 log_cpm: None,
423 };
424 diff_splice(&input, &fit, gene_id, tested, None, params)
425}
426
427pub fn splice_variants<T: EdgeFloat>(
464 counts: &[T],
465 n_exons: usize,
466 n_samples: usize,
467 gene_id: &[usize],
468 group: &[usize],
469 dispersion: &[f64],
470) -> Result<DiffSpliceResult, EdgeErrors> {
471 if n_exons == 0 || n_samples == 0 {
472 return Err(EdgeErrors::EmptyCounts {
473 n_genes: n_exons,
474 n_samples,
475 });
476 }
477 if counts.len() != n_exons * n_samples {
478 return Err(EdgeErrors::LengthMismatch {
479 name: "counts",
480 expected: n_exons * n_samples,
481 got: counts.len(),
482 });
483 }
484 if gene_id.len() != n_exons {
485 return Err(EdgeErrors::LengthMismatch {
486 name: "gene_id",
487 expected: n_exons,
488 got: gene_id.len(),
489 });
490 }
491 if group.len() != n_samples {
492 return Err(EdgeErrors::LengthMismatch {
493 name: "group",
494 expected: n_samples,
495 got: group.len(),
496 });
497 }
498 if let Some(bad) = dispersion.iter().find(|d| !d.is_finite() || **d < 0.0) {
499 return Err(EdgeErrors::InvalidDispersion(*bad));
500 }
501
502 let mut levels: Vec<usize> = group.to_vec();
503 levels.sort_unstable();
504 levels.dedup();
505 let n_groups = levels.len();
506 if n_groups < 2 {
507 return Err(EdgeErrors::InvalidArgument(format!(
508 "splice_variants needs at least two groups; got {n_groups}"
509 )));
510 }
511 let group_level: Vec<usize> = group
512 .iter()
513 .map(|g| {
514 levels
515 .iter()
516 .position(|l| l == g)
517 .expect("level was taken from group")
518 })
519 .collect();
520
521 let nonzero: Vec<usize> = (0..n_exons)
523 .filter(|e| {
524 counts[e * n_samples..(e + 1) * n_samples]
525 .iter()
526 .any(|v| *v > T::zero())
527 })
528 .collect();
529 let kept_ids: Vec<usize> = nonzero.iter().map(|e| gene_id[*e]).collect();
530 let groups = group_by_gene(&kept_ids);
531 let n_genes = groups.ids.len();
532 if n_genes == 0 {
533 return Err(EdgeErrors::InvalidArgument(
534 "every exon is zero in every sample".to_string(),
535 ));
536 }
537 if dispersion.len() != 1 && dispersion.len() != n_genes {
538 return Err(EdgeErrors::LengthMismatch {
539 name: "dispersion",
540 expected: n_genes,
541 got: dispersion.len(),
542 });
543 }
544
545 let mut gene_statistic = vec![0.0_f64; n_genes];
546 let mut gene_p = vec![1.0_f64; n_genes];
547 let gene_n_exons: Vec<usize> = groups.members.iter().map(Vec::len).collect();
548
549 let mut sizes: Vec<usize> = gene_n_exons.clone();
550 sizes.sort_unstable();
551 sizes.dedup();
552 for n_exon in sizes {
553 if n_exon < 2 {
555 continue;
556 }
557 let batch: Vec<usize> = (0..n_genes)
558 .filter(|g| gene_n_exons[*g] == n_exon)
559 .collect();
560 let (statistic, p_value) = fit_exon_group_interaction(
561 counts,
562 n_samples,
563 &nonzero,
564 &groups,
565 &batch,
566 n_exon,
567 n_groups,
568 &group_level,
569 dispersion,
570 )?;
571 for (slot, &gene) in batch.iter().enumerate() {
572 gene_statistic[gene] = statistic[slot];
573 gene_p[gene] = p_value[slot];
574 }
575 }
576
577 Ok(DiffSpliceResult {
578 exon_log_fc: Vec::new(),
579 exon_statistic: Vec::new(),
580 exon_p_value: Vec::new(),
581 gene_id: groups.ids,
582 gene_simes_p: gene_p.clone(),
583 gene_f_statistic: gene_statistic,
584 gene_f_p_value: gene_p,
585 gene_n_exons,
586 })
587}
588
589struct GeneGroups {
595 ids: Vec<usize>,
597 members: Vec<Vec<usize>>,
599}
600
601fn group_by_gene(gene_id: &[usize]) -> GeneGroups {
613 let mut slot: FxHashMap<usize, usize> = FxHashMap::default();
614 let mut ids = Vec::new();
615 let mut members: Vec<Vec<usize>> = Vec::new();
616 for (row, id) in gene_id.iter().enumerate() {
617 let next = ids.len();
618 let g = *slot.entry(*id).or_insert_with(|| {
619 ids.push(*id);
620 members.push(Vec::new());
621 next
622 });
623 members[g].push(row);
624 }
625 GeneGroups { ids, members }
626}
627
628struct SpliceTests {
634 exon_statistic: Vec<f64>,
636 exon_p: Vec<f64>,
638 gene_statistic: Vec<f64>,
640 gene_p: Vec<f64>,
642}
643
644fn lrt_tests(
661 exon_lr: &[f64],
662 exon_df_test: f64,
663 gene_lr: &[f64],
664 gene_df_test: &[f64],
665) -> Result<SpliceTests, EdgeErrors> {
666 let exon_p = exon_lr
667 .par_iter()
668 .map(|lr| chisq_sf(lr.max(0.0), exon_df_test))
669 .collect::<Result<Vec<f64>, EdgeErrors>>()?;
670 let gene_p = gene_lr
671 .par_iter()
672 .zip(gene_df_test.par_iter())
673 .map(|(lr, df)| chisq_sf(lr.max(0.0), *df))
674 .collect::<Result<Vec<f64>, EdgeErrors>>()?;
675 Ok(SpliceTests {
676 exon_statistic: exon_lr.to_vec(),
677 exon_p,
678 gene_statistic: gene_lr.to_vec(),
679 gene_p,
680 })
681}
682
683#[allow(clippy::too_many_arguments)]
707fn ql_tests(
708 exon_lr: &[f64],
709 exon_df_test: f64,
710 gene_lr: &[f64],
711 gene_df_test: &[f64],
712 kept_exons: &[usize],
713 exon_gene: &[usize],
714 n_genes: usize,
715 ql: &DiffSpliceQl<'_>,
716 params: &DiffSpliceParams,
717) -> Result<SpliceTests, EdgeErrors> {
718 let mut gene_df_residual = vec![0.0_f64; n_genes];
719 let mut gene_deviance = vec![0.0_f64; n_genes];
720 for (exon, &gene) in kept_exons.iter().zip(exon_gene.iter()) {
721 gene_df_residual[gene] += ql.df_residual[*exon];
722 gene_deviance[gene] += ql.deviance[*exon];
723 }
724 let gene_s2: Vec<f64> = gene_deviance
725 .iter()
726 .zip(gene_df_residual.iter())
727 .map(|(dev, df)| if *df > 0.0 { dev / df } else { 0.0 })
728 .collect();
729
730 let robust = params.robust.unwrap_or(ql.df_prior.len() > 1);
731 let squeezed = squeeze_var(
732 &gene_s2,
733 &gene_df_residual,
734 None,
735 Some(SqueezeVarParams {
736 robust,
737 ..Default::default()
738 }),
739 )?;
740 let cap: f64 = gene_df_residual.iter().sum();
741 let gene_df_total: Vec<f64> = (0..n_genes)
742 .map(|g| {
743 let prior = squeezed.df_prior[g.min(squeezed.df_prior.len() - 1)];
744 (gene_df_residual[g] + prior).min(cap)
745 })
746 .collect();
747 let s2_post = &squeezed.var_post;
748
749 let exon_statistic: Vec<f64> = exon_lr
750 .iter()
751 .zip(exon_gene.iter())
752 .map(|(lr, g)| lr / exon_df_test / s2_post[*g])
753 .collect();
754 let gene_statistic: Vec<f64> = (0..n_genes)
755 .map(|g| gene_lr[g] / gene_df_test[g] / s2_post[g])
756 .collect();
757
758 let mut exon_p = exon_statistic
759 .par_iter()
760 .zip(exon_gene.par_iter())
761 .map(|(f, g)| f_sf(f.max(0.0), exon_df_test, gene_df_total[*g]))
762 .collect::<Result<Vec<f64>, EdgeErrors>>()?;
763 let gene_p = gene_statistic
764 .par_iter()
765 .enumerate()
766 .map(|(g, f)| f_sf(f.max(0.0), gene_df_test[g], gene_df_total[g]))
767 .collect::<Result<Vec<f64>, EdgeErrors>>()?;
768
769 if ql.legacy_zeros {
770 for (i, g) in exon_gene.iter().enumerate() {
771 if s2_post[*g] < 1.0 {
772 exon_p[i] = exon_p[i].max(chisq_sf(exon_lr[i].max(0.0), exon_df_test)?);
773 }
774 }
775 }
776
777 Ok(SpliceTests {
778 exon_statistic,
779 exon_p,
780 gene_statistic,
781 gene_p,
782 })
783}
784
785fn simes(p: &[f64]) -> f64 {
802 if p.is_empty() {
803 return 1.0;
804 }
805 let mut sorted = p.to_vec();
806 sorted.sort_by(f64::total_cmp);
807 let n = sorted.len() as f64;
808 sorted
809 .iter()
810 .enumerate()
811 .map(|(k, v)| v * n / (k + 1) as f64)
812 .fold(f64::INFINITY, f64::min)
813}
814
815#[allow(clippy::too_many_arguments)]
843fn fit_exon_group_interaction<T: EdgeFloat>(
844 counts: &[T],
845 n_samples: usize,
846 nonzero: &[usize],
847 groups: &GeneGroups,
848 batch: &[usize],
849 n_exon: usize,
850 n_groups: usize,
851 group_level: &[usize],
852 dispersion: &[f64],
853) -> Result<(Vec<f64>, Vec<f64>), EdgeErrors> {
854 let n_obs = n_exon * n_samples;
855 let n_coef = n_exon * n_groups;
856 let n_interaction = (n_exon - 1) * (n_groups - 1);
857 if n_coef >= n_obs {
858 return Err(EdgeErrors::InvalidArgument(format!(
859 "a gene with {n_exon} exons needs more than {n_groups} samples for the \
860 exon-by-group interaction; got {n_samples}"
861 )));
862 }
863
864 let mut design = vec![0.0_f64; n_obs * n_coef];
865 for exon in 0..n_exon {
866 for sample in 0..n_samples {
867 let row = &mut design[(exon * n_samples + sample) * n_coef..][..n_coef];
868 let level = group_level[sample];
869 row[0] = 1.0;
870 if exon > 0 {
871 row[exon] = 1.0;
872 }
873 if level > 0 {
874 row[n_exon - 1 + level] = 1.0;
875 }
876 if exon > 0 && level > 0 {
877 row[n_exon + n_groups - 2 + (level - 1) * (n_exon - 1) + exon] = 1.0;
878 }
879 }
880 }
881
882 let mut unrolled: Vec<T> = Vec::with_capacity(batch.len() * n_obs);
883 for &gene in batch {
884 for &member in &groups.members[gene] {
885 let exon = nonzero[member];
886 unrolled.extend_from_slice(&counts[exon * n_samples..(exon + 1) * n_samples]);
887 }
888 }
889 let disp = if dispersion.len() == 1 {
890 Recycled::scalar(dispersion[0])
891 } else {
892 Recycled::by_gene(batch.iter().map(|g| dispersion[*g]).collect())
893 };
894
895 let offset = Recycled::scalar(0.0);
896 let fit = glm_fit(
897 &unrolled,
898 batch.len(),
899 n_obs,
900 &design,
901 n_coef,
902 &disp,
903 &offset,
904 None,
905 0.0,
906 )?;
907 let input = GlmTestInput {
908 counts: &unrolled,
909 n_genes: batch.len(),
910 n_samples: n_obs,
911 design: &design,
912 n_coef,
913 dispersion: &disp,
914 offset: &offset,
915 weights: None,
916 log_cpm: None,
917 };
918 let tested = Tested::Coef((n_coef - n_interaction..n_coef).collect());
919 let test = glm_lrt(&input, &fit, &tested, None)?;
920 Ok((test.statistic, test.p_value))
921}
922
923struct ResolvedCoef {
929 design: Vec<f64>,
932 coef: usize,
934 beta: Vec<f64>,
936}
937
938fn resolve_tested<T: EdgeFloat>(
955 input: &GlmTestInput<'_, T>,
956 fit: &GlmFit,
957 tested: &Tested,
958) -> Result<ResolvedCoef, EdgeErrors> {
959 let n_coef = input.n_coef;
960 match tested {
961 Tested::Coef(coefs) => {
962 let coef = *coefs.first().ok_or_else(|| {
963 EdgeErrors::InvalidArgument("no coefficient was given to test".to_string())
964 })?;
965 if coef >= n_coef {
966 return Err(EdgeErrors::CoefOutOfRange {
967 index: coef,
968 n_coef,
969 });
970 }
971 let beta = (0..input.n_genes)
972 .map(|exon| fit.coefficients[exon * n_coef + coef])
973 .collect();
974 Ok(ResolvedCoef {
975 design: input.design.to_vec(),
976 coef,
977 beta,
978 })
979 }
980 Tested::Contrast {
981 values,
982 n_contrasts,
983 } => {
984 if *n_contrasts == 0 {
985 return Err(EdgeErrors::InvalidArgument(
986 "no contrast was given to test".to_string(),
987 ));
988 }
989 if values.len() != n_coef * n_contrasts {
990 return Err(EdgeErrors::LengthMismatch {
991 name: "contrast",
992 expected: n_coef * n_contrasts,
993 got: values.len(),
994 });
995 }
996 let first = &values[..n_coef];
997 let reform = contrast_as_coef(input.design, input.n_samples, n_coef, first, 1, true)?;
998 let beta = (0..input.n_genes)
999 .map(|exon| {
1000 let row = &fit.coefficients[exon * n_coef..(exon + 1) * n_coef];
1001 row.iter().zip(first.iter()).map(|(b, c)| b * c).sum()
1002 })
1003 .collect();
1004 Ok(ResolvedCoef {
1005 design: reform.design,
1006 coef: reform.coef[0],
1007 beta,
1008 })
1009 }
1010 }
1011}
1012
1013fn validate<T: EdgeFloat>(
1027 input: &GlmTestInput<'_, T>,
1028 fit: &GlmFit,
1029 gene_id: &[usize],
1030 ql: Option<&DiffSpliceQl<'_>>,
1031 params: &DiffSpliceParams,
1032) -> Result<(), EdgeErrors> {
1033 let (n_exons, n_samples, n_coef) = (input.n_genes, input.n_samples, input.n_coef);
1034 if n_exons == 0 || n_samples == 0 {
1035 return Err(EdgeErrors::EmptyCounts {
1036 n_genes: n_exons,
1037 n_samples,
1038 });
1039 }
1040 if n_coef < 2 {
1041 return Err(EdgeErrors::InvalidArgument(format!(
1042 "diff_splice needs a design with at least two columns, usually an intercept and the \
1043 comparison; got {n_coef}"
1044 )));
1045 }
1046 if input.counts.len() != n_exons * n_samples {
1047 return Err(EdgeErrors::LengthMismatch {
1048 name: "counts",
1049 expected: n_exons * n_samples,
1050 got: input.counts.len(),
1051 });
1052 }
1053 if input.design.len() != n_samples * n_coef {
1054 return Err(EdgeErrors::LengthMismatch {
1055 name: "design",
1056 expected: n_samples * n_coef,
1057 got: input.design.len(),
1058 });
1059 }
1060 if gene_id.len() != n_exons {
1061 return Err(EdgeErrors::LengthMismatch {
1062 name: "gene_id",
1063 expected: n_exons,
1064 got: gene_id.len(),
1065 });
1066 }
1067 if fit.coefficients.len() != n_exons * n_coef {
1068 return Err(EdgeErrors::LengthMismatch {
1069 name: "fit.coefficients",
1070 expected: n_exons * n_coef,
1071 got: fit.coefficients.len(),
1072 });
1073 }
1074 if fit.deviance.len() != n_exons {
1075 return Err(EdgeErrors::LengthMismatch {
1076 name: "fit.deviance",
1077 expected: n_exons,
1078 got: fit.deviance.len(),
1079 });
1080 }
1081 if params.prior_count < 0.0 || params.prior_count.is_nan() {
1082 return Err(EdgeErrors::InvalidArgument(format!(
1083 "prior_count must be non-negative, got {}",
1084 params.prior_count
1085 )));
1086 }
1087 input.dispersion.validate(n_exons, n_samples)?;
1088 input.offset.validate(n_exons, n_samples)?;
1089 if let Some(w) = input.weights {
1090 w.validate(n_exons, n_samples)?;
1091 }
1092 if let Some(q) = ql {
1093 if q.df_residual.len() != n_exons {
1094 return Err(EdgeErrors::LengthMismatch {
1095 name: "ql.df_residual",
1096 expected: n_exons,
1097 got: q.df_residual.len(),
1098 });
1099 }
1100 if q.deviance.len() != n_exons {
1101 return Err(EdgeErrors::LengthMismatch {
1102 name: "ql.deviance",
1103 expected: n_exons,
1104 got: q.deviance.len(),
1105 });
1106 }
1107 if q.df_prior.is_empty() {
1108 return Err(EdgeErrors::InvalidArgument(
1109 "ql.df_prior is empty".to_string(),
1110 ));
1111 }
1112 }
1113 Ok(())
1114}
1115
1116fn drop_column(matrix: &[f64], n_rows: usize, n_cols: usize, drop: usize) -> Vec<f64> {
1129 let mut out = Vec::with_capacity(n_rows * (n_cols - 1));
1130 for row in matrix.chunks_exact(n_cols) {
1131 for (col, v) in row.iter().enumerate() {
1132 if col != drop {
1133 out.push(*v);
1134 }
1135 }
1136 }
1137 out
1138}
1139
1140#[cfg(test)]
1145mod tests {
1146 use super::*;
1147 use approx::assert_relative_eq;
1148
1149 fn gate() -> (Vec<f64>, Vec<usize>, Vec<f64>) {
1151 let counts = vec![
1152 8.0, 16.0, 32.0, 64.0, 128.0, 256.0, 4.0, 4.0, 8.0, 8.0, 16.0, 16.0, 64.0, 64.0, 64.0, 128.0, 128.0, 128.0, 12.0, 20.0, 28.0, 40.0, 56.0, 72.0, 100.0, 110.0, 90.0, 220.0, 200.0, 240.0, 6.0, 6.0, 6.0, 12.0, 12.0, 12.0, ];
1159 let gene_id = vec![1, 1, 1, 2, 2, 2];
1160 let design = vec![
1161 1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, ];
1168 (counts, gene_id, design)
1169 }
1170
1171 fn mixed() -> (Vec<f64>, Vec<usize>, Vec<f64>) {
1174 let counts = vec![
1175 8.0, 16.0, 32.0, 64.0, 128.0, 256.0, 4.0, 4.0, 8.0, 8.0, 16.0, 16.0, 64.0, 64.0, 64.0, 128.0, 128.0, 128.0, 32.0, 16.0, 8.0, 4.0, 2.0, 1.0, 12.0, 20.0, 28.0, 40.0, 56.0, 72.0, 100.0, 110.0, 90.0, 220.0, 200.0, 240.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 6.0, 6.0, 6.0, 12.0, 12.0, 12.0, 256.0, 128.0, 192.0, 64.0, 96.0, 32.0, 8.0, 8.0, 8.0, 8.0, 8.0, 8.0, 20.0, 24.0, 16.0, 48.0, 40.0, 44.0, 512.0, 256.0, 128.0, 64.0, 32.0, 16.0, 96.0, 80.0, 64.0, 48.0, 32.0, 16.0, ];
1189 let gene_id = vec![1, 1, 1, 1, 2, 3, 3, 3, 4, 4, 5, 5, 5];
1190 let design = vec![
1191 1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, ];
1198 (counts, gene_id, design)
1199 }
1200
1201 fn offsets_from(counts: &[f64], n_exons: usize, n_samples: usize) -> Recycled<f64> {
1203 let mut lib = vec![0.0_f64; n_samples];
1204 for row in counts.chunks_exact(n_samples) {
1205 for (acc, v) in lib.iter_mut().zip(row.iter()) {
1206 *acc += v;
1207 }
1208 }
1209 assert_eq!(counts.len(), n_exons * n_samples);
1210 Recycled::by_sample(lib.iter().map(|v| v.ln()).collect())
1211 }
1212
1213 fn exon_fit(counts: &[f64], n_exons: usize, design: &[f64]) -> (GlmFit, Recycled<f64>) {
1215 let offset = offsets_from(counts, n_exons, 6);
1216 let fit = glm_fit(
1217 counts,
1218 n_exons,
1219 6,
1220 design,
1221 2,
1222 &Recycled::scalar(0.1),
1223 &offset,
1224 None,
1225 DEFAULT_PRIOR_COUNT,
1226 )
1227 .unwrap();
1228 (fit, offset)
1229 }
1230
1231 #[test]
1242 fn test_matches_edger_lrt_on_the_gate_fixture() {
1243 let (counts, gene_id, design) = gate();
1244 let (fit, offset) = exon_fit(&counts, 6, &design);
1245 let input = GlmTestInput {
1246 counts: &counts,
1247 n_genes: 6,
1248 n_samples: 6,
1249 design: &design,
1250 n_coef: 2,
1251 dispersion: &Recycled::scalar(0.1),
1252 offset: &offset,
1253 weights: None,
1254 log_cpm: None,
1255 };
1256 let out = diff_splice(&input, &fit, &gene_id, &Tested::Coef(vec![1]), None, None).unwrap();
1257
1258 let expected_log_fc = [
1260 0.855_174_395_193_151_3,
1261 -0.261_843_130_654_270_2,
1262 -0.461_416_768_452_753_1,
1263 0.192_177_299_143_341_5,
1264 -0.035_676_657_669_298_6,
1265 -0.128_554_161_416_470_9,
1266 ];
1267 let expected_lr = [
1269 8.436_569_219_765_81,
1270 0.440_940_993_487_607_8,
1271 2.839_571_646_837_012_7,
1272 0.413_720_073_978_469,
1273 0.017_921_690_140_715_3,
1274 0.115_727_330_674_815_6,
1275 ];
1276 let expected_p = [
1278 0.003_677_493_961_314_914_4,
1279 0.506_668_627_239_259_9,
1280 0.091_968_728_075_895_53,
1281 0.520_087_356_281_412_4,
1282 0.893_503_857_408_516_5,
1283 0.733_715_557_310_116_2,
1284 ];
1285 for i in 0..6 {
1286 assert_relative_eq!(out.exon_log_fc[i], expected_log_fc[i], max_relative = 1e-9);
1287 assert_relative_eq!(out.exon_statistic[i], expected_lr[i], max_relative = 1e-9);
1288 assert_relative_eq!(out.exon_p_value[i], expected_p[i], max_relative = 1e-9);
1289 }
1290
1291 assert_eq!(out.gene_id, vec![1, 2]);
1292 assert_eq!(out.gene_n_exons, vec![3, 3]);
1293 assert_relative_eq!(
1295 out.gene_f_statistic[0],
1296 11.717_081_860_090_431,
1297 max_relative = 1e-9
1298 );
1299 assert_relative_eq!(
1300 out.gene_f_statistic[1],
1301 0.547_369_094_793_999_9,
1302 max_relative = 1e-9
1303 );
1304 assert_relative_eq!(
1305 out.gene_f_p_value[0],
1306 0.008_417_910_555_119_631,
1307 max_relative = 1e-9
1308 );
1309 assert_relative_eq!(
1310 out.gene_f_p_value[1],
1311 0.908_367_971_898_078,
1312 max_relative = 1e-9
1313 );
1314 assert_relative_eq!(
1315 out.gene_simes_p[0],
1316 0.011_032_481_883_944_744,
1317 max_relative = 1e-9
1318 );
1319 assert_relative_eq!(
1320 out.gene_simes_p[1],
1321 0.893_503_857_408_516_5,
1322 max_relative = 1e-9
1323 );
1324 }
1325
1326 #[test]
1339 fn test_matches_edger_lrt_with_mixed_exon_counts() {
1340 let (counts, gene_id, design) = mixed();
1341 let (fit, offset) = exon_fit(&counts, 13, &design);
1342 let input = GlmTestInput {
1343 counts: &counts,
1344 n_genes: 13,
1345 n_samples: 6,
1346 design: &design,
1347 n_coef: 2,
1348 dispersion: &Recycled::scalar(0.1),
1349 offset: &offset,
1350 weights: None,
1351 log_cpm: None,
1352 };
1353 let out = diff_splice(&input, &fit, &gene_id, &Tested::Coef(vec![1]), None, None).unwrap();
1354
1355 assert_eq!(out.gene_id, vec![1, 3, 4, 5]);
1357 assert_eq!(out.gene_n_exons, vec![4, 3, 2, 3]);
1358 assert_eq!(out.exon_log_fc[4], 0.0);
1359 assert_eq!(out.exon_statistic[4], 0.0);
1360 assert_eq!(out.exon_p_value[4], 1.0);
1361
1362 let expected_log_fc = [
1364 0.986_261_373_785_026_8,
1365 -0.092_813_816_117_244_93,
1366 -0.299_573_258_165_403_7,
1367 -2.932_713_900_334_851_7,
1368 0.002_284_551_340_094_198_6,
1369 -0.822_162_460_092_049_4,
1370 -0.078_425_809_463_148_25,
1371 -0.072_451_134_208_873_84,
1372 0.991_832_771_986_186_4,
1373 1.951_633_344_827_402_4,
1374 -0.785_350_396_075_128_2,
1375 0.286_727_193_468_867,
1376 ];
1377 let expected_lr = [
1379 11.203_352_118_219_136,
1380 0.039_045_494_218_443_72,
1381 1.189_460_502_071_776_1,
1382 49.591_886_657_154_48,
1383 0.000_124_411_095_185_239_52,
1384 0.0,
1385 0.029_691_603_087_216_08,
1386 0.073_511_269_031_998_34,
1387 6.456_369_674_304_806,
1388 39.738_828_764_140_41,
1389 7.975_958_887_749_272,
1390 0.991_332_488_893_542_4,
1391 ];
1392 let expected_p = [
1394 0.000_816_497_019_046_571_5,
1395 0.843_358_561_026_85,
1396 0.275_438_406_419_161,
1397 1.892_949_108_888_925e-12,
1398 0.991_100_602_344_373_6,
1399 1.0,
1400 0.863_191_845_368_248_2,
1401 0.786_291_343_598_799_3,
1402 0.011_055_528_859_942_796,
1403 2.902_963_175_866_408e-10,
1404 0.004_740_264_033_567_157,
1405 0.319_416_920_511_383_43,
1406 ];
1407 let kept: Vec<usize> = (0..13).filter(|i| *i != 4).collect();
1408 for (slot, &exon) in kept.iter().enumerate() {
1409 assert_relative_eq!(
1410 out.exon_log_fc[exon],
1411 expected_log_fc[slot],
1412 max_relative = 1e-9,
1413 epsilon = 1e-12
1414 );
1415 assert_relative_eq!(
1416 out.exon_statistic[exon],
1417 expected_lr[slot],
1418 max_relative = 1e-9,
1419 epsilon = 1e-12
1420 );
1421 assert_relative_eq!(
1422 out.exon_p_value[exon],
1423 expected_p[slot],
1424 max_relative = 1e-9
1425 );
1426 }
1427
1428 let expected_gene_lr = [
1430 62.023_744_771_663_84,
1431 0.029_816_014_182_401_318,
1432 6.529_880_943_336_805,
1433 48.706_120_140_783_22,
1434 ];
1435 let expected_gene_p = [
1437 1.088_995_352_370_079_7e-12,
1438 0.998_642_900_101_906_2,
1439 0.038_199_208_951_176_705,
1440 1.506_569_551_839_668e-10,
1441 ];
1442 let expected_simes = [
1444 7.571_796_435_555_7e-12,
1445 1.0,
1446 0.022_111_057_719_885_592,
1447 8.708_889_527_599_224e-10,
1448 ];
1449 for g in 0..4 {
1450 assert_relative_eq!(
1451 out.gene_f_statistic[g],
1452 expected_gene_lr[g],
1453 max_relative = 1e-9
1454 );
1455 assert_relative_eq!(
1456 out.gene_f_p_value[g],
1457 expected_gene_p[g],
1458 max_relative = 1e-9
1459 );
1460 assert_relative_eq!(out.gene_simes_p[g], expected_simes[g], max_relative = 1e-9);
1461 }
1462 }
1463
1464 #[test]
1473 fn test_matches_edger_quasi_likelihood_f_tests() {
1474 let (counts, gene_id, design) = mixed();
1475 let offset = offsets_from(&counts, 13, 6);
1476
1477 let coefficients = vec![
1479 -3.716_649_123_803_044,
1480 2.053_338_438_767_273_3,
1481 -5.004_790_281_784_431,
1482 0.958_599_359_856_350_5,
1483 -2.521_384_664_641_309,
1484 0.736_712_308_560_846_2,
1485 -3.818_356_521_647_034_5,
1486 -1.922_703_330_975_371_8,
1487 -3.662_138_872_007_650_6,
1488 1.039_972_007_661_760_2,
1489 -2.075_371_926_749_308,
1490 0.829_195_942_258_679_3,
1491 -8.762_906_779_045_517,
1492 0.0,
1493 -4.898_543_552_754_313,
1494 0.753_138_951_659_528_8,
1495 -1.453_993_800_252_147,
1496 -1.010_073_952_288_818_5,
1497 -4.613_303_129_296_377,
1498 0.066_946_339_544_628_59,
1499 -3.698_761_496_409_288_5,
1500 0.848_147_053_835_922_5,
1501 -1.086_383_432_125_770_6,
1502 -1.908_243_414_438_834,
1503 -2.323_859_444_983_016,
1504 -0.829_704_616_425_026_7,
1505 ];
1506 let deviance = vec![
1508 32.934_761_083_769_92,
1509 5.474_240_344_388_582_5,
1510 3.263_241_513_475_087_6,
1511 5.470_865_795_828_582_5,
1512 11.857_671_991_595_268,
1513 3.339_511_158_808_827_8,
1514 0.0,
1515 0.950_343_670_388_99,
1516 15.350_643_930_570_67,
1517 1.123_733_784_713_515_8,
1518 2.832_718_144_252_497_4,
1519 24.339_069_453_856_304,
1520 10.860_457_084_684_125,
1521 ];
1522 let df_residual_adj = [
1524 3.949_399_184_578_337,
1525 4.429_910_119_524_08,
1526 3.995_636_528_129_935_5,
1527 6.215_119_099_483_442,
1528 3.947_839_985_398_613_5,
1529 3.999_088_419_001_173,
1530 0.0,
1531 4.298_601_889_799_675,
1532 3.996_644_613_146_141,
1533 4.160_749_445_488_15,
1534 3.941_931_767_768_654,
1535 3.987_982_672_430_048,
1536 3.979_648_238_150_599,
1537 ];
1538 let deviance_adj = [
1540 31.681_571_682_117_89,
1541 5.716_898_207_948_569,
1542 3.199_241_360_732_763_4,
1543 7.325_504_268_111_918,
1544 11.299_290_927_664_186,
1545 3.281_195_035_994_136,
1546 0.0,
1547 0.943_989_863_873_622_4,
1548 15.053_574_377_441_105,
1549 1.055_510_220_953_673_5,
1550 2.703_138_273_427_556_6,
1551 23.743_938_823_095_38,
1552 10.517_433_786_052_315,
1553 ];
1554 let df_prior = [50.431_634_778_955_26];
1556 let average_ql_dispersion = 2.517_465_302_235_576;
1557
1558 let fit = GlmFit {
1559 coefficients,
1560 unshrunk_coefficients: None,
1561 fitted: Vec::new(),
1562 deviance,
1563 df_residual: 4,
1564 method: crate::glm::fit::FitMethod::OneWay,
1565 };
1566 let ql = DiffSpliceQl {
1567 df_prior: &df_prior,
1568 df_residual: &df_residual_adj,
1569 deviance: &deviance_adj,
1570 legacy_zeros: false,
1571 average_ql_dispersion: Some(average_ql_dispersion),
1572 };
1573 let input = GlmTestInput {
1574 counts: &counts,
1575 n_genes: 13,
1576 n_samples: 6,
1577 design: &design,
1578 n_coef: 2,
1579 dispersion: &Recycled::scalar(0.1),
1581 offset: &offset,
1582 weights: None,
1583 log_cpm: None,
1584 };
1585 let out = diff_splice(
1586 &input,
1587 &fit,
1588 &gene_id,
1589 &Tested::Coef(vec![1]),
1590 Some(&ql),
1591 None,
1592 )
1593 .unwrap();
1594
1595 let expected_f = [
1597 10.923_983_891_986_724,
1598 0.012_553_476_455_043_623,
1599 1.147_597_224_575_699_3,
1600 33.801_175_852_216_82,
1601 0.000_892_201_161_999_136_9,
1602 0.0,
1603 0.013_982_948_088_757_496,
1604 0.080_179_046_492_054_87,
1605 4.186_641_136_888_011,
1606 35.647_046_431_592_5,
1607 8.176_517_864_533_706,
1608 0.884_633_969_063_336_8,
1609 ];
1610 let expected_p = [
1612 0.001_823_606_690_634_109_5,
1613 0.911_267_482_829_818_5,
1614 0.289_528_269_680_133_8,
1615 5.158_549_168_775_326e-7,
1616 0.976_297_542_709_692_6,
1617 1.0,
1618 0.906_374_380_415_774_7,
1619 0.778_299_597_202_179_1,
1620 0.046_366_642_339_368_824,
1621 2.992_427_861_639_236_6e-7,
1622 0.006_311_974_788_731_110_5,
1623 0.351_749_366_873_619_46,
1624 ];
1625 let expected_log_fc = [
1627 1.024_069_012_156_488_6,
1628 -0.070_670_066_754_434_21,
1629 -0.292_557_118_049_938_45,
1630 -2.951_972_757_586_156_5,
1631 0.007_033_482_166_629_912,
1632 -0.822_162_460_092_049_4,
1633 -0.069_023_508_432_520_59,
1634 -0.074_627_228_010_850_64,
1635 1.002_393_063_822_596_4,
1636 1.960_274_530_132_001,
1637 -0.796_115_938_142_755_4,
1638 0.282_422_859_871_052,
1639 ];
1640 let kept: Vec<usize> = (0..13).filter(|i| *i != 4).collect();
1641 for (slot, &exon) in kept.iter().enumerate() {
1642 assert_relative_eq!(
1643 out.exon_log_fc[exon],
1644 expected_log_fc[slot],
1645 max_relative = 1e-9,
1646 epsilon = 1e-12
1647 );
1648 assert_relative_eq!(
1649 out.exon_statistic[exon],
1650 expected_f[slot],
1651 max_relative = 1e-9,
1652 epsilon = 1e-12
1653 );
1654 assert_relative_eq!(
1655 out.exon_p_value[exon],
1656 expected_p[slot],
1657 max_relative = 1e-9
1658 );
1659 }
1660
1661 let expected_gene_f = [
1663 11.471_327_611_308_572,
1664 0.004_958_383_083_585_545,
1665 2.133_410_091_690_033,
1666 14.902_732_755_063_182,
1667 ];
1668 let expected_gene_p = [
1669 1.412_135_847_820_031_8e-6,
1670 0.999_512_145_784_596_1,
1671 0.129_771_944_348_671_37,
1672 5.987_503_169_646_61e-7,
1673 ];
1674 let expected_simes = [
1675 2.063_419_667_510_130_6e-6,
1676 1.0,
1677 0.092_733_284_678_737_65,
1678 8.977_283_584_917_71e-7,
1679 ];
1680 for g in 0..4 {
1681 assert_relative_eq!(
1682 out.gene_f_statistic[g],
1683 expected_gene_f[g],
1684 max_relative = 1e-9
1685 );
1686 assert_relative_eq!(
1687 out.gene_f_p_value[g],
1688 expected_gene_p[g],
1689 max_relative = 1e-9
1690 );
1691 assert_relative_eq!(out.gene_simes_p[g], expected_simes[g], max_relative = 1e-9);
1692 }
1693 }
1694
1695 #[test]
1697 fn test_contrast_reproduces_the_coefficient_path() {
1698 let (counts, gene_id, design) = gate();
1699 let (fit, offset) = exon_fit(&counts, 6, &design);
1700 let input = GlmTestInput {
1701 counts: &counts,
1702 n_genes: 6,
1703 n_samples: 6,
1704 design: &design,
1705 n_coef: 2,
1706 dispersion: &Recycled::scalar(0.1),
1707 offset: &offset,
1708 weights: None,
1709 log_cpm: None,
1710 };
1711 let by_coef =
1712 diff_splice(&input, &fit, &gene_id, &Tested::Coef(vec![1]), None, None).unwrap();
1713 let by_contrast = diff_splice(
1714 &input,
1715 &fit,
1716 &gene_id,
1717 &Tested::Contrast {
1718 values: vec![0.0, 1.0],
1719 n_contrasts: 1,
1720 },
1721 None,
1722 None,
1723 )
1724 .unwrap();
1725
1726 for i in 0..6 {
1727 assert_relative_eq!(
1728 by_contrast.exon_p_value[i],
1729 by_coef.exon_p_value[i],
1730 max_relative = 1e-9
1731 );
1732 assert_relative_eq!(
1733 by_contrast.exon_log_fc[i],
1734 by_coef.exon_log_fc[i],
1735 max_relative = 1e-9,
1736 epsilon = 1e-12
1737 );
1738 }
1739 }
1740
1741 #[test]
1743 fn test_gene_grouping_is_independent_of_row_order() {
1744 let (counts, gene_id, design) = gate();
1745 let (fit, offset) = exon_fit(&counts, 6, &design);
1746 let input = GlmTestInput {
1747 counts: &counts,
1748 n_genes: 6,
1749 n_samples: 6,
1750 design: &design,
1751 n_coef: 2,
1752 dispersion: &Recycled::scalar(0.1),
1753 offset: &offset,
1754 weights: None,
1755 log_cpm: None,
1756 };
1757 let sorted =
1758 diff_splice(&input, &fit, &gene_id, &Tested::Coef(vec![1]), None, None).unwrap();
1759
1760 let order = [0_usize, 3, 1, 4, 2, 5];
1762 let shuffled_counts: Vec<f64> = order
1763 .iter()
1764 .flat_map(|r| counts[r * 6..(r + 1) * 6].iter().copied())
1765 .collect();
1766 let shuffled_ids: Vec<usize> = order.iter().map(|r| gene_id[*r]).collect();
1767 let (shuffled_fit, shuffled_offset) = exon_fit(&shuffled_counts, 6, &design);
1768 let shuffled_input = GlmTestInput {
1769 counts: &shuffled_counts,
1770 n_genes: 6,
1771 n_samples: 6,
1772 design: &design,
1773 n_coef: 2,
1774 dispersion: &Recycled::scalar(0.1),
1775 offset: &shuffled_offset,
1776 weights: None,
1777 log_cpm: None,
1778 };
1779 let shuffled = diff_splice(
1780 &shuffled_input,
1781 &shuffled_fit,
1782 &shuffled_ids,
1783 &Tested::Coef(vec![1]),
1784 None,
1785 None,
1786 )
1787 .unwrap();
1788
1789 assert_eq!(shuffled.gene_id, sorted.gene_id);
1790 for g in 0..2 {
1791 assert_relative_eq!(
1792 shuffled.gene_simes_p[g],
1793 sorted.gene_simes_p[g],
1794 max_relative = 1e-9
1795 );
1796 }
1797 for (slot, &row) in order.iter().enumerate() {
1798 assert_relative_eq!(
1799 shuffled.exon_p_value[slot],
1800 sorted.exon_p_value[row],
1801 max_relative = 1e-9
1802 );
1803 }
1804 }
1805
1806 #[test]
1808 fn test_dge_wrapper_matches_the_explicit_call() {
1809 let (counts, gene_id, design) = gate();
1810 let (fit, offset) = exon_fit(&counts, 6, &design);
1811 let input = GlmTestInput {
1812 counts: &counts,
1813 n_genes: 6,
1814 n_samples: 6,
1815 design: &design,
1816 n_coef: 2,
1817 dispersion: &Recycled::scalar(0.1),
1818 offset: &offset,
1819 weights: None,
1820 log_cpm: None,
1821 };
1822 let direct =
1823 diff_splice(&input, &fit, &gene_id, &Tested::Coef(vec![1]), None, None).unwrap();
1824
1825 let mut dge = DgeList::new(counts.clone(), 6, 6, None).unwrap();
1826 dge.common_dispersion = Some(0.1);
1827 let wrapped =
1828 diff_splice_dge(&dge, &design, 2, &gene_id, &Tested::Coef(vec![1]), None).unwrap();
1829
1830 for i in 0..6 {
1831 assert_relative_eq!(
1832 wrapped.exon_p_value[i],
1833 direct.exon_p_value[i],
1834 max_relative = 1e-9
1835 );
1836 assert_relative_eq!(
1837 wrapped.exon_log_fc[i],
1838 direct.exon_log_fc[i],
1839 max_relative = 1e-9
1840 );
1841 }
1842 }
1843
1844 #[test]
1850 fn test_matches_edger_splice_variants() {
1851 let (counts, gene_id, _) = mixed();
1852 let group = vec![1, 1, 1, 2, 2, 2];
1853 let out = splice_variants(&counts, 13, 6, &gene_id, &group, &[0.1]).unwrap();
1854
1855 assert_eq!(out.gene_id, vec![1, 2, 3, 4, 5]);
1856 assert_eq!(out.gene_n_exons, vec![4, 1, 2, 2, 3]);
1858 assert!(out.exon_p_value.is_empty());
1859
1860 let expected_lr = [
1862 64.964_266_458_512_41,
1863 0.0,
1864 0.040_945_652_052_297_28,
1865 5.370_218_472_941_118,
1866 47.582_028_134_328_2,
1867 ];
1868 let expected_p = [
1870 5.105_088_859_960_058e-14,
1871 1.0,
1872 0.839_642_863_748_024_1,
1873 0.020_483_411_823_205_97,
1874 4.652_580_238_805_525e-11,
1875 ];
1876 for g in 0..5 {
1881 assert_relative_eq!(
1882 out.gene_f_statistic[g],
1883 expected_lr[g],
1884 max_relative = 1e-6,
1885 epsilon = 1e-12
1886 );
1887 assert_relative_eq!(out.gene_f_p_value[g], expected_p[g], max_relative = 1e-6);
1888 assert_relative_eq!(out.gene_simes_p[g], expected_p[g], max_relative = 1e-6);
1889 }
1890 }
1891
1892 #[test]
1894 fn test_splice_variants_accepts_a_genewise_dispersion() {
1895 let (counts, gene_id, _) = mixed();
1896 let group = vec![1, 1, 1, 2, 2, 2];
1897 let common = splice_variants(&counts, 13, 6, &gene_id, &group, &[0.1]).unwrap();
1898 let genewise =
1899 splice_variants(&counts, 13, 6, &gene_id, &group, &[0.1, 0.1, 0.1, 0.5, 0.1]).unwrap();
1900
1901 assert_relative_eq!(
1902 genewise.gene_f_statistic[0],
1903 common.gene_f_statistic[0],
1904 max_relative = 1e-12
1905 );
1906 assert!(genewise.gene_f_statistic[3] < common.gene_f_statistic[3]);
1907 }
1908
1909 #[test]
1911 fn test_simes_bounds() {
1912 assert_relative_eq!(simes(&[0.4]), 0.4, max_relative = 1e-12);
1913 assert_relative_eq!(simes(&[0.5, 0.73, 0.89]), 0.89, max_relative = 1e-12);
1914 assert_relative_eq!(simes(&[0.01, 0.5, 0.9]), 0.03, max_relative = 1e-12);
1915 assert_eq!(simes(&[]), 1.0);
1916 }
1917
1918 #[test]
1920 fn test_rejects_a_single_column_design() {
1921 let (counts, gene_id, _) = gate();
1922 let design = vec![1.0; 6];
1923 let offset = offsets_from(&counts, 6, 6);
1924 let fit = glm_fit(
1925 &counts,
1926 6,
1927 6,
1928 &design,
1929 1,
1930 &Recycled::scalar(0.1),
1931 &offset,
1932 None,
1933 0.0,
1934 )
1935 .unwrap();
1936 let input = GlmTestInput {
1937 counts: &counts,
1938 n_genes: 6,
1939 n_samples: 6,
1940 design: &design,
1941 n_coef: 1,
1942 dispersion: &Recycled::scalar(0.1),
1943 offset: &offset,
1944 weights: None,
1945 log_cpm: None,
1946 };
1947 let err =
1948 diff_splice(&input, &fit, &gene_id, &Tested::Coef(vec![0]), None, None).unwrap_err();
1949 assert!(matches!(err, EdgeErrors::InvalidArgument(_)));
1950 }
1951
1952 #[test]
1953 fn test_rejects_a_gene_id_of_the_wrong_length() {
1954 let (counts, _, design) = gate();
1955 let (fit, offset) = exon_fit(&counts, 6, &design);
1956 let input = GlmTestInput {
1957 counts: &counts,
1958 n_genes: 6,
1959 n_samples: 6,
1960 design: &design,
1961 n_coef: 2,
1962 dispersion: &Recycled::scalar(0.1),
1963 offset: &offset,
1964 weights: None,
1965 log_cpm: None,
1966 };
1967 let err =
1968 diff_splice(&input, &fit, &[1, 1, 2], &Tested::Coef(vec![1]), None, None).unwrap_err();
1969 assert!(matches!(
1970 err,
1971 EdgeErrors::LengthMismatch {
1972 name: "gene_id",
1973 expected: 6,
1974 got: 3
1975 }
1976 ));
1977 }
1978
1979 #[test]
1980 fn test_rejects_a_coefficient_out_of_range() {
1981 let (counts, gene_id, design) = gate();
1982 let (fit, offset) = exon_fit(&counts, 6, &design);
1983 let input = GlmTestInput {
1984 counts: &counts,
1985 n_genes: 6,
1986 n_samples: 6,
1987 design: &design,
1988 n_coef: 2,
1989 dispersion: &Recycled::scalar(0.1),
1990 offset: &offset,
1991 weights: None,
1992 log_cpm: None,
1993 };
1994 let err =
1995 diff_splice(&input, &fit, &gene_id, &Tested::Coef(vec![7]), None, None).unwrap_err();
1996 assert!(matches!(
1997 err,
1998 EdgeErrors::CoefOutOfRange {
1999 index: 7,
2000 n_coef: 2
2001 }
2002 ));
2003 }
2004
2005 #[test]
2006 fn test_rejects_an_empty_coefficient_list() {
2007 let (counts, gene_id, design) = gate();
2008 let (fit, offset) = exon_fit(&counts, 6, &design);
2009 let input = GlmTestInput {
2010 counts: &counts,
2011 n_genes: 6,
2012 n_samples: 6,
2013 design: &design,
2014 n_coef: 2,
2015 dispersion: &Recycled::scalar(0.1),
2016 offset: &offset,
2017 weights: None,
2018 log_cpm: None,
2019 };
2020 let err = diff_splice(
2021 &input,
2022 &fit,
2023 &gene_id,
2024 &Tested::Coef(Vec::new()),
2025 None,
2026 None,
2027 )
2028 .unwrap_err();
2029 assert!(matches!(err, EdgeErrors::InvalidArgument(_)));
2030 }
2031
2032 #[test]
2033 fn test_rejects_a_contrast_of_the_wrong_length() {
2034 let (counts, gene_id, design) = gate();
2035 let (fit, offset) = exon_fit(&counts, 6, &design);
2036 let input = GlmTestInput {
2037 counts: &counts,
2038 n_genes: 6,
2039 n_samples: 6,
2040 design: &design,
2041 n_coef: 2,
2042 dispersion: &Recycled::scalar(0.1),
2043 offset: &offset,
2044 weights: None,
2045 log_cpm: None,
2046 };
2047 let err = diff_splice(
2048 &input,
2049 &fit,
2050 &gene_id,
2051 &Tested::Contrast {
2052 values: vec![1.0, 0.0, 0.0],
2053 n_contrasts: 1,
2054 },
2055 None,
2056 None,
2057 )
2058 .unwrap_err();
2059 assert!(matches!(
2060 err,
2061 EdgeErrors::LengthMismatch {
2062 name: "contrast",
2063 ..
2064 }
2065 ));
2066 }
2067
2068 #[test]
2070 fn test_rejects_when_no_gene_has_more_than_one_exon() {
2071 let (counts, _, design) = gate();
2072 let (fit, offset) = exon_fit(&counts, 6, &design);
2073 let input = GlmTestInput {
2074 counts: &counts,
2075 n_genes: 6,
2076 n_samples: 6,
2077 design: &design,
2078 n_coef: 2,
2079 dispersion: &Recycled::scalar(0.1),
2080 offset: &offset,
2081 weights: None,
2082 log_cpm: None,
2083 };
2084 let err = diff_splice(
2085 &input,
2086 &fit,
2087 &[1, 2, 3, 4, 5, 6],
2088 &Tested::Coef(vec![1]),
2089 None,
2090 None,
2091 )
2092 .unwrap_err();
2093 assert!(matches!(err, EdgeErrors::InvalidArgument(_)));
2094 }
2095
2096 #[test]
2097 fn test_rejects_a_negative_prior_count() {
2098 let (counts, gene_id, design) = gate();
2099 let (fit, offset) = exon_fit(&counts, 6, &design);
2100 let input = GlmTestInput {
2101 counts: &counts,
2102 n_genes: 6,
2103 n_samples: 6,
2104 design: &design,
2105 n_coef: 2,
2106 dispersion: &Recycled::scalar(0.1),
2107 offset: &offset,
2108 weights: None,
2109 log_cpm: None,
2110 };
2111 let err = diff_splice(
2112 &input,
2113 &fit,
2114 &gene_id,
2115 &Tested::Coef(vec![1]),
2116 None,
2117 Some(DiffSpliceParams::new(-1.0, None)),
2118 )
2119 .unwrap_err();
2120 assert!(matches!(err, EdgeErrors::InvalidArgument(_)));
2121 }
2122
2123 #[test]
2124 fn test_rejects_a_quasi_likelihood_summary_of_the_wrong_length() {
2125 let (counts, gene_id, design) = gate();
2126 let (fit, offset) = exon_fit(&counts, 6, &design);
2127 let input = GlmTestInput {
2128 counts: &counts,
2129 n_genes: 6,
2130 n_samples: 6,
2131 design: &design,
2132 n_coef: 2,
2133 dispersion: &Recycled::scalar(0.1),
2134 offset: &offset,
2135 weights: None,
2136 log_cpm: None,
2137 };
2138 let ql = DiffSpliceQl {
2139 df_prior: &[5.0],
2140 df_residual: &[4.0, 4.0],
2141 deviance: &[1.0; 6],
2142 legacy_zeros: false,
2143 average_ql_dispersion: None,
2144 };
2145 let err = diff_splice(
2146 &input,
2147 &fit,
2148 &gene_id,
2149 &Tested::Coef(vec![1]),
2150 Some(&ql),
2151 None,
2152 )
2153 .unwrap_err();
2154 assert!(matches!(
2155 err,
2156 EdgeErrors::LengthMismatch {
2157 name: "ql.df_residual",
2158 ..
2159 }
2160 ));
2161 }
2162
2163 #[test]
2164 fn test_rejects_a_fit_of_the_wrong_length() {
2165 let (counts, gene_id, design) = gate();
2166 let (mut fit, offset) = exon_fit(&counts, 6, &design);
2167 fit.deviance.pop();
2168 let input = GlmTestInput {
2169 counts: &counts,
2170 n_genes: 6,
2171 n_samples: 6,
2172 design: &design,
2173 n_coef: 2,
2174 dispersion: &Recycled::scalar(0.1),
2175 offset: &offset,
2176 weights: None,
2177 log_cpm: None,
2178 };
2179 let err =
2180 diff_splice(&input, &fit, &gene_id, &Tested::Coef(vec![1]), None, None).unwrap_err();
2181 assert!(matches!(
2182 err,
2183 EdgeErrors::LengthMismatch {
2184 name: "fit.deviance",
2185 ..
2186 }
2187 ));
2188 }
2189
2190 #[test]
2191 fn test_splice_variants_rejects_a_single_group() {
2192 let (counts, gene_id, _) = mixed();
2193 let err = splice_variants(&counts, 13, 6, &gene_id, &[1; 6], &[0.1]).unwrap_err();
2194 assert!(matches!(err, EdgeErrors::InvalidArgument(_)));
2195 }
2196
2197 #[test]
2198 fn test_splice_variants_rejects_a_bad_dispersion_length() {
2199 let (counts, gene_id, _) = mixed();
2200 let group = vec![1, 1, 1, 2, 2, 2];
2201 let err = splice_variants(&counts, 13, 6, &gene_id, &group, &[0.1, 0.2]).unwrap_err();
2202 assert!(matches!(
2203 err,
2204 EdgeErrors::LengthMismatch {
2205 name: "dispersion",
2206 ..
2207 }
2208 ));
2209 }
2210
2211 #[test]
2212 fn test_splice_variants_rejects_a_negative_dispersion() {
2213 let (counts, gene_id, _) = mixed();
2214 let group = vec![1, 1, 1, 2, 2, 2];
2215 let err = splice_variants(&counts, 13, 6, &gene_id, &group, &[-0.1]).unwrap_err();
2216 assert!(matches!(err, EdgeErrors::InvalidDispersion(_)));
2217 }
2218
2219 #[test]
2220 fn test_splice_variants_rejects_an_all_zero_matrix() {
2221 let err = splice_variants(&[0.0_f64; 12], 2, 6, &[1, 1], &[1, 1, 1, 2, 2, 2], &[0.1])
2222 .unwrap_err();
2223 assert!(matches!(err, EdgeErrors::InvalidArgument(_)));
2224 }
2225
2226 #[test]
2227 fn test_splice_variants_rejects_a_group_of_the_wrong_length() {
2228 let (counts, gene_id, _) = mixed();
2229 let err = splice_variants(&counts, 13, 6, &gene_id, &[1, 2], &[0.1]).unwrap_err();
2230 assert!(matches!(
2231 err,
2232 EdgeErrors::LengthMismatch { name: "group", .. }
2233 ));
2234 }
2235
2236 #[test]
2237 fn test_splice_variants_rejects_empty_counts() {
2238 let err =
2239 splice_variants(&[0.0_f64; 0], 0, 6, &[], &[1, 1, 1, 2, 2, 2], &[0.1]).unwrap_err();
2240 assert!(matches!(err, EdgeErrors::EmptyCounts { .. }));
2241 }
2242
2243 #[test]
2245 fn test_runs_on_f32_counts() {
2246 let (counts, gene_id, design) = gate();
2247 let counts32: Vec<f32> = counts.iter().map(|v| *v as f32).collect();
2248 let offset = offsets_from(&counts, 6, 6);
2249 let fit = glm_fit(
2250 &counts32,
2251 6,
2252 6,
2253 &design,
2254 2,
2255 &Recycled::scalar(0.1),
2256 &offset,
2257 None,
2258 DEFAULT_PRIOR_COUNT,
2259 )
2260 .unwrap();
2261 let input = GlmTestInput {
2262 counts: &counts32,
2263 n_genes: 6,
2264 n_samples: 6,
2265 design: &design,
2266 n_coef: 2,
2267 dispersion: &Recycled::scalar(0.1),
2268 offset: &offset,
2269 weights: None,
2270 log_cpm: None,
2271 };
2272 let out = diff_splice(&input, &fit, &gene_id, &Tested::Coef(vec![1]), None, None).unwrap();
2273 assert_relative_eq!(
2274 out.gene_simes_p[0],
2275 0.011_032_481_883_944_744,
2276 max_relative = 1e-6
2277 );
2278 }
2279}