1#ifndef SCRAN_SCORE_MARKERS_HPP
2#define SCRAN_SCORE_MARKERS_HPP
6#include "sanisizer/sanisizer.hpp"
8#include "quickstats/quickstats.hpp"
15#include "scan_matrix.hpp"
16#include "cohens_d.hpp"
17#include "simple_diff.hpp"
20#include "average_group_stats.hpp"
21#include "create_combinations.hpp"
175template<
typename Stat_,
typename Rank_>
202 std::vector<SummaryBuffers<Stat_, Rank_> >
cohens_d;
211 std::vector<SummaryBuffers<Stat_, Rank_> >
auc;
238template<
typename Stat_,
typename Index_>
239using MinrankTopQueues = std::vector<std::optional<std::vector<topicks::TopQueue<Stat_, Index_> > > >;
241template<
typename Stat_,
typename Index_,
typename Rank_>
242void preallocate_minrank_queues(
243 const std::size_t num_groups,
244 MinrankTopQueues<Stat_, Index_>& queue,
253 sanisizer::resize(queue, num_groups);
254 for (I<
decltype(num_groups)> g1 = 0; g1 < num_groups; ++g1) {
255 if (summaries[g1].min_rank == NULL) {
260 auto& g_queue = *(queue[g1]);
261 sanisizer::reserve(g_queue, num_groups);
262 for (I<
decltype(num_groups)> g2 = 0; g2 < num_groups; ++g2) {
263 g_queue.emplace_back(limit,
true, qopt);
268template<
typename Stat_,
typename Index_,
typename Rank_>
269void compute_summary_stats_per_gene(
271 const std::size_t num_groups,
272 const Stat_*
const pairwise_buffer_ptr,
273 std::vector<Stat_>& summary_buffer,
274 MaybeMultipleQuantiles<Stat_>& summary_qcalcs,
275 MinrankTopQueues<Stat_, Index_>& minrank_queues,
276 const std::vector<SummaryBuffers<Stat_, Rank_> >& summaries
278 for (I<
decltype(num_groups)> gr = 0; gr < num_groups; ++gr) {
279 auto& cursummary = summaries[gr];
280 const auto in_offset = sanisizer::product_unsafe<std::size_t>(num_groups, gr);
281 summarize_comparisons(num_groups, pairwise_buffer_ptr + in_offset, gr, gene, cursummary, summary_qcalcs, summary_buffer);
283 if (cursummary.min_rank) {
284 auto& gr_queue = *(minrank_queues[gr]);
285 for (I<
decltype(num_groups)> gr2 = 0; gr2 < num_groups; ++gr2) {
287 gr_queue[gr2].emplace(pairwise_buffer_ptr[in_offset + gr2], gene);
294template<
typename Stat_,
typename Index_,
typename Rank_>
295void report_minrank_from_queues(
296 const Index_ num_genes,
297 const std::size_t num_groups,
298 std::vector<std::optional<MinrankTopQueues<Stat_, Index_> > >& all_queues,
299 const std::vector<SummaryBuffers<Stat_, Rank_> >& summaries,
300 const int num_threads,
303 if (all_queues.empty()) {
311 tatami::parallelize([&](
const int,
const std::size_t start,
const std::size_t length) ->
void {
312 std::vector<Index_> tie_buffer;
314 for (I<
decltype(num_groups)> gr = start, grend = start + length; gr < grend; ++gr) {
315 const auto mr_out = summaries[gr].min_rank;
316 if (mr_out == NULL) {
321 const auto maxrank_placeholder = sanisizer::cast<Rank_>(num_genes);
322 std::fill_n(mr_out, num_genes, maxrank_placeholder);
324 auto& first_mr_queue = *(all_queues.front());
325 for (I<
decltype(num_groups)> gr2 = 0; gr2 < num_groups; ++gr2) {
332 auto current_out = std::move((*(first_mr_queue[gr]))[gr2]);
334 const auto num_queues = all_queues.size();
335 for (I<
decltype(num_queues)> q = 1; q < num_queues; ++q) {
336 auto& current_mr_queue = *(all_queues[q]);
337 auto current_in = std::move((*(current_mr_queue[gr]))[gr2]);
338 while (!current_in.empty()) {
339 current_out.push(current_in.top());
347 while (!current_out.empty()) {
348 auto& mr = mr_out[current_out.top().second];
349 mr = std::min(mr,
static_cast<Rank_
>(current_out.size()));
353 while (!current_out.empty()) {
355 const auto curtop = current_out.top();
358 while (!current_out.empty() && current_out.top().first == curtop.first) {
359 tie_buffer.push_back(current_out.top().second);
364 const Rank_ tied_rank = current_out.size() + 1;
366 mr_out[curtop.second] = std::min(mr_out[curtop.second], tied_rank);
367 for (
const auto t : tie_buffer) {
368 mr_out[t] = std::min(mr_out[t], tied_rank);
374 }, num_groups, num_threads);
377template<
typename Index_,
typename Stat_,
typename Rank_>
378void process_simple_summary_effects(
379 const Index_ num_genes,
380 const std::size_t num_groups,
381 const std::size_t num_blocks,
382 const std::size_t num_combos,
383 const std::vector<Stat_>& combo_means,
384 const std::vector<Stat_>& combo_vars,
385 const std::vector<Stat_>& combo_detected,
386 const double threshold,
387 const BlockAverageInfo<Stat_>& average_info,
388 const std::optional<std::vector<double> >& summary_quantiles,
389 const Index_ minrank_limit,
390 const bool minrank_keep_ties,
391 const ScoreMarkersSummaryBuffers<Stat_, Rank_>& output,
392 const int num_threads
394 std::optional<std::vector<std::optional<MinrankTopQueues<Stat_, Index_> > > > cohens_d_minrank_all_queues, delta_mean_minrank_all_queues, delta_detected_minrank_all_queues;
395 if (output.cohens_d.size()) {
396 cohens_d_minrank_all_queues.emplace(sanisizer::cast<I<
decltype(cohens_d_minrank_all_queues->size())> >(num_threads));
398 if (output.delta_mean.size()) {
399 delta_mean_minrank_all_queues.emplace(sanisizer::cast<I<
decltype(delta_mean_minrank_all_queues->size())> >(num_threads));
401 if (output.delta_detected.size()) {
402 delta_detected_minrank_all_queues.emplace(sanisizer::cast<I<
decltype(delta_detected_minrank_all_queues->size())> >(num_threads));
405 std::optional<std::vector<Stat_> > total_weights_per_group;
406 const Stat_* total_weights_ptr = NULL;
407 if (average_info.use_mean()) {
408 if (!output.mean.empty() || !output.detected.empty()) {
409 if (num_blocks > 1) {
410 total_weights_per_group = compute_total_weight_per_group(num_groups, num_blocks, average_info.combo_weights().data());
411 total_weights_ptr = total_weights_per_group->data();
413 total_weights_ptr = average_info.combo_weights().data();
418 std::optional<PrecomputedPairwiseWeights<Stat_> > preweights;
419 if (average_info.use_mean()) {
420 if (!output.cohens_d.empty() || !output.delta_mean.empty() || !output.delta_detected.empty()) {
421 preweights = PrecomputedPairwiseWeights<Stat_>(num_groups, num_blocks, average_info.combo_weights().data());
425 const auto num_groups2 = sanisizer::product<typename std::vector<Stat_>::size_type>(num_groups, num_groups);
426 const auto nused =
tatami::parallelize([&](
const int t,
const Index_ start,
const Index_ length) ->
void {
427 std::vector<Stat_> pairwise_buffer(num_groups2);
428 std::vector<Stat_> summary_buffer(num_groups);
429 auto summary_qcalcs = setup_multiple_quantiles<Stat_>(summary_quantiles, num_groups);
431 std::optional<std::vector<Stat_> > qbuffer, qrevbuffer;
432 std::optional<quickstats::SingleQuantileVariableNumber<Stat_> > qcalc;
433 if (!average_info.use_mean()) {
435 qrevbuffer.emplace();
436 qcalc.emplace(num_blocks, average_info.quantile());
439 std::optional<MinrankTopQueues<Stat_, Index_> > cohens_d_minrank_queue, delta_mean_minrank_queue, delta_detected_minrank_queue;
440 if (output.cohens_d.size()) {
441 cohens_d_minrank_queue.emplace();
442 preallocate_minrank_queues(num_groups, *cohens_d_minrank_queue, output.cohens_d, minrank_limit, minrank_keep_ties);
444 if (output.delta_mean.size()) {
445 delta_mean_minrank_queue.emplace();
446 preallocate_minrank_queues(num_groups, *delta_mean_minrank_queue, output.delta_mean, minrank_limit, minrank_keep_ties);
448 if (output.delta_detected.size()) {
449 delta_detected_minrank_queue.emplace();
450 preallocate_minrank_queues(num_groups, *delta_detected_minrank_queue, output.delta_detected, minrank_limit, minrank_keep_ties);
453 for (Index_ gene = start, end = start + length; gene < end; ++gene) {
454 const auto in_offset = sanisizer::product_unsafe<std::size_t>(gene, num_combos);
456 if (!output.mean.empty()) {
457 const auto tmp_means = combo_means.data() + in_offset;
458 if (average_info.use_mean()) {
459 average_group_stats_blockmean(gene, num_groups, num_blocks, tmp_means, average_info.combo_weights().data(), total_weights_ptr, output.mean);
461 average_group_stats_blockquantile(gene, num_groups, num_blocks, tmp_means, *qbuffer, *qcalc, output.mean);
465 if (!output.detected.empty()) {
466 const auto tmp_detected = combo_detected.data() + in_offset;
467 if (average_info.use_mean()) {
468 average_group_stats_blockmean(gene, num_groups, num_blocks, tmp_detected, average_info.combo_weights().data(), total_weights_ptr, output.detected);
470 average_group_stats_blockquantile(gene, num_groups, num_blocks, tmp_detected, *qbuffer, *qcalc, output.detected);
474 if (output.cohens_d.size()) {
475 const auto tmp_means = combo_means.data() + in_offset;
476 const auto tmp_variances = combo_vars.data() + in_offset;
477 if (average_info.use_mean()) {
478 compute_pairwise_cohens_d_blockmean(tmp_means, tmp_variances, num_groups, num_blocks, threshold, *preweights, pairwise_buffer.data());
480 compute_pairwise_cohens_d_blockquantile(tmp_means, tmp_variances, num_groups, num_blocks, threshold, *qbuffer, *qrevbuffer, *qcalc, pairwise_buffer.data());
482 compute_summary_stats_per_gene(gene, num_groups, pairwise_buffer.data(), summary_buffer, summary_qcalcs, *cohens_d_minrank_queue, output.cohens_d);
485 if (output.delta_mean.size()) {
486 const auto tmp_means = combo_means.data() + in_offset;
487 if (average_info.use_mean()) {
488 compute_pairwise_simple_diff_blockmean(tmp_means, num_groups, num_blocks, *preweights, pairwise_buffer.data());
490 compute_pairwise_simple_diff_blockquantile(tmp_means, num_groups, num_blocks, *qbuffer, *qcalc, pairwise_buffer.data());
492 compute_summary_stats_per_gene(gene, num_groups, pairwise_buffer.data(), summary_buffer, summary_qcalcs, *delta_mean_minrank_queue, output.delta_mean);
495 if (output.delta_detected.size()) {
496 const auto tmp_det = combo_detected.data() + in_offset;
497 if (average_info.use_mean()) {
498 compute_pairwise_simple_diff_blockmean(tmp_det, num_groups, num_blocks, *preweights, pairwise_buffer.data());
500 compute_pairwise_simple_diff_blockquantile(tmp_det, num_groups, num_blocks, *qbuffer, *qcalc, pairwise_buffer.data());
502 compute_summary_stats_per_gene(gene, num_groups, pairwise_buffer.data(), summary_buffer, summary_qcalcs, *delta_detected_minrank_queue, output.delta_detected);
507 if (output.cohens_d.size()) {
508 (*cohens_d_minrank_all_queues)[t] = std::move(cohens_d_minrank_queue);
510 if (output.delta_mean.size()) {
511 (*delta_mean_minrank_all_queues)[t] = std::move(delta_mean_minrank_queue);
513 if (output.delta_detected.size()) {
514 (*delta_detected_minrank_all_queues)[t] = std::move(delta_detected_minrank_queue);
516 }, num_genes, num_threads);
518 if (output.cohens_d.size()) {
519 cohens_d_minrank_all_queues->resize(nused);
520 report_minrank_from_queues(num_genes, num_groups, *cohens_d_minrank_all_queues, output.cohens_d, num_threads, minrank_keep_ties);
522 if (output.delta_mean.size()) {
523 delta_mean_minrank_all_queues->resize(nused);
524 report_minrank_from_queues(num_genes, num_groups, *delta_mean_minrank_all_queues, output.delta_mean, num_threads, minrank_keep_ties);
526 if (output.delta_detected.size()) {
527 delta_detected_minrank_all_queues->resize(nused);
528 report_minrank_from_queues(num_genes, num_groups, *delta_detected_minrank_all_queues, output.delta_detected, num_threads, minrank_keep_ties);
543 const Group_*
const group,
544 const std::size_t num_groups,
545 const Block_*
const block,
546 const std::size_t num_blocks,
547 const std::size_t*
const combo,
548 const std::size_t num_combos,
549 const std::vector<Index_>& combo_sizes,
550 const ScoreMarkersSummaryOptions& options,
551 const ScoreMarkersSummaryBuffers<Stat_, Rank_>& output
553 const auto num_genes = matrix.
nrow();
554 const auto payload_size = sanisizer::product<typename std::vector<Stat_>::size_type>(num_genes, num_combos);
555 std::vector<Stat_> combo_means, combo_vars, combo_detected;
556 if (!output.mean.empty() || !output.cohens_d.empty() || !output.delta_mean.empty()) {
557 combo_means.resize(payload_size);
559 if (!output.cohens_d.empty()) {
560 combo_vars.resize(payload_size);
562 if (!output.detected.empty() || !output.delta_detected.empty()) {
563 combo_detected.resize(payload_size);
568 BlockAverageInfo<Stat_> average_info;
569 if (options.block_average_policy == BlockAveragePolicy::MEAN) {
570 average_info = BlockAverageInfo<Stat_>(
573 options.block_weight_policy,
574 options.variable_block_weight_parameters
578 average_info = BlockAverageInfo<Stat_>(options.block_quantile);
581 const Index_ minrank_limit = sanisizer::cap<Index_>(options.min_rank_limit);
582 internal::validate_quantiles(options.compute_summary_quantiles);
584 if (!output.auc.empty()) {
585 auto auc_minrank_all_queues = sanisizer::create<std::vector<std::optional<MinrankTopQueues<Stat_, Index_> > > >(options.num_threads);
587 struct AucResultWorkspace {
588 AucResultWorkspace(
const std::size_t num_groups,
const std::optional<std::vector<double> >& summary_quantiles) :
589 pairwise_buffer(sanisizer::product<typename std::vector<Stat_>::size_type>(num_groups, num_groups)),
590 summary_buffer(sanisizer::cast<typename std::vector<Stat_>::size_type>(num_groups)),
591 summary_qcalcs(setup_multiple_quantiles<Stat_>(summary_quantiles, num_groups))
595 std::vector<Stat_> pairwise_buffer;
596 std::vector<Stat_> summary_buffer;
597 MaybeMultipleQuantiles<Stat_> summary_qcalcs;
598 MinrankTopQueues<Stat_, Index_> queue;
601 const auto num_used = scan_matrix_by_row_custom_auc<single_block_>(
615 [&](
const int) -> AucResultWorkspace {
616 AucResultWorkspace res_work(num_groups, options.compute_summary_quantiles);
617 preallocate_minrank_queues(num_groups, res_work.queue, output.auc, minrank_limit, options.min_rank_preserve_ties);
620 [&](
const Index_ gene, AucScanWorkspace<Value_, Group_, Stat_, Index_>& auc_work, AucResultWorkspace& res_work) ->
void {
621 process_auc_for_rows(auc_work, num_groups, num_blocks, options.threshold, res_work.pairwise_buffer.data());
622 compute_summary_stats_per_gene(gene, num_groups, res_work.pairwise_buffer.data(), res_work.summary_buffer, res_work.summary_qcalcs, res_work.queue, output.auc);
624 [&](
const int t, AucResultWorkspace& res_work) ->
void {
625 auc_minrank_all_queues[t] = std::move(res_work.queue);
630 auc_minrank_all_queues.resize(num_used);
631 report_minrank_from_queues(num_genes, num_groups, auc_minrank_all_queues, output.auc, options.num_threads, options.min_rank_preserve_ties);
634 scan_matrix_by_row_full_auc<single_block_>(
647 static_cast<Stat_*
>(NULL),
653 scan_matrix_by_column(
656 if constexpr(single_block_) {
663 if constexpr(single_block_) {
677 process_simple_summary_effects(
687 options.compute_summary_quantiles,
689 options.min_rank_preserve_ties,
735template<
typename Value_,
typename Index_,
typename Group_,
typename Stat_,
typename Rank_>
738 const Group_*
const group,
739 const std::size_t num_groups,
743 const auto group_sizes = tabulate_groups(matrix.
ncol(), group, num_groups);
744 internal::score_markers_summary<true>(
748 static_cast<int*
>(NULL),
750 static_cast<std::size_t*
>(NULL),
788template<
typename Value_,
typename Index_,
typename Group_,
typename Block_,
typename Stat_,
typename Rank_>
791 const Group_*
const group,
792 const std::size_t num_groups,
793 const Block_*
const block,
794 const std::size_t num_blocks,
798 const auto combo_out = create_combinations(matrix.
ncol(), group, num_groups, block, num_blocks);
799 internal::score_markers_summary<false>(
805 combo_out.combinations.data(),
806 combo_out.num_combinations,
807 combo_out.frequencies,
818template<
typename Stat_,
typename Rank_>
824 std::vector<std::vector<Stat_> >
mean;
839 std::vector<SummaryResults<Stat_, Rank_> >
cohens_d;
848 std::vector<SummaryResults<Stat_, Rank_> >
auc;
872template<
typename Index_,
typename Stat_,
typename Rank_>
874 const Index_ num_genes,
875 const std::size_t num_groups,
882 internal::preallocate_average_results(num_genes, num_groups, store.
mean, output.
mean);
886 internal::preallocate_average_results(num_genes, num_groups, store.
detected, output.
detected);
890 output.
cohens_d = internal::fill_summary_results(
904 output.
auc = internal::fill_summary_results(
918 output.
delta_mean = internal::fill_summary_results(
969template<
typename Stat_ =
double,
typename Rank_ =
int,
typename Value_,
typename Index_,
typename Group_>
972 const Group_*
const group,
973 const std::size_t num_groups,
977 const auto buffers = preallocate_summary_results(matrix.
nrow(), num_groups, output, options);
1004template<
typename Stat_ =
double,
typename Rank_ =
int,
typename Value_,
typename Index_,
typename Group_,
typename Block_>
1007 const Group_*
const group,
1008 const std::size_t num_groups,
1009 const Block_*
const block,
1010 const std::size_t num_blocks,
1014 const auto buffers = preallocate_summary_results(matrix.
nrow(), num_groups, output, options);
Averaging statistics over blocks.
virtual Index_ ncol() const=0
virtual Index_ nrow() const=0
virtual bool prefer_rows() const=0
void compute_weights(const std::size_t num_blocks, const Size_ *const sizes, const WeightPolicy policy, const VariableWeightParameters &variable, Weight_ *const weights)
Marker detection for single-cell data.
Definition score_markers_pairwise.hpp:26
void score_markers_summary_blocked(const tatami::Matrix< Value_, Index_ > &matrix, const Group_ *const group, const std::size_t num_groups, const Block_ *const block, const std::size_t num_blocks, const ScoreMarkersSummaryOptions &options, const ScoreMarkersSummaryBuffers< Stat_, Rank_ > &output)
Definition score_markers_summary.hpp:789
BlockAveragePolicy
Definition block_averages.hpp:27
void score_markers_summary(const tatami::Matrix< Value_, Index_ > &matrix, const Group_ *const group, const std::size_t num_groups, const ScoreMarkersSummaryOptions &options, const ScoreMarkersSummaryBuffers< Stat_, Rank_ > &output)
Definition score_markers_summary.hpp:736
int parallelize(Function_ fun, const Index_ tasks, const int workers)
Pointers to arrays to hold the summary statistics.
Definition summarize_comparisons.hpp:33
Utilities for effect summarization.