scran_markers
Marker detection for single-cell data
Loading...
Searching...
No Matches
score_markers_summary.hpp
Go to the documentation of this file.
1#ifndef SCRAN_SCORE_MARKERS_HPP
2#define SCRAN_SCORE_MARKERS_HPP
3
4#include "scan_matrix.hpp"
5#include "cohens_d.hpp"
6#include "simple_diff.hpp"
8#include "average_group_stats.hpp"
9#include "create_combinations.hpp"
10
12#include "tatami/tatami.hpp"
13
14#include <array>
15#include <map>
16#include <vector>
17
23namespace scran_markers {
24
33 double threshold = 0;
34
39 int num_threads = 1;
40
45 int cache_size = 100;
46
51 bool compute_cohens_d = true;
52
57 bool compute_auc = true;
58
63 bool compute_delta_mean = true;
64
70
75 bool compute_min = true;
76
81 bool compute_mean = true;
82
87 bool compute_median = true;
88
93 bool compute_max = true;
94
99 bool compute_min_rank = true;
100
104 scran_blocks::WeightPolicy block_weight_policy = scran_blocks::WeightPolicy::VARIABLE;
105
111};
112
118template<typename Stat_, typename Rank_>
125 std::vector<Stat_*> mean;
126
132 std::vector<Stat_*> detected;
133
141 std::vector<SummaryBuffers<Stat_, Rank_> > cohens_d;
142
150 std::vector<SummaryBuffers<Stat_, Rank_> > auc;
151
159 std::vector<SummaryBuffers<Stat_, Rank_> > delta_mean;
160
168 std::vector<SummaryBuffers<Stat_, Rank_> > delta_detected;
169};
170
176template<typename Stat_, typename Rank_>
182 std::vector<std::vector<Stat_> > mean;
183
188 std::vector<std::vector<Stat_> > detected;
189
197 std::vector<SummaryResults<Stat_, Rank_> > cohens_d;
198
206 std::vector<SummaryResults<Stat_, Rank_> > auc;
207
215 std::vector<SummaryResults<Stat_, Rank_> > delta_mean;
216
224 std::vector<SummaryResults<Stat_, Rank_> > delta_detected;
225};
226
230namespace internal {
231
232enum class CacheAction : unsigned char { SKIP, COMPUTE, CACHE };
233
234/*
235 * We compute effect sizes in a pairwise fashion with some nested loops, i.e.,
236 * iterating from g1 in [1, G) and then for g2 in [0, g1). When we compute the
237 * effect size for g1 vs g2, we sometimes get a free effect size for g2 vs g1,
238 * which we call the "reverse effect". However, we can't use the reverse effect
239 * size until we get around to summarizing effects for g2, hence the caching.
240 *
241 * This cache tries to store as many of the reverse effects as possible before
242 * it starts evicting. Evictions are based on the principle that it is better
243 * to store effects that will be re-used quickly, thus freeing up the cache for
244 * future stores. The 'speed' of reusability of each cache entry depends on the
245 * first group in the comparison corresponding to each cached effect size; the
246 * smaller the first group, the sooner it will be reached when iterating across
247 * groups in the ScoreMarkers function.
248 *
249 * So, the policy is to evict cache entries when the identity of the first
250 * group in the cached entry is larger than the identity of the first group for
251 * the incoming entry. Given that, if the cache is full, we have to throw away
252 * one of these effects anyway, I'd prefer to hold onto the one we're using
253 * soon, because at least it'll be freed up rapidly.
254 */
255template<typename Stat_>
256class EffectsCacher {
257public:
258 EffectsCacher(size_t ngenes, size_t ngroups, size_t cache_size) :
259 my_ngenes(ngenes),
260 my_ngroups(ngroups),
261 my_cache_size(std::min(cache_size, ngroups * (ngroups - 1) / 2)), // cap it at the maximum possible number of comparisons.
262 my_actions(ngroups),
263 my_common_cache(ngenes * my_cache_size),
264 my_staging_cache(ngroups)
265 {
266 my_unused_pool.reserve(my_cache_size);
267 auto ptr = my_common_cache.data();
268 for (size_t c = 0; c < my_cache_size; ++c, ptr += ngenes) {
269 my_unused_pool.push_back(ptr);
270 }
271 }
272
273private:
274 size_t my_ngenes;
275 size_t my_ngroups;
276 size_t my_cache_size;
277
278 std::vector<CacheAction> my_actions;
279
280 // 'common_cache' contains allocation so that we don't have to do
281 // a lot of fiddling with move constructors, swaps, etc.
282 std::vector<Stat_> my_common_cache;
283
284 // 'staging_cache' contains the set of cached effects in the other
285 // direction, i.e., all other groups compared to the current group. This is
286 // only used to avoid repeated look-ups in 'cached' while filling the
287 // effect size vectors; they will ultimately be transferred to cached after
288 // the processing for the current group is complete.
289 std::vector<Stat_*> my_staging_cache;
290
291 // 'unused_pool' contains the currently-unused set of pointers to free
292 // subarrays in 'my_common_cache'
293 std::vector<Stat_*> my_unused_pool;
294
295 // 'cached' contains the cached effect size vectors from previous groups. Note
296 // that the use of a map is deliberate as we need the sorting.
297 std::map<std::pair<size_t, size_t>, Stat_*> my_cached;
298
299public:
300 void clear() {
301 my_cached.clear();
302 }
303
304public:
305 void fill_effects_from_cache(size_t group, std::vector<double>& full_effects) {
306 // During calculation of effects, the current group (i.e., 'group') is
307 // the first group in the comparison and 'other' is the second group.
308 // However, remember that we want to cache the reverse effects, so in
309 // the cached entry, 'group' is second and 'other' is first.
310 for (size_t other = 0; other < my_ngroups; ++other) {
311 if (other == group) {
312 my_actions[other] = CacheAction::SKIP;
313 continue;
314 }
315
316 if (my_cache_size == 0) {
317 my_actions[other] = CacheAction::COMPUTE;
318 continue;
319 }
320
321 // If 'other' is later than 'group', it's a candidate to be cached,
322 // as it will be used when this method is called with 'group = other'.
323 // Note that the ACTUAL caching decision is made in the refinement step below.
324 if (other > group) {
325 my_actions[other] = CacheAction::CACHE;
326 continue;
327 }
328
329 // Need to recompute cache entries that were previously evicted. We
330 // do so if the cache is empty or the first group of the first cached
331 // entry has a higher index than the current group (and thus the
332 // desired comparison's effects cannot possibly exist in the cache).
333 if (my_cached.empty()) {
334 my_actions[other] = CacheAction::COMPUTE;
335 continue;
336 }
337
338 const auto& front = my_cached.begin()->first;
339 if (front.first > group || front.second > other) {
340 // Technically, the second clause should be (front.first == group && front.second > other).
341 // However, less-thans should be impossible as they should have been used up during processing
342 // of previous 'group' values. Thus, equality is already implied if the first condition fails.
343 my_actions[other] = CacheAction::COMPUTE;
344 continue;
345 }
346
347 // If we got past the previous clause, this implies that the first cache entry
348 // contains the effect sizes for the desired comparison (i.e., 'group - other').
349 // We thus transfer the cached vector to the full_set.
350 my_actions[other] = CacheAction::SKIP;
351 auto curcache = my_cached.begin()->second;
352 for (size_t i = 0; i < my_ngenes; ++i) {
353 full_effects[other + my_ngroups * i /* already size_t's */] = curcache[i];
354 }
355
356 my_unused_pool.push_back(curcache);
357 my_cached.erase(my_cached.begin());
358 }
359
360 // Refining our choice of cacheable entries by doing a dummy run and
361 // seeing whether eviction actually happens. If it doesn't, we won't
362 // bother caching, because that would be a waste of memory accesses.
363 for (size_t other = 0; other < my_ngroups; ++other) {
364 if (my_actions[other] != CacheAction::CACHE) {
365 continue;
366 }
367
368 std::pair<size_t, size_t> key(other, group);
369 if (my_cached.size() < my_cache_size) {
370 auto ptr = my_unused_pool.back();
371 my_cached[key] = ptr;
372 my_staging_cache[other] = ptr;
373 my_unused_pool.pop_back();
374 continue;
375 }
376
377 // Looking at the last cache entry. If the first group of this
378 // entry is larger than the first group of the incoming entry, we
379 // evict it, as the incoming entry has faster reusability.
380 auto it = my_cached.end();
381 --it;
382 if ((it->first).first > other) {
383 auto ptr = it->second;
384 my_cached[key] = ptr;
385 my_staging_cache[other] = ptr;
386 my_cached.erase(it);
387 } else {
388 // Otherwise, if we're not going to do any evictions, we
389 // indicate that we shouldn't even bother computing the
390 // reverse, because we're won't cache the incoming entry.
391 my_actions[other] = CacheAction::COMPUTE;
392 }
393 }
394 }
395
396public:
397 CacheAction get_action(size_t other) const {
398 return my_actions[other];
399 }
400
401 Stat_* get_cache_location(size_t other) const {
402 return my_staging_cache[other];
403 }
404};
405
406template<typename Stat_, typename Rank_>
407void process_simple_summary_effects(
408 size_t ngenes,
409 size_t ngroups,
410 size_t nblocks,
411 size_t ncombos,
412 const std::vector<Stat_>& combo_means,
413 const std::vector<Stat_>& combo_vars,
414 const std::vector<Stat_>& combo_detected,
415 const ScoreMarkersSummaryBuffers<Stat_, Rank_>& output,
416 const std::vector<Stat_>& combo_weights,
417 double threshold,
418 size_t cache_size,
419 int num_threads)
420{
421 // First, computing the pooled averages to get that out of the way.
422 {
423 std::vector<Stat_> total_weights_per_group;
424 const Stat_* total_weights_ptr = combo_weights.data();
425 if (nblocks > 1) {
426 total_weights_per_group = compute_total_weight_per_group(ngroups, nblocks, combo_weights.data());
427 total_weights_ptr = total_weights_per_group.data();
428 }
429
430 tatami::parallelize([&](size_t, size_t start, size_t length) -> void {
431 size_t in_offset = ncombos * static_cast<size_t>(start);
432 const auto* tmp_means = combo_means.data() + in_offset;
433 const auto* tmp_detected = combo_detected.data() + in_offset;
434 for (size_t gene = start, end = start + length; gene < end; ++gene, tmp_means += ncombos, tmp_detected += ncombos) {
435 average_group_stats(gene, ngroups, nblocks, tmp_means, tmp_detected, combo_weights.data(), total_weights_ptr, output.mean, output.detected);
436 }
437 }, ngenes, num_threads);
438 }
439
440 PrecomputedPairwiseWeights<Stat_> preweights(ngroups, nblocks, combo_weights.data());
441 EffectsCacher<Stat_> cache(ngenes, ngroups, cache_size);
442 std::vector<Stat_> full_effects(ngroups * ngenes);
443 std::vector<std::vector<Stat_> > effect_buffers(num_threads);
444 for (auto& ef : effect_buffers) {
445 ef.resize(ngroups);
446 }
447
448 if (output.cohens_d.size()) {
449 cache.clear();
450 for (size_t group = 0; group < ngroups; ++group) {
451 cache.fill_effects_from_cache(group, full_effects);
452
453 tatami::parallelize([&](size_t t, size_t start, size_t length) -> void {
454 size_t in_offset = ncombos * static_cast<size_t>(start); // cast to avoid oerflow.
455 auto my_means = combo_means.data() + in_offset;
456 auto my_variances = combo_vars.data() + in_offset;
457 auto store_ptr = full_effects.data() + static_cast<size_t>(start) * ngroups; // cast to avoid overflow.
458 auto& effect_buffer = effect_buffers[t];
459
460 for (size_t gene = start, end = start + length; gene < end; ++gene, my_means += ncombos, my_variances += ncombos, store_ptr += ngroups) {
461 for (size_t other = 0; other < ngroups; ++other) {
462 auto cache_action = cache.get_action(other);
463 if (cache_action == internal::CacheAction::COMPUTE) {
464 store_ptr[other] = compute_pairwise_cohens_d_one_sided(group, other, my_means, my_variances, ngroups, nblocks, preweights, threshold);
465 } else if (cache_action == internal::CacheAction::CACHE) {
466 auto tmp = compute_pairwise_cohens_d_two_sided(group, other, my_means, my_variances, ngroups, nblocks, preweights, threshold);
467 store_ptr[other] = tmp.first;
468 cache.get_cache_location(other)[gene] = tmp.second;
469 }
470 }
471 summarize_comparisons(ngroups, store_ptr, group, gene, output.cohens_d[group], effect_buffer);
472 }
473 }, ngenes, num_threads);
474
475 auto mr = output.cohens_d[group].min_rank;
476 if (mr) {
477 compute_min_rank_for_group(ngenes, ngroups, group, full_effects.data(), mr, num_threads);
478 }
479 }
480 }
481
482 if (output.delta_mean.size()) {
483 cache.clear();
484 for (size_t group = 0; group < ngroups; ++group) {
485 cache.fill_effects_from_cache(group, full_effects);
486
487 tatami::parallelize([&](size_t t, size_t start, size_t length) -> void {
488 auto my_means = combo_means.data() + ncombos * static_cast<size_t>(start); // cast to size_t to avoid overflow.
489 auto store_ptr = full_effects.data() + static_cast<size_t>(start) * ngroups;
490 auto& effect_buffer = effect_buffers[t];
491
492 for (size_t gene = start, end = start + length; gene < end; ++gene, my_means += ncombos, store_ptr += ngroups) {
493 for (size_t other = 0; other < ngroups; ++other) {
494 auto cache_action = cache.get_action(other);
495 if (cache_action != internal::CacheAction::SKIP) {
496 auto val = compute_pairwise_simple_diff(group, other, my_means, ngroups, nblocks, preweights);
497 store_ptr[other] = val;
498 if (cache_action == CacheAction::CACHE) {
499 cache.get_cache_location(other)[gene] = -val;
500 }
501 }
502 }
503 summarize_comparisons(ngroups, store_ptr, group, gene, output.delta_mean[group], effect_buffer);
504 }
505 }, ngenes, num_threads);
506
507 auto mr = output.delta_mean[group].min_rank;
508 if (mr) {
509 compute_min_rank_for_group(ngenes, ngroups, group, full_effects.data(), mr, num_threads);
510 }
511 }
512 }
513
514 if (output.delta_detected.size()) {
515 cache.clear();
516 for (size_t group = 0; group < ngroups; ++group) {
517 cache.fill_effects_from_cache(group, full_effects);
518
519 tatami::parallelize([&](size_t t, size_t start, size_t length) -> void {
520 auto my_detected = combo_detected.data() + ncombos * static_cast<size_t>(start); // cast to size_t to avoid overflow.
521 auto store_ptr = full_effects.data() + static_cast<size_t>(start) * ngroups;
522 auto& effect_buffer = effect_buffers[t];
523
524 for (size_t gene = start, end = start + length; gene < end; ++gene, my_detected += ncombos, store_ptr += ngroups) {
525 for (size_t other = 0; other < ngroups; ++other) {
526 auto cache_action = cache.get_action(other);
527 if (cache_action != CacheAction::SKIP) {
528 auto val = compute_pairwise_simple_diff(group, other, my_detected, ngroups, nblocks, preweights);
529 store_ptr[other] = val;
530 if (cache_action == CacheAction::CACHE) {
531 cache.get_cache_location(other)[gene] = -val;
532 }
533 }
534 }
535 summarize_comparisons(ngroups, store_ptr, group, gene, output.delta_detected[group], effect_buffer);
536 }
537 }, ngenes, num_threads);
538
539 auto mr = output.delta_detected[group].min_rank;
540 if (mr) {
541 compute_min_rank_for_group(ngenes, ngroups, group, full_effects.data(), mr, num_threads);
542 }
543 }
544 }
545}
546
547template<typename Stat_, typename Rank_>
548ScoreMarkersSummaryBuffers<Stat_, Rank_> fill_summary_results(size_t ngenes, size_t ngroups, ScoreMarkersSummaryResults<Stat_, Rank_>& store, const ScoreMarkersSummaryOptions& options) {
549 ScoreMarkersSummaryBuffers<Stat_, Rank_> output;
550
551 internal::fill_average_results(ngenes, ngroups, store.mean, store.detected, output.mean, output.detected);
552
553 if (options.compute_cohens_d) {
554 output.cohens_d = internal::fill_summary_results(
555 ngenes,
556 ngroups,
557 store.cohens_d,
558 options.compute_min,
559 options.compute_mean,
560 options.compute_median,
561 options.compute_max,
562 options.compute_min_rank
563 );
564 }
565
566 if (options.compute_auc) {
567 output.auc = internal::fill_summary_results(
568 ngenes,
569 ngroups,
570 store.auc,
571 options.compute_min,
572 options.compute_mean,
573 options.compute_median,
574 options.compute_max,
575 options.compute_min_rank
576 );
577 }
578
579 if (options.compute_delta_mean) {
580 output.delta_mean = internal::fill_summary_results(
581 ngenes,
582 ngroups,
583 store.delta_mean,
584 options.compute_min,
585 options.compute_mean,
586 options.compute_median,
587 options.compute_max,
588 options.compute_min_rank
589 );
590 }
591
592 if (options.compute_delta_detected) {
593 output.delta_detected = internal::fill_summary_results(
594 ngenes,
595 ngroups,
596 store.delta_detected,
597 options.compute_min,
598 options.compute_mean,
599 options.compute_median,
600 options.compute_max,
601 options.compute_min_rank
602 );
603 }
604
605 return output;
606}
607
608}
645template<typename Value_, typename Index_, typename Group_, typename Stat_, typename Rank_>
648 const Group_* group,
651{
652 Index_ NC = matrix.ncol();
653 auto group_sizes = tatami_stats::tabulate_groups(group, NC);
654 size_t ngenes = static_cast<size_t>(matrix.nrow());
655 size_t ngroups = group_sizes.size();
656
657 // In most cases this doesn't really matter, but we do it for consistency with the 1-block case,
658 // and to account for variable weighting where non-zero block sizes get zero weight.
659 auto group_weights = scran_blocks::compute_weights<Stat_>(group_sizes, options.block_weight_policy, options.variable_block_weight_parameters);
660
661 size_t payload_size = ngenes * ngroups; // already cast to size_t to avoid overflow.
663
664 bool do_auc = !output.auc.empty();
665 std::vector<Stat_> tmp_auc;
666 Stat_* auc_ptr = NULL;
667 if (do_auc) {
668 tmp_auc.resize(ngroups * ngroups * ngenes);
669 auc_ptr = tmp_auc.data();
670 }
671
672 if (do_auc || matrix.prefer_rows()) {
673 internal::scan_matrix_by_row<true>(
674 matrix,
675 ngroups,
676 group,
677 1,
678 static_cast<int*>(NULL),
679 ngroups,
680 NULL,
684 auc_ptr,
687 options.threshold,
688 options.num_threads
689 );
690
691 } else {
692 internal::scan_matrix_by_column(
693 matrix,
694 ngroups,
695 group,
700 options.num_threads
701 );
702 }
703
704 internal::process_simple_summary_effects(
705 matrix.nrow(),
706 ngroups,
707 1,
708 ngroups,
712 output,
714 options.threshold,
715 options.cache_size,
716 options.num_threads
717 );
718
719 if (do_auc) {
720 internal::summarize_comparisons(ngenes, ngroups, auc_ptr, output.auc, options.num_threads);
721 internal::compute_min_rank_pairwise(ngenes, ngroups, auc_ptr, output.auc, options.num_threads);
722 }
723}
724
752template<typename Value_, typename Index_, typename Group_, typename Block_, typename Stat_, typename Rank_>
755 const Group_* group,
756 const Block_* block,
759{
760 Index_ NC = matrix.ncol();
761 size_t ngenes = static_cast<size_t>(matrix.nrow());
762 size_t ngroups = output.mean.size();
763 size_t nblocks = tatami_stats::total_groups(block, NC);
764
765 auto combinations = internal::create_combinations(ngroups, group, block, NC);
766 auto combo_sizes = internal::tabulate_combinations<Index_>(ngroups, nblocks, combinations);
767 size_t ncombos = combo_sizes.size();
768 auto combo_weights = scran_blocks::compute_weights<Stat_>(combo_sizes, options.block_weight_policy, options.variable_block_weight_parameters);
769
770 size_t payload_size = ngenes * ncombos; // already cast to size_t to avoid overflow.
772
773 bool do_auc = !output.auc.empty();
774 std::vector<Stat_> tmp_auc;
775 Stat_* auc_ptr = NULL;
776 if (do_auc) {
777 tmp_auc.resize(ngroups * ngroups * ngenes);
778 auc_ptr = tmp_auc.data();
779 }
780
781 if (do_auc || matrix.prefer_rows()) {
782 internal::scan_matrix_by_row<false>(
783 matrix,
784 ngroups,
785 group,
786 nblocks,
787 block,
788 ncombos,
789 combinations.data(),
793 auc_ptr,
796 options.threshold,
797 options.num_threads
798 );
799
800 } else {
801 internal::scan_matrix_by_column(
802 matrix,
803 ncombos,
804 combinations.data(),
809 options.num_threads
810 );
811 }
812
813 internal::process_simple_summary_effects(
814 matrix.nrow(),
815 ngroups,
816 nblocks,
817 ncombos,
821 output,
823 options.threshold,
824 options.cache_size,
825 options.num_threads
826 );
827
828 if (do_auc) {
829 internal::summarize_comparisons(ngenes, ngroups, auc_ptr, output.auc, options.num_threads);
830 internal::compute_min_rank_pairwise(ngenes, ngroups, auc_ptr, output.auc, options.num_threads);
831 }
832}
833
850template<typename Stat_ = double, typename Rank_ = int, typename Value_, typename Index_, typename Group_>
853 const Group_* group,
855{
856 size_t ngroups = tatami_stats::total_groups(group, matrix.ncol());
858 auto buffers = internal::fill_summary_results(matrix.nrow(), ngroups, output, options);
860 return output;
861}
862
882template<typename Stat_ = double, typename Rank_ = int, typename Value_, typename Index_, typename Group_, typename Block_>
895
896}
897
898#endif
Marker detection for single-cell data.
Definition score_markers_pairwise.hpp:23
void score_markers_summary(const tatami::Matrix< Value_, Index_ > &matrix, const Group_ *group, const ScoreMarkersSummaryOptions &options, const ScoreMarkersSummaryBuffers< Stat_, Rank_ > &output)
Definition score_markers_summary.hpp:646
void score_markers_summary_blocked(const tatami::Matrix< Value_, Index_ > &matrix, const Group_ *group, const Block_ *block, const ScoreMarkersSummaryOptions &options, const ScoreMarkersSummaryBuffers< Stat_, Rank_ > &output)
Definition score_markers_summary.hpp:753
void parallelize(Function_ fun, Index_ tasks, int threads)
Buffers for score_markers_pairwise() and friends.
Definition score_markers_pairwise.hpp:82
Stat_ * auc
Definition score_markers_pairwise.hpp:119
std::vector< Stat_ * > mean
Definition score_markers_pairwise.hpp:88
Buffers for score_markers_summary() and friends.
Definition score_markers_summary.hpp:119
std::vector< Stat_ * > detected
Definition score_markers_summary.hpp:132
std::vector< SummaryBuffers< Stat_, Rank_ > > delta_detected
Definition score_markers_summary.hpp:168
std::vector< Stat_ * > mean
Definition score_markers_summary.hpp:125
std::vector< SummaryBuffers< Stat_, Rank_ > > delta_mean
Definition score_markers_summary.hpp:159
std::vector< SummaryBuffers< Stat_, Rank_ > > cohens_d
Definition score_markers_summary.hpp:141
std::vector< SummaryBuffers< Stat_, Rank_ > > auc
Definition score_markers_summary.hpp:150
Options for score_markers_summary() and friends.
Definition score_markers_summary.hpp:28
bool compute_max
Definition score_markers_summary.hpp:93
bool compute_delta_detected
Definition score_markers_summary.hpp:69
int cache_size
Definition score_markers_summary.hpp:45
bool compute_auc
Definition score_markers_summary.hpp:57
bool compute_min
Definition score_markers_summary.hpp:75
bool compute_median
Definition score_markers_summary.hpp:87
bool compute_mean
Definition score_markers_summary.hpp:81
double threshold
Definition score_markers_summary.hpp:33
scran_blocks::WeightPolicy block_weight_policy
Definition score_markers_summary.hpp:104
bool compute_cohens_d
Definition score_markers_summary.hpp:51
bool compute_min_rank
Definition score_markers_summary.hpp:99
int num_threads
Definition score_markers_summary.hpp:39
bool compute_delta_mean
Definition score_markers_summary.hpp:63
scran_blocks::VariableWeightParameters variable_block_weight_parameters
Definition score_markers_summary.hpp:110
Results for score_markers_summary() and friends.
Definition score_markers_summary.hpp:177
std::vector< SummaryResults< Stat_, Rank_ > > cohens_d
Definition score_markers_summary.hpp:197
std::vector< std::vector< Stat_ > > mean
Definition score_markers_summary.hpp:182
std::vector< SummaryResults< Stat_, Rank_ > > auc
Definition score_markers_summary.hpp:206
std::vector< SummaryResults< Stat_, Rank_ > > delta_detected
Definition score_markers_summary.hpp:224
std::vector< std::vector< Stat_ > > detected
Definition score_markers_summary.hpp:188
std::vector< SummaryResults< Stat_, Rank_ > > delta_mean
Definition score_markers_summary.hpp:215
Utilities for effect summarization.