Skip to content

Commit 67ec04e

Browse files
committed
Fleshed out scenario tests for score_markers_summary/best.
Also cleaned up a missingness test for summarize_comparisons.
1 parent 59dfdd4 commit 67ec04e

3 files changed

Lines changed: 310 additions & 14 deletions

File tree

tests/src/score_markers_best.cpp

Lines changed: 219 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
444661
class ScoreMarkersBestOneAtATimeTest : public ScoreMarkersBestTestCore, public ::testing::TestWithParam<int> {
445662
protected:
446663
inline static std::shared_ptr<tatami::Matrix<double, int> > dense_row, dense_column, sparse_row, sparse_column;

tests/src/score_markers_summary.cpp

Lines changed: 80 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -533,7 +533,7 @@ TEST_F(ScoreMarkersSummaryScenariosTest, Thresholds) {
533533
EXPECT_EQ(ref.detected[l], out.detected[l]);
534534
}
535535

536-
EXPECT_TRUE(some_diff); // (from above)... at least one is '>', hopefully.
536+
EXPECT_GT(some_diff, 0); // (from above)... at least one is '>', hopefully.
537537

538538
// Quantile should give the same results for a single block.
539539
auto qopt = sopt;
@@ -544,6 +544,85 @@ TEST_F(ScoreMarkersSummaryScenariosTest, Thresholds) {
544544
compare_effects(ngroups, out, qout, true);
545545
}
546546

547+
TEST_F(ScoreMarkersSummaryScenariosTest, Missing) {
548+
int nrows = 144, ncols = 109;
549+
tatami::DenseRowMatrix<double, int> mat(
550+
nrows,
551+
ncols,
552+
scran_tests::simulate_vector(
553+
nrows * ncols,
554+
[]{
555+
scran_tests::SimulateVectorParameters sparam;
556+
sparam.seed = 696969;
557+
return sparam;
558+
}()
559+
)
560+
);
561+
562+
int ngroups = 4;
563+
std::vector<int> groupings = create_groupings(ncols, ngroups);
564+
565+
scran_markers::ScoreMarkersSummaryOptions opt;
566+
auto ref = scran_markers::score_markers_summary(mat, groupings.data(), opt);
567+
568+
// Zero is effectively the missing group here.
569+
for (auto& g : groupings) {
570+
++g;
571+
}
572+
auto lost = scran_markers::score_markers_summary(mat, groupings.data(), opt);
573+
574+
// First group is effectively all-NA.
575+
for (int g = 0; g < nrows; ++g) {
576+
EXPECT_TRUE(std::isnan(lost.cohens_d[0].min[g]));
577+
EXPECT_TRUE(std::isnan(lost.cohens_d[0].max[g]));
578+
EXPECT_TRUE(std::isnan(lost.cohens_d[0].mean[g]));
579+
EXPECT_TRUE(std::isnan(lost.cohens_d[0].median[g]));
580+
EXPECT_EQ(lost.cohens_d[0].min_rank[g], nrows);
581+
582+
EXPECT_TRUE(std::isnan(lost.auc[0].min[g]));
583+
EXPECT_TRUE(std::isnan(lost.auc[0].max[g]));
584+
EXPECT_TRUE(std::isnan(lost.auc[0].mean[g]));
585+
EXPECT_TRUE(std::isnan(lost.auc[0].median[g]));
586+
EXPECT_EQ(lost.auc[0].min_rank[g], nrows);
587+
588+
EXPECT_TRUE(std::isnan(lost.delta_mean[0].min[g]));
589+
EXPECT_TRUE(std::isnan(lost.delta_mean[0].max[g]));
590+
EXPECT_TRUE(std::isnan(lost.delta_mean[0].mean[g]));
591+
EXPECT_TRUE(std::isnan(lost.delta_mean[0].median[g]));
592+
EXPECT_EQ(lost.delta_mean[0].min_rank[g], nrows);
593+
594+
EXPECT_TRUE(std::isnan(lost.delta_detected[0].min[g]));
595+
EXPECT_TRUE(std::isnan(lost.delta_detected[0].max[g]));
596+
EXPECT_TRUE(std::isnan(lost.delta_detected[0].mean[g]));
597+
EXPECT_TRUE(std::isnan(lost.delta_detected[0].median[g]));
598+
EXPECT_EQ(lost.delta_detected[0].min_rank[g], nrows);
599+
}
600+
601+
// Expect all but the first group to give the same results.
602+
{
603+
auto copy = lost;
604+
copy.mean.erase(copy.mean.begin());
605+
compare_averages(ref.mean, copy.mean);
606+
607+
copy.detected.erase(copy.detected.begin());
608+
compare_averages(ref.detected, copy.detected);
609+
610+
copy.cohens_d.erase(copy.cohens_d.begin());
611+
copy.auc.erase(copy.auc.begin());
612+
copy.delta_mean.erase(copy.delta_mean.begin());
613+
copy.delta_detected.erase(copy.delta_detected.begin());
614+
compare_effects(ngroups, ref, copy, true);
615+
}
616+
617+
// Quantile should give the same results for a single block.
618+
auto qopt = opt;
619+
qopt.block_average_policy = scran_markers::AveragePolicy::QUANTILE;
620+
auto qlost = scran_markers::score_markers_summary(mat, groupings.data(), qopt);
621+
compare_averages(lost.mean, qlost.mean);
622+
compare_averages(lost.detected, qlost.detected);
623+
compare_effects(ngroups + 1, lost, qlost, true);
624+
}
625+
547626
TEST_F(ScoreMarkersSummaryScenariosTest, BlockConfounded) {
548627
int nrows = 103, ncols = 290;
549628
tatami::DenseRowMatrix<double, int> mat(

tests/src/summarize_comparisons.cpp

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -120,21 +120,19 @@ TEST_P(SummarizeComparisonsTest, Missing) {
120120
for (int gene = 0; gene < ngenes; ++ gene) {
121121
for (int g = 0; g < ngroups; ++g) {
122122
if (g == lost) {
123-
for (int i = 0; i < 3; ++i) {
124-
EXPECT_TRUE(std::isnan(ptrs[g].min[gene]));
125-
EXPECT_TRUE(std::isnan(ptrs[g].mean[gene]));
126-
EXPECT_TRUE(std::isnan(ptrs[g].median[gene]));
127-
EXPECT_TRUE(std::isnan(ptrs[g].max[gene]));
128-
}
123+
EXPECT_TRUE(std::isnan(ptrs[g].min[gene]));
124+
EXPECT_TRUE(std::isnan(ptrs[g].mean[gene]));
125+
EXPECT_TRUE(std::isnan(ptrs[g].median[gene]));
126+
EXPECT_TRUE(std::isnan(ptrs[g].max[gene]));
129127
continue;
130128
}
131129

132130
// Checking that the minimum is correct.
133131
double baseline = g * gene;
134132
if ((g==0 && lost==1) || (g==1 && lost==0)) {
135-
baseline += 2;
133+
baseline += 2; // first non-self, non-NaN effect is 2.
136134
} else if (g==0 || lost==0) {
137-
baseline += 1;
135+
baseline += 1; // self is NaN, so first non-self, non-NaN effect is 1.
138136
}
139137
EXPECT_FLOAT_EQ(ptrs[g].min[gene], baseline);
140138

@@ -148,11 +146,13 @@ TEST_P(SummarizeComparisonsTest, Missing) {
148146
EXPECT_FLOAT_EQ(ptrs[g].mean[gene], baseline);
149147

150148
// Checking that the maximum is correct.
151-
baseline = g * gene + ngroups - 1;
149+
baseline = g * gene;
152150
if ((g==ngroups - 1 && lost==ngroups - 2) || (g==ngroups - 2 && lost==ngroups - 1)) {
153-
baseline -= 2;
151+
baseline += ngroups - 3; // i.e., the last non-self, non-NaN effect.
154152
} else if (g==ngroups - 1 || lost==ngroups - 1) {
155-
baseline -= 1;
153+
baseline += ngroups - 2; // self is NaN, so we get a different maximum.
154+
} else {
155+
baseline += ngroups - 1;
156156
}
157157
EXPECT_FLOAT_EQ(ptrs[g].max[gene], baseline);
158158
}

0 commit comments

Comments
 (0)