scran_variances
Model per-gene variance in expression
Loading...
Searching...
No Matches
model_gene_variances.hpp
Go to the documentation of this file.
1#ifndef SCRAN_MODEL_GENE_VARIANCES_HPP
2#define SCRAN_MODEL_GENE_VARIANCES_HPP
3
4#include <algorithm>
5#include <vector>
6#include <limits>
7#include <cstddef>
8#include <cassert>
9#include <optional>
10
11#include "tatami/tatami.hpp"
12#include "tatami_stats/tatami_stats.hpp"
13#include "quickstats/quickstats.hpp"
15#include "sanisizer/sanisizer.hpp"
16
18#include "utils.hpp"
19
25namespace scran_variances {
26
34enum class BlockAveragePolicy : unsigned char { MEAN, QUANTILE, NONE };
35
45 bool trend = true;
46
52
58 BlockAveragePolicy block_average_policy = BlockAveragePolicy::MEAN;
59
70 scran_blocks::WeightPolicy block_weight_policy = scran_blocks::WeightPolicy::VARIABLE;
71
78
82 // Back-compatibility only.
83 bool compute_average = true;
93 double block_quantile = 0.5;
94
99 int num_threads = 1;
100};
101
110template<typename Stat_>
115 Stat_* mean;
116
120 Stat_* variance;
121
127 Stat_* fitted;
128
134 Stat_* residual;
135};
136
154template<typename Value_, typename Index_, typename Stat_>
158 const ModelGeneVariancesOptions& options
159) {
160 tatami_stats::VarianceBuffers<Stat_> vbuf;
161 vbuf.mean = buffers.mean;
162 vbuf.variance = buffers.variance;
163
164 tatami_stats::VarianceOptions vopt;
165 vopt.num_threads = options.num_threads;
166 tatami_stats::variance(true, mat, vbuf, vopt);
167
169 auto fopt = options.fit_variance_trend_options;
170 fopt.num_threads = options.num_threads;
171
172 if (buffers.fitted != NULL && buffers.residual != NULL) {
173 const auto NR = mat.nrow();
174 if (mat.ncol() >= 2) {
175 fit_variance_trend(NR, buffers.mean, buffers.variance, buffers.fitted, buffers.residual, work, fopt);
176 } else {
177 std::fill_n(buffers.fitted, NR, std::numeric_limits<double>::quiet_NaN());
178 std::fill_n(buffers.residual, NR, std::numeric_limits<double>::quiet_NaN());
179 }
180 }
181}
182
187template<typename Stat_>
192 ModelGeneVariancesResults() = default;
193
194 ModelGeneVariancesResults(const std::size_t ngenes, const bool trend) :
195 mean(sanisizer::cast<I<decltype(mean.size())> >(ngenes)
196#ifdef SCRAN_VARIANCES_TEST_INIT
197 , SCRAN_VARIANCES_TEST_INIT
198#endif
199 ),
200 variance(sanisizer::cast<I<decltype(variance.size())> >(ngenes)
201#ifdef SCRAN_VARIANCES_TEST_INIT
202 , SCRAN_VARIANCES_TEST_INIT
203#endif
204 ),
205 fitted(sanisizer::cast<I<decltype(fitted.size())> >(trend ? ngenes : 0)
206#ifdef SCRAN_VARIANCES_TEST_INIT
207 , SCRAN_VARIANCES_TEST_INIT
208#endif
209 ),
210 residual(sanisizer::cast<I<decltype(residual.size())> >(trend ? ngenes : 0)
211#ifdef SCRAN_VARIANCES_TEST_INIT
212 , SCRAN_VARIANCES_TEST_INIT
213#endif
214 )
215 {}
223 std::vector<Stat_> mean;
224
228 std::vector<Stat_> variance;
229
235 std::vector<Stat_> fitted;
236
242 std::vector<Stat_> residual;
243};
244
258template<typename Stat_ = double, typename Value_, typename Index_>
260 ModelGeneVariancesResults<Stat_> output(mat.nrow(), options.trend); // cast is safe, as any tatami Index_ can always fit into a size_t.
261
263 buffers.mean = output.mean.data();
264 buffers.variance = output.variance.data();
265
266 if (options.trend) {
267 buffers.fitted = output.fitted.data();
268 buffers.residual = output.residual.data();
269 } else {
270 buffers.fitted = NULL;
271 buffers.residual = NULL;
272 }
273
274 model_gene_variances(mat, std::move(buffers), options);
275 return output;
276}
277
282template<typename Stat_>
288 std::vector<ModelGeneVariancesBuffers<Stat_> > per_block;
289
300};
301
306template<typename Stat_>
312
313 ModelGeneVariancesBlockedResults(const std::size_t ngenes, const std::size_t nblocks, const bool do_average, const bool do_trend) :
314 average(do_average ? ngenes : 0, do_trend)
315 {
316 per_block.reserve(nblocks);
317 for (I<decltype(nblocks)> b = 0; b < nblocks; ++b) {
318 per_block.emplace_back(ngenes, do_trend);
319 }
320 }
328 std::vector<ModelGeneVariancesResults<Stat_> > per_block;
329
335};
336
340template<typename Stat_, typename Index_>
341void extract_blocked_weights(
342 const std::size_t num_blocks,
343 const std::vector<Stat_>& block_weights,
344 const std::vector<Index_>& block_sizes,
345 const Index_ min_size,
346 std::vector<Stat_>& tmp_weights
347) {
348 assert(sanisizer::is_equal(num_blocks, block_weights.size()));
349 assert(sanisizer::is_equal(num_blocks, block_sizes.size()));
350 tmp_weights.clear();
351 for (std::size_t b = 0; b < num_blocks; ++b) {
352 if (block_sizes[b] < min_size) { // skip blocks with insufficient cells.
353 continue;
354 }
355 tmp_weights.push_back(block_weights[b]);
356 }
357}
358
359template<typename Stat_, typename Index_, class Function_>
360void extract_blocked_pointers(
361 const std::size_t num_blocks,
362 const std::vector<ModelGeneVariancesBuffers<Stat_> >& per_block,
363 const std::vector<Index_>& block_sizes,
364 const Index_ min_size,
365 const Function_ fun,
366 std::vector<Stat_*>& tmp_pointers
367) {
368 assert(sanisizer::is_equal(num_blocks, per_block.size()));
369 assert(sanisizer::is_equal(num_blocks, block_sizes.size()));
370 tmp_pointers.clear();
371 for (std::size_t b = 0; b < num_blocks; ++b) {
372 if (block_sizes[b] < min_size) { // skip blocks with insufficient cells.
373 continue;
374 }
375 tmp_pointers.push_back(fun(per_block[b]));
376 }
377}
407template<typename Value_, typename Index_, typename Block_, typename Stat_>
410 const Block_* const block,
411 const std::size_t num_blocks,
413 const ModelGeneVariancesOptions& options
414) {
415 if (!sanisizer::is_equal(num_blocks, buffers.per_block.size())) {
416 throw std::runtime_error("length of 'buffers.per_block' is not equal to 'num_blocks'");
417 }
418 assert(mat.ncol() == 0 || sanisizer::is_less_than(*std::max_element(block, block + mat.ncol()), num_blocks));
419
420 tatami_stats::GroupRssBuffers<Stat_, Index_> vbuf;
421 vbuf.mean.reserve(num_blocks);
422 vbuf.rss.reserve(num_blocks);
423 for (std::size_t b = 0; b < num_blocks; ++b) {
424 vbuf.mean.push_back(buffers.per_block[b].mean);
425 vbuf.rss.push_back(buffers.per_block[b].variance);
426 }
427
428 auto block_sizes = sanisizer::create<std::vector<Index_> >(num_blocks);
429 vbuf.count = block_sizes.data();
430
431 // Using group_rss() instead of group_variance(), as the latter doesn't pass back the block sizes yet.
432 tatami_stats::GroupRssOptions vopt;
433 vopt.num_threads = options.num_threads;
434 tatami_stats::group_rss(true, mat, block, num_blocks, vbuf, vopt);
435
436 const auto NR = mat.nrow();
437 for (std::size_t b = 0; b < num_blocks; ++b) {
438 quickstats::rss_to_variance(NR, block_sizes[b], vbuf.rss[b]);
439 }
440
442 auto fopt = options.fit_variance_trend_options;
443 fopt.num_threads = options.num_threads;
444 bool all_trends_fitted = true;
445
446 for (std::size_t b = 0; b < num_blocks; ++b) {
447 const auto& current = buffers.per_block[b];
448 if (current.fitted == NULL || current.residual == NULL) {
449 all_trends_fitted = false;
450 continue;
451 }
452 if (block_sizes[b] >= 2) {
453 fit_variance_trend(NR, current.mean, current.variance, current.fitted, current.residual, work, fopt);
454 } else {
455 std::fill_n(current.fitted, NR, std::numeric_limits<double>::quiet_NaN());
456 std::fill_n(current.residual, NR, std::numeric_limits<double>::quiet_NaN());
457 }
458 }
459
460 const auto ave_means = buffers.average.mean;
461 const auto ave_variances = buffers.average.variance;
462 const auto ave_fitted = buffers.average.fitted;
463 const auto ave_residuals = buffers.average.residual;
464
465 if ((ave_fitted || ave_residuals) && !all_trends_fitted) {
466 throw std::runtime_error("cannot compute average fitted values/residuals without per-block trend fits");
467 }
468
469 std::vector<Stat_*> tmp_pointers;
470 tmp_pointers.reserve(num_blocks);
471
472 if (options.block_average_policy == BlockAveragePolicy::MEAN) {
473 const auto block_weight = scran_blocks::compute_weights<Stat_>(block_sizes, options.block_weight_policy, options.variable_block_weight_parameters);
474 std::vector<Stat_> tmp_weights;
475 tmp_weights.reserve(num_blocks);
476
477 if (ave_means) {
478 extract_blocked_weights(num_blocks, block_weight, block_sizes, static_cast<Index_>(1), tmp_weights);
479 extract_blocked_pointers(num_blocks, buffers.per_block, block_sizes, static_cast<Index_>(1), [](const auto& x) -> Stat_* { return x.mean; }, tmp_pointers);
480 scran_blocks::parallel_weighted_means(NR, tmp_pointers, tmp_weights.data(), ave_means, /* skip_nan = */ false);
481 }
482
483 // Skip blocks without enough cells to compute the variance.
484 extract_blocked_weights(num_blocks, block_weight, block_sizes, static_cast<Index_>(2), tmp_weights);
485
486 if (ave_variances) {
487 extract_blocked_pointers(num_blocks, buffers.per_block, block_sizes, static_cast<Index_>(2), [](const auto& x) -> Stat_* { return x.variance; }, tmp_pointers);
488 scran_blocks::parallel_weighted_means(NR, tmp_pointers, tmp_weights.data(), ave_variances, /* skip_nan = */ false);
489 }
490
491 if (ave_fitted) {
492 extract_blocked_pointers(num_blocks, buffers.per_block, block_sizes, static_cast<Index_>(2), [](const auto& x) -> Stat_* { return x.fitted; }, tmp_pointers);
493 scran_blocks::parallel_weighted_means(NR, tmp_pointers, tmp_weights.data(), ave_fitted, /* skip_nan = */ false);
494 }
495
496 if (ave_residuals) {
497 extract_blocked_pointers(num_blocks, buffers.per_block, block_sizes, static_cast<Index_>(2), [](const auto& x) -> Stat_* { return x.residual; }, tmp_pointers);
498 scran_blocks::parallel_weighted_means(NR, tmp_pointers, tmp_weights.data(), ave_residuals, /* skip_nan = */ false);
499 }
500
501 } else if (options.block_average_policy == BlockAveragePolicy::QUANTILE) {
502 if (ave_means) {
503 extract_blocked_pointers(num_blocks, buffers.per_block, block_sizes, static_cast<Index_>(1), [](const auto& x) -> Stat_* { return x.mean; }, tmp_pointers);
504 scran_blocks::parallel_quantiles(NR, tmp_pointers, options.block_quantile, ave_means, /* skip_nan = */ false);
505 }
506
507 // Again, skip blocks without enough cells to compute the variance.
508
509 if (ave_variances) {
510 extract_blocked_pointers(num_blocks, buffers.per_block, block_sizes, static_cast<Index_>(2), [](const auto& x) -> Stat_* { return x.variance; }, tmp_pointers);
511 scran_blocks::parallel_quantiles(NR, tmp_pointers, options.block_quantile, ave_variances, /* skip_nan = */ false);
512 }
513
514 if (ave_fitted) {
515 extract_blocked_pointers(num_blocks, buffers.per_block, block_sizes, static_cast<Index_>(2), [](const auto& x) -> Stat_* { return x.fitted; }, tmp_pointers);
516 scran_blocks::parallel_quantiles(NR, tmp_pointers, options.block_quantile, ave_fitted, /* skip_nan = */ false);
517 }
518
519 if (ave_residuals) {
520 extract_blocked_pointers(num_blocks, buffers.per_block, block_sizes, static_cast<Index_>(2), [](const auto& x) -> Stat_* { return x.residual; }, tmp_pointers);
521 scran_blocks::parallel_quantiles(NR, tmp_pointers, options.block_quantile, ave_residuals, /* skip_nan = */ false);
522 }
523 }
524}
525
543template<typename Stat_ = double, typename Value_, typename Index_, typename Block_>
546 const Block_* const block,
547 const std::size_t num_blocks,
548 const ModelGeneVariancesOptions& options
549) {
550 const bool do_average = options.compute_average /* for back-compatibility */ && options.block_average_policy != BlockAveragePolicy::NONE;
552 mat.nrow(), // cast is safe, any tatami Index_ can always fit into a size_t.
553 num_blocks,
554 do_average,
555 options.trend
556 );
557
559 sanisizer::resize(buffers.per_block, num_blocks);
560 for (std::size_t b = 0; b < num_blocks; ++b) {
561 auto& current = buffers.per_block[b];
562 current.mean = output.per_block[b].mean.data();
563 current.variance = output.per_block[b].variance.data();
564
565 if (options.trend) {
566 current.fitted = output.per_block[b].fitted.data();
567 current.residual = output.per_block[b].residual.data();
568 } else {
569 current.fitted = NULL;
570 current.residual = NULL;
571 }
572 }
573
574 if (!do_average) {
575 buffers.average.mean = NULL;
576 buffers.average.variance = NULL;
577 buffers.average.fitted = NULL;
578 buffers.average.residual = NULL;
579 } else {
580 buffers.average.mean = output.average.mean.data();
581 buffers.average.variance = output.average.variance.data();
582
583 if (options.trend) {
584 buffers.average.fitted = output.average.fitted.data();
585 buffers.average.residual = output.average.residual.data();
586 } else {
587 buffers.average.fitted = NULL;
588 buffers.average.residual = NULL;
589 }
590 }
591
592 model_gene_variances_blocked(mat, block, num_blocks, buffers, options);
593 return output;
594}
595
596}
597
598#endif
virtual Index_ ncol() const=0
virtual Index_ nrow() const=0
Fit a mean-variance trend to log-count data.
void compute_weights(const std::size_t num_blocks, const Size_ *const sizes, const WeightPolicy policy, const VariableWeightParameters &variable, Weight_ *const weights)
void parallel_weighted_means(const std::size_t n, std::vector< Stat_ * > in, const Weight_ *const w, Output_ *const out, const bool skip_nan)
void parallel_quantiles(const std::size_t n, const std::vector< Stat_ * > &in, const double quantile, Output_ *const out, const bool skip_nan)
Variance modelling for single-cell expression data.
Definition choose_highly_variable_genes.hpp:15
void model_gene_variances(const tatami::Matrix< Value_, Index_ > &mat, const ModelGeneVariancesBuffers< Stat_ > buffers, const ModelGeneVariancesOptions &options)
Definition model_gene_variances.hpp:155
void fit_variance_trend(const std::size_t n, const Float_ *const mean, const Float_ *const variance, Float_ *const fitted, Float_ *const residual, FitVarianceTrendWorkspace< Float_ > &workspace, const FitVarianceTrendOptions &options)
Definition fit_variance_trend.hpp:149
void model_gene_variances_blocked(const tatami::Matrix< Value_, Index_ > &mat, const Block_ *const block, const std::size_t num_blocks, const ModelGeneVariancesBlockedBuffers< Stat_ > &buffers, const ModelGeneVariancesOptions &options)
Definition model_gene_variances.hpp:408
BlockAveragePolicy
Definition model_gene_variances.hpp:34
Options for fit_variance_trend().
Definition fit_variance_trend.hpp:24
int num_threads
Definition fit_variance_trend.hpp:96
Workspace for fit_variance_trend().
Definition fit_variance_trend.hpp:105
Buffers for model_gene_variances_blocked().
Definition model_gene_variances.hpp:283
ModelGeneVariancesBuffers< Stat_ > average
Definition model_gene_variances.hpp:299
std::vector< ModelGeneVariancesBuffers< Stat_ > > per_block
Definition model_gene_variances.hpp:288
Results of model_gene_variances_blocked().
Definition model_gene_variances.hpp:307
std::vector< ModelGeneVariancesResults< Stat_ > > per_block
Definition model_gene_variances.hpp:328
ModelGeneVariancesResults< Stat_ > average
Definition model_gene_variances.hpp:334
Buffers for model_gene_variances() and friends.
Definition model_gene_variances.hpp:111
Stat_ * mean
Definition model_gene_variances.hpp:115
Stat_ * fitted
Definition model_gene_variances.hpp:127
Stat_ * variance
Definition model_gene_variances.hpp:120
Stat_ * residual
Definition model_gene_variances.hpp:134
Options for model_gene_variances() and friends.
Definition model_gene_variances.hpp:39
FitVarianceTrendOptions fit_variance_trend_options
Definition model_gene_variances.hpp:51
double block_quantile
Definition model_gene_variances.hpp:93
bool trend
Definition model_gene_variances.hpp:45
BlockAveragePolicy block_average_policy
Definition model_gene_variances.hpp:58
scran_blocks::VariableWeightParameters variable_block_weight_parameters
Definition model_gene_variances.hpp:77
int num_threads
Definition model_gene_variances.hpp:99
scran_blocks::WeightPolicy block_weight_policy
Definition model_gene_variances.hpp:70
Results of model_gene_variances().
Definition model_gene_variances.hpp:188
std::vector< Stat_ > fitted
Definition model_gene_variances.hpp:235
std::vector< Stat_ > variance
Definition model_gene_variances.hpp:228
std::vector< Stat_ > residual
Definition model_gene_variances.hpp:242
std::vector< Stat_ > mean
Definition model_gene_variances.hpp:223