@@ -327,7 +327,8 @@ TEST_P(ScoreMarkersBestBlockedTest, AgainstPairwiseMean) {
327327 }
328328 }
329329
330- // Note: skipping the multi-threaded checks as this is the same as the unblocked case.
330+ // Note: skipping the multi-threaded checks as we've already got too many test cases.
331+ // Besides, the multi-threaded code is the same as the unblocked case.
331332
332333 // Comparing to all of the other matrix representations.
333334 {
@@ -404,7 +405,8 @@ TEST_P(ScoreMarkersBestBlockedTest, AgainstPairwiseQuantile) {
404405 }
405406 }
406407
407- // Note: skipping the multi-threaded checks as this is the same as the unblocked case.
408+ // Note: skipping the multi-threaded checks as we've already got too many test cases.
409+ // Besides, the multi-threaded code is the same as the unblocked case.
408410
409411 // Comparing to all of the other matrix representations.
410412 {
@@ -441,6 +443,221 @@ INSTANTIATE_TEST_SUITE_P(
441443
442444/* ********************************************/
443445
446+ class ScoreMarkersBestScenariosTest : public ScoreMarkersBestTestCore , public ::testing::Test {};
447+
448+ TEST_F (ScoreMarkersBestScenariosTest, Thresholds) {
449+ int nrows = 291 , ncols = 91 ;
450+ tatami::DenseRowMatrix<double , int > mat (
451+ nrows,
452+ ncols,
453+ scran_tests::simulate_vector (
454+ nrows * ncols,
455+ []{
456+ scran_tests::SimulateVectorParameters sparam;
457+ sparam.seed = 696969 ;
458+ return sparam;
459+ }()
460+ )
461+ );
462+
463+ int ngroups = 3 ;
464+ std::vector<int > groupings = create_groupings (mat.ncol (), ngroups);
465+
466+ int top = 10 ;
467+ scran_markers::ScoreMarkersBestOptions sopt;
468+ auto ref = scran_markers::score_markers_best<double >(mat, groupings.data (), top, sopt);
469+ sopt.threshold = 1 ;
470+ auto out = scran_markers::score_markers_best<double >(mat, groupings.data (), top, sopt);
471+
472+ int some_diff = 0 ;
473+ for (int l = 0 ; l < ngroups; ++l) {
474+ // Not affected.
475+ EXPECT_EQ (ref.mean [l], out.mean [l]);
476+ EXPECT_EQ (ref.detected [l], out.detected [l]);
477+
478+ for (int k = 0 ; k < ngroups; ++k) {
479+ if (k == l) {
480+ continue ;
481+ }
482+
483+ EXPECT_EQ (ref.delta_mean [l][k], out.delta_mean [l][k]);
484+ EXPECT_EQ (ref.delta_detected [l][k], out.delta_detected [l][k]);
485+
486+ const auto & rcohen = ref.cohens_d [l][k];
487+ const auto & ocohen = out.cohens_d [l][k];
488+ const std::size_t ncohen = std::min (rcohen.size (), ocohen.size ());
489+ for (std::size_t i = 0 ; i < ncohen; ++i) {
490+ EXPECT_GT (rcohen[i].second , ocohen[i].second );
491+ }
492+
493+ const auto & rauc = ref.auc [l][k];
494+ const auto & oauc = out.auc [l][k];
495+ const std::size_t nauc = std::min (rauc.size (), oauc.size ());
496+ for (std::size_t i = 0 ; i < nauc; ++i) {
497+ // Can't use GT as the ranks might not be affected by the threshold.
498+ EXPECT_GE (rauc[i].second , oauc[i].second );
499+ some_diff += (rauc[i].second != oauc[i].second );
500+ }
501+ }
502+ }
503+
504+ EXPECT_GT (some_diff, 0 ); // though hopefully at least one is changed.
505+
506+ // Quantile should give the same results for a single block.
507+ auto qopt = sopt;
508+ qopt.block_average_policy = scran_markers::AveragePolicy::QUANTILE ;
509+ auto qout = scran_markers::score_markers_best<double >(mat, groupings.data (), top, qopt);
510+ compare_averages (out.mean , qout.mean );
511+ compare_averages (out.detected , qout.detected );
512+ compare_best (out, qout);
513+ }
514+
515+ TEST_F (ScoreMarkersBestScenariosTest, Missing) {
516+ int nrows = 144 , ncols = 109 ;
517+ tatami::DenseRowMatrix<double , int > mat (
518+ nrows,
519+ ncols,
520+ scran_tests::simulate_vector (
521+ nrows * ncols,
522+ []{
523+ scran_tests::SimulateVectorParameters sparam;
524+ sparam.seed = 696969 ;
525+ return sparam;
526+ }()
527+ )
528+ );
529+
530+ int ngroups = 4 ;
531+ std::vector<int > groupings = create_groupings (ncols, ngroups);
532+
533+ int top = 10 ;
534+ scran_markers::ScoreMarkersBestOptions opt;
535+ auto ref = scran_markers::score_markers_best<double >(mat, groupings.data (), top, opt);
536+
537+ // Zero is effectively the missing group here.
538+ for (auto & g : groupings) {
539+ ++g;
540+ }
541+ auto lost = scran_markers::score_markers_best<double >(mat, groupings.data (), top, opt);
542+
543+ // Everything should be empty.
544+ for (int g = 1 ; g <= ngroups; ++g) {
545+ EXPECT_TRUE (lost.cohens_d [0 ][g].empty ());
546+ EXPECT_TRUE (lost.cohens_d [g][0 ].empty ());
547+ EXPECT_TRUE (lost.auc [0 ][g].empty ());
548+ EXPECT_TRUE (lost.auc [g][0 ].empty ());
549+ EXPECT_TRUE (lost.delta_mean [0 ][g].empty ());
550+ EXPECT_TRUE (lost.delta_mean [g][0 ].empty ());
551+ EXPECT_TRUE (lost.delta_detected [0 ][g].empty ());
552+ EXPECT_TRUE (lost.delta_detected [g][0 ].empty ());
553+ }
554+ for (int r = 0 ; r < nrows; ++r) {
555+ EXPECT_TRUE (std::isnan (lost.mean [0 ][r]));
556+ EXPECT_TRUE (std::isnan (lost.detected [0 ][r]));
557+ }
558+
559+ // Other metrics should be the same as usual.
560+ for (int g1 = 0 ; g1 < ngroups; ++g1) {
561+ EXPECT_EQ (lost.mean [g1+1 ], ref.mean [g1]);
562+ EXPECT_EQ (lost.detected [g1+1 ], ref.detected [g1]);
563+
564+ for (int g2 = 0 ; g2 < ngroups; ++g2) {
565+ EXPECT_EQ (lost.cohens_d [g1 + 1 ][g2 + 1 ], ref.cohens_d [g1][g2]);
566+ EXPECT_EQ (lost.auc [g1 + 1 ][g2 + 1 ], ref.auc [g1][g2]);
567+ EXPECT_EQ (lost.delta_mean [g1 + 1 ][g2 + 1 ], ref.delta_mean [g1][g2]);
568+ EXPECT_EQ (lost.delta_detected [g1 + 1 ][g2 + 1 ], ref.delta_detected [g1][g2]);
569+ }
570+ }
571+
572+ // Quantile should give the same results for a single block.
573+ auto qopt = opt;
574+ qopt.block_average_policy = scran_markers::AveragePolicy::QUANTILE ;
575+ auto qlost = scran_markers::score_markers_best<double >(mat, groupings.data (), top, qopt);
576+ compare_averages (lost.mean , qlost.mean );
577+ compare_averages (lost.detected , qlost.detected );
578+ compare_best (lost, qlost);
579+ }
580+
581+ TEST_F (ScoreMarkersBestScenariosTest, BlockConfounded) {
582+ int nrows = 198 , ncols = 99 ;
583+ std::shared_ptr<tatami::Matrix<double , int > > mat (
584+ new tatami::DenseRowMatrix<double , int >(
585+ nrows,
586+ ncols,
587+ scran_tests::simulate_vector (
588+ nrows * ncols,
589+ []{
590+ scran_tests::SimulateVectorParameters sparam;
591+ sparam.seed = 69696969 ;
592+ return sparam;
593+ }()
594+ )
595+ )
596+ );
597+
598+ int ngroups = 4 ;
599+ std::vector<int > groupings = create_groupings (ncols, ngroups);
600+
601+ // Block is fully confounded with one group.
602+ std::vector<int > blocks (ncols);
603+ for (int c = 0 ; c < ncols; ++c) {
604+ blocks[c] = (groupings[c] == 0 );
605+ }
606+
607+ int top = 10 ;
608+ scran_markers::ScoreMarkersBestOptions opt;
609+ auto comres = scran_markers::score_markers_best_blocked<double >(*mat, groupings.data (), blocks.data (), top, opt);
610+
611+ // First group should only be NaN's.
612+ for (int g = 1 ; g < ngroups; ++g) {
613+ EXPECT_TRUE (comres.cohens_d [0 ][g].empty ());
614+ EXPECT_TRUE (comres.cohens_d [g][0 ].empty ());
615+ EXPECT_TRUE (comres.auc [0 ][g].empty ());
616+ EXPECT_TRUE (comres.auc [g][0 ].empty ());
617+ EXPECT_TRUE (comres.delta_mean [0 ][g].empty ());
618+ EXPECT_TRUE (comres.delta_mean [g][0 ].empty ());
619+ EXPECT_TRUE (comres.delta_detected [0 ][g].empty ());
620+ EXPECT_TRUE (comres.delta_detected [g][0 ].empty ());
621+ }
622+
623+ // Excluding the confounded group and running on the remaining samples.
624+ std::vector<int > subgroups;
625+ std::vector<int > keep;
626+ for (int c = 0 ; c < ncols; ++c) {
627+ auto g = groupings[c];
628+ if (g != 0 ) {
629+ subgroups.push_back (g - 1 );
630+ keep.push_back (c);
631+ }
632+ }
633+
634+ auto sub = tatami::make_DelayedSubset (mat, std::move (keep), false );
635+ auto ref = scran_markers::score_markers_best<double >(*sub, subgroups.data (), top, opt);
636+
637+ for (int g1 = 1 ; g1 < ngroups; ++g1) {
638+ EXPECT_EQ (comres.mean [g1], ref.mean [g1 - 1 ]);
639+ EXPECT_EQ (comres.detected [g1], ref.detected [g1 - 1 ]);
640+
641+ for (int g2 = 1 ; g2 < ngroups; ++g2) {
642+ EXPECT_EQ (comres.cohens_d [g1][g2], ref.cohens_d [g1 - 1 ][g2 - 1 ]);
643+ EXPECT_EQ (comres.auc [g1][g2], ref.auc [g1 - 1 ][g2 - 1 ]);
644+ EXPECT_EQ (comres.delta_mean [g1][g2], ref.delta_mean [g1 - 1 ][g2 - 1 ]);
645+ EXPECT_EQ (comres.delta_detected [g1][g2], ref.delta_detected [g1 - 1 ][g2 - 1 ]);
646+ }
647+ }
648+
649+ // Quantile should give the same results, as there's basically only one block;
650+ // the second block is fully confounded.
651+ auto qopt = opt;
652+ qopt.block_average_policy = scran_markers::AveragePolicy::QUANTILE ;
653+ auto qcomres = scran_markers::score_markers_best_blocked<double >(*mat, groupings.data (), blocks.data (), top, qopt);
654+ compare_averages (comres.mean , qcomres.mean );
655+ compare_averages (comres.detected , qcomres.detected );
656+ compare_best (comres, qcomres);
657+ }
658+
659+ /* ********************************************/
660+
444661class ScoreMarkersBestOneAtATimeTest : public ScoreMarkersBestTestCore , public ::testing::TestWithParam<int > {
445662protected:
446663 inline static std::shared_ptr<tatami::Matrix<double , int > > dense_row, dense_column, sparse_row, sparse_column;
0 commit comments