tatami_stats
Matrix statistics for tatami
Loading...
Searching...
No Matches
group_rss.hpp
Go to the documentation of this file.
1#ifndef TATAMI_STATS_SKIP_NAN_GROUP_RSS_HPP
2#define TATAMI_STATS_SKIP_NAN_GROUP_RSS_HPP
3
4#include <vector>
5#include <algorithm>
6#include <cstddef>
7#include <optional>
8#include <cassert>
9#include <limits>
10#include <cmath>
11
12#include "tatami/tatami.hpp"
13#include "sanisizer/sanisizer.hpp"
14#include "quickstats/quickstats.hpp"
15
16#include "../utils.hpp"
17#include "../group_rss.hpp"
18
25namespace tatami_stats {
26
27namespace skip_nan {
28
37 int num_threads = 1;
38};
39
48template<typename Output_, typename Count_>
55 std::vector<Output_*> mean;
56
62 std::vector<Output_*> rss;
63
69 std::vector<Count_*> count;
70};
71
75template<typename Value_, typename Index_, typename Group_, typename Output_, typename Count_>
76void group_rss_direct(
77 const bool row,
79 const Group_* const group,
80 const std::size_t num_groups,
82 const GroupRssOptions& opt
83) {
84 const auto dim = (row ? mat.nrow() : mat.ncol());
85 const auto otherdim = (row ? mat.ncol() : mat.nrow());
86
87 if (mat.sparse()) {
88 auto full_group_sizes = sanisizer::create<std::vector<Index_> >(num_groups);
89 for (Index_ i = 0; i < otherdim; ++i) {
90 full_group_sizes[group[i]] += 1;
91 }
92
93 tatami::parallelize([&](int, Index_ s, Index_ l) -> void {
94 auto ext = tatami::consecutive_extractor<true>(mat, row, s, l);
97 auto cur_means = sanisizer::create<std::vector<Output_> >(num_groups);
98 auto cur_rss = sanisizer::create<std::vector<Output_> >(num_groups);
99 auto cur_non_zeros = sanisizer::create<std::vector<Index_> >(num_groups);
100 auto cur_sizes = sanisizer::create<std::vector<Index_> >(num_groups);
101
102 for (Index_ x = 0; x < l; ++x) {
103 auto range = ext->fetch(vbuffer.data(), ibuffer.data());
104
105 // Computing the mean first.
106 for (Index_ i = 0; i < range.number; ++i) {
107 const auto val = range.value[i];
108 const auto b = group[range.index[i]];
109 if (!std::isnan(val)) {
110 ++cur_non_zeros[b];
111 cur_means[b] += val;
112 } else {
113 ++cur_sizes[b];
114 }
115 }
116 for (std::size_t g = 0; g < num_groups; ++g) {
117 const auto actual_size = full_group_sizes[g] - cur_sizes[g];
118 cur_sizes[g] = actual_size;
119 output.count[g][s + x] = actual_size;
120 }
121 group_rss_finish_means(num_groups, cur_sizes.data(), cur_means, static_cast<Index_>(s + x), output.mean);
122
123 // Now computing the RSS.
124 for (Index_ i = 0; i < range.number; ++i) {
125 const auto val = range.value[i];
126 if (!std::isnan(val)) {
127 const auto g = group[range.index[i]];
128 const auto delta = val - cur_means[g];
129 cur_rss[g] += delta * delta;
130 }
131 }
132 for (std::size_t g = 0; g < num_groups; ++g) {
133 if (cur_sizes[g] > 0) { // preserve RSS = 0 for empty groups, otherwise NaN mean causes problems.
134 const Output_ my_rss = cur_rss[g] + cur_means[g] * cur_means[g] * (cur_sizes[g] - cur_non_zeros[g]);
135 output.rss[g][s + x] = my_rss;
136 } else {
137 output.rss[g][s + x] = 0;
138 }
139 }
140
141 std::fill(cur_means.begin(), cur_means.end(), 0);
142 std::fill(cur_rss.begin(), cur_rss.end(), 0);
143 std::fill(cur_non_zeros.begin(), cur_non_zeros.end(), 0);
144 std::fill(cur_sizes.begin(), cur_sizes.end(), 0);
145 }
146 }, dim, opt.num_threads);
147
148 } else {
149 tatami::parallelize([&](int, Index_ s, Index_ l) -> void {
150 auto ext = tatami::consecutive_extractor<false>(mat, row, s, l);
152 auto cur_means = sanisizer::create<std::vector<Output_> >(num_groups);
153 auto cur_rss = sanisizer::create<std::vector<Output_> >(num_groups);
154 auto cur_sizes = sanisizer::create<std::vector<Index_> >(num_groups);
155
156 for (Index_ x = 0; x < l; ++x) {
157 auto ptr = ext->fetch(buffer.data());
158
159 // Computing the mean first.
160 for (Index_ j = 0; j < otherdim; ++j) {
161 const auto val = ptr[j];
162 if (!std::isnan(val)) {
163 const auto g = group[j];
164 cur_means[g] += val;
165 ++cur_sizes[g];
166 }
167 }
168 for (std::size_t g = 0; g < num_groups; ++g) {
169 output.count[g][s + x] = cur_sizes[g];
170 }
171 group_rss_finish_means(num_groups, cur_sizes.data(), cur_means, static_cast<Index_>(s + x), output.mean);
172
173 // Now computing the RSS.
174 for (Index_ j = 0; j < otherdim; ++j) {
175 const auto val = ptr[j];
176 if (!std::isnan(val)) {
177 const auto g = group[j];
178 const auto delta = val - cur_means[g];
179 cur_rss[g] += delta * delta;
180 }
181 }
182 for (std::size_t g = 0; g < num_groups; ++g) {
183 output.rss[g][s + x] = cur_rss[g];
184 }
185
186 std::fill(cur_means.begin(), cur_means.end(), 0);
187 std::fill(cur_rss.begin(), cur_rss.end(), 0);
188 std::fill(cur_sizes.begin(), cur_sizes.end(), 0);
189 }
190 }, dim, opt.num_threads);
191 }
192}
193
194template<typename Value_, typename Index_, typename Group_, typename Output_, typename Count_>
195void group_rss_running(
196 const bool row,
198 const Group_* const group,
199 const std::size_t num_groups,
200 GroupRssBuffers<Output_, Count_>& output,
201 const GroupRssOptions& opt
202) {
203 const auto dim = (row ? mat.nrow() : mat.ncol());
204 const auto otherdim = (row ? mat.ncol() : mat.nrow());
205 const bool is_sparse = mat.is_sparse();
206
207 if (otherdim == 0) {
208 for (std::size_t g = 0; g < num_groups; ++g) {
209 std::fill_n(output.mean[g], dim, std::numeric_limits<Output_>::quiet_NaN());
210 std::fill_n(output.rss[g], dim, 0);
211 std::fill_n(output.count[g], dim, 0);
212 }
213 return;
214 }
215
216 const bool do_parallel = opt.num_threads > 1;
217 std::optional<std::vector<std::optional<std::vector<std::vector<Output_> > > > > all_partial_mean, all_partial_rss;
218 std::optional<std::vector<std::optional<std::vector<std::vector<Count_> > > > > all_partial_count;
219 if (do_parallel) {
220 // -1, as we'll repurpose the RSS output buffer to store the partial RSS of the first thread.
221 all_partial_rss.emplace(sanisizer::cast<I<decltype(all_partial_rss->size())> >(opt.num_threads - 1));
222 all_partial_mean.emplace(sanisizer::cast<I<decltype(all_partial_mean->size())> >(opt.num_threads));
223 all_partial_count.emplace(sanisizer::cast<I<decltype(all_partial_count->size())> >(opt.num_threads));
224 }
225
226 for (std::size_t g = 0; g < num_groups; ++g) {
227 std::fill_n(output.mean[g], dim, 0);
228 std::fill_n(output.rss[g], dim, 0);
229 std::fill_n(output.count[g], dim, 0);
230 }
231
232 const int nused = tatami::parallelize([&](int thread, Index_ s, Index_ l) -> void {
233 Output_** mean_ptrs;
234 Output_** rss_ptrs;
235 std::optional<std::vector<Output_*> > tmp_mean_ptrs, tmp_rss_ptrs;
236 std::optional<std::vector<std::vector<Output_> > > cur_mean, cur_rss;
237 Count_** count_ptrs;
238 std::optional<std::vector<Count_*> > tmp_count_ptrs;
239 std::optional<std::vector<std::vector<Count_> > > cur_count;
240
241 if (!do_parallel) {
242 // Storing mean and RSS directly in the output vector to cut down two allocations if we're not working in parallel.
243 mean_ptrs = output.mean.data();
244 rss_ptrs = output.rss.data();
245 count_ptrs = output.count.data();
246 } else {
247 // Storing the partial RSS directly in the output vectors to save ourselves an allocation if we're in the first thread.
248 // We can't do the same for the mean, though, as we need to keep the partial mean and the global mean separate for the reduction.
249 cur_mean.emplace(sanisizer::cast<I<decltype(cur_mean->size())> >(num_groups));
250 tmp_mean_ptrs.emplace(sanisizer::cast<I<decltype(tmp_mean_ptrs->size())> >(num_groups));
251 for (std::size_t g = 0; g < num_groups; ++g) {
252 tatami::resize_container_to_Index_size((*cur_mean)[g], dim);
253 (*tmp_mean_ptrs)[g] = (*cur_mean)[g].data();
254 }
255 mean_ptrs = tmp_mean_ptrs->data();
256
257 cur_count.emplace(sanisizer::cast<I<decltype(cur_count->size())> >(num_groups));
258 tmp_count_ptrs.emplace(sanisizer::cast<I<decltype(tmp_count_ptrs->size())> >(num_groups));
259 for (std::size_t g = 0; g < num_groups; ++g) {
260 tatami::resize_container_to_Index_size((*cur_count)[g], dim);
261 (*tmp_count_ptrs)[g] = (*cur_count)[g].data();
262 }
263 count_ptrs = tmp_count_ptrs->data();
264
265 if (thread == 0) {
266 rss_ptrs = output.rss.data();
267 } else {
268 cur_rss.emplace(sanisizer::cast<I<decltype(cur_rss->size())> >(num_groups));
269 tmp_rss_ptrs.emplace(sanisizer::cast<I<decltype(tmp_rss_ptrs->size())> >(num_groups));
270 for (std::size_t g = 0; g < num_groups; ++g) {
271 tatami::resize_container_to_Index_size((*cur_rss)[g], dim);
272 (*tmp_rss_ptrs)[g] = (*cur_rss)[g].data();
273 }
274 rss_ptrs = tmp_rss_ptrs->data();
275 }
276 }
277
278 if (is_sparse) {
279 auto ext = tatami::consecutive_extractor<true>(mat, !row, s, l);
282
283 auto nonzeros = sanisizer::create<std::vector<std::vector<Count_> > >(num_groups);
284 std::vector<quickstats::RssRunningSparseSkip<Count_, Value_, Output_> > runners;
285 sanisizer::reserve(runners, num_groups);
286 for (std::size_t g = 0; g < num_groups; ++g) {
288 runners.emplace_back(dim, mean_ptrs[g], rss_ptrs[g], nonzeros[g].data(), count_ptrs[g]);
289 }
290
291 for (Index_ x = 0; x < l; ++x) {
292 auto out = ext->fetch(vbuffer.data(), ibuffer.data());
293 runners[group[x + s]].add(
294 out.number,
295 out.value,
296 out.index,
297 [](const std::size_t, const Value_ val) -> bool {
298 return std::isnan(val);
299 }
300 );
301 }
302
303 for (std::size_t g = 0; g < num_groups; ++g) {
304 runners[g].finish();
305 }
306
307 } else {
308 auto ext = tatami::consecutive_extractor<false>(mat, !row, s, l);
310
311 std::vector<quickstats::RssRunningDenseSkip<Count_, Value_, Output_> > runners;
312 sanisizer::reserve(runners, num_groups);
313 for (std::size_t g = 0; g < num_groups; ++g) {
314 runners.emplace_back(dim, mean_ptrs[g], rss_ptrs[g], count_ptrs[g]);
315 }
316
317 for (Index_ x = 0; x < l; ++x) {
318 auto out = ext->fetch(buffer.data());
319 runners[group[x + s]].add(
320 out,
321 [](const std::size_t, const Value_ val) -> bool {
322 return std::isnan(val);
323 }
324 );
325 }
326
327 for (std::size_t g = 0; g < num_groups; ++g) {
328 runners[g].finish();
329 }
330 }
331
332 if (do_parallel) {
333 (*all_partial_count)[thread] = std::move(cur_count);
334 (*all_partial_mean)[thread] = std::move(cur_mean);
335 if (thread > 0) {
336 (*all_partial_rss)[thread - 1] = std::move(cur_rss);
337 }
338 }
339 }, otherdim, opt.num_threads);
340 assert(nused > 0);
341
342 if (do_parallel) {
343 const auto& ap_mean = *all_partial_mean;
344 const auto& ap_rss = *all_partial_rss;
345 const auto& ap_count = *all_partial_count;
346
347 for (std::size_t g = 0; g < num_groups; ++g) {
348 const auto cur_global_count = output.count[g];
349 for (int u = 0; u < nused; ++u) {
350 const auto& cur_count = (*(ap_count[u]))[g];
351 for (Index_ d = 0; d < dim; ++d) {
352 cur_global_count[d] += cur_count[d];
353 }
354 }
355 }
356
357 // Computing the global mean.
358 for (std::size_t g = 0; g < num_groups; ++g) {
359 const auto cur_global_count = output.count[g];
360 const auto cur_global_mean = output.mean[g];
361
362 for (int u = 0; u < nused; ++u) {
363 const auto& cur_mean = (*(ap_mean[u]))[g];
364 const auto& cur_count = (*(ap_count[u]))[g];
365 for (Index_ d = 0; d < dim; ++d) {
366 if (cur_count[d] > 0) {
367 const auto mult = static_cast<Output_>(cur_count[d]) / static_cast<Output_>(cur_global_count[d]);
368 cur_global_mean[d] += cur_mean[d] * mult;
369 }
370 }
371 }
372
373 for (Index_ d = 0; d < dim; ++d) {
374 if (cur_global_count[d] == 0) {
375 cur_global_mean[d] = std::numeric_limits<Output_>::quiet_NaN();
376 }
377 }
378 }
379
380 // Combining the RSS. We need to use the safe variant of recenter_rss(), just to protect against the
381 // case where a group has no observations within a particular thread.
382 for (std::size_t g = 0; g < num_groups; ++g) {
383 const auto cur_global_mean = output.mean[g];
384 const auto cur_output = output.rss[g];
385 for (int u = 0; u < nused; ++u) {
386 const auto& cur_mean = (*(ap_mean[u]))[g];
387 const auto& cur_count = (*(ap_count[u]))[g];
388 if (u == 0) {
389 for (Index_ d = 0; d < dim; ++d) {
390 cur_output[d] = quickstats::recenter_rss(cur_count[d], cur_output[d], cur_mean[d], cur_global_mean[d]);
391 }
392 } else {
393 const auto& cur_rss = (*(ap_rss[u - 1]))[g];
394 for (Index_ d = 0; d < dim; ++d) {
395 cur_output[d] += quickstats::recenter_rss(cur_count[d], cur_rss[d], cur_mean[d], cur_global_mean[d]);
396 }
397 }
398 }
399 }
400 }
401}
427template<typename Value_, typename Index_, typename Group_, typename Output_, typename Count_>
429 bool row,
431 const Group_* const group,
432 const std::size_t num_groups,
434 const GroupRssOptions& opt
435) {
436 assert(sanisizer::is_equal(num_groups, output.mean.size()));
437 assert(sanisizer::is_equal(num_groups, output.rss.size()));
438 assert(sanisizer::is_equal(num_groups, output.count.size()));
439 if (mat.prefer_rows() == row) {
440 group_rss_direct(row, mat, group, num_groups, output, opt);
441 } else {
442 group_rss_running(row, mat, group, num_groups, output, opt);
443 }
444}
445
454template<typename Output_, typename Count_>
461 std::vector<std::vector<Output_> > mean;
462
468 std::vector<std::vector<Output_> > rss;
469
475 std::vector<std::vector<Count_> > count;
476};
477
499template<typename Output_, typename Count_, typename Value_, typename Index_, typename Group_>
501 bool row,
503 const Group_* const group,
504 const std::size_t num_groups,
505 const GroupRssOptions& opt
506) {
508 sanisizer::resize(output.mean, num_groups);
509 sanisizer::resize(output.rss, num_groups);
510 sanisizer::resize(output.count, num_groups);
511
513 sanisizer::resize(buffers.mean, num_groups);
514 sanisizer::resize(buffers.rss, num_groups);
515 sanisizer::resize(buffers.count, num_groups);
516
517 const auto dim = (row ? mat.nrow() : mat.ncol());
518 for (std::size_t g = 0; g < num_groups; ++g) {
520#ifdef TATAMI_STATS_TEST_DIRTY
521 , -1
522#endif
523 );
524 buffers.mean[g] = output.mean[g].data();
525
527#ifdef TATAMI_STATS_TEST_DIRTY
528 , -1
529#endif
530 );
531 buffers.rss[g] = output.rss[g].data();
532
534#ifdef TATAMI_STATS_TEST_DIRTY
535 , -1
536#endif
537 );
538 buffers.count[g] = output.count[g].data();
539 }
540
541 group_rss(row, mat, group, num_groups, buffers, opt);
542 return output;
543}
544
545}
546
547}
548
549#endif
virtual Index_ ncol() const=0
virtual Index_ nrow() const=0
virtual bool prefer_rows() const=0
virtual bool is_sparse() const=0
virtual std::unique_ptr< MyopicSparseExtractor< Value_, Index_ > > sparse(bool row, const Options &opt) const=0
Compute group-wise residual sum of squares from a tatami::Matrix.
void group_rss(bool row, const tatami::Matrix< Value_, Index_ > &mat, const Group_ *const group, const std::size_t num_groups, GroupRssBuffers< Output_, Count_ > &output, const GroupRssOptions &opt)
Definition group_rss.hpp:428
Functions to compute statistics from a tatami::Matrix.
Definition count.hpp:20
void range(bool row, const tatami::Matrix< Value_, Index_ > &mat, RangeBuffers< Output_ > &output, const RangeOptions &opt)
Definition range.hpp:400
void resize_container_to_Index_size(Container_ &container, const Index_ x, Args_ &&... args)
int parallelize(Function_ fun, const Index_ tasks, const int workers)
Container_ create_container_of_Index_size(const Index_ x, Args_ &&... args)
auto consecutive_extractor(const Matrix< Value_, Index_ > &matrix, const bool row, const Index_ iter_start, const Index_ iter_length, Args_ &&... args)
Result buffers for skip_nan::group_rss().
Definition group_rss.hpp:49
std::vector< Output_ * > mean
Definition group_rss.hpp:55
std::vector< Output_ * > rss
Definition group_rss.hpp:62
std::vector< Count_ * > count
Definition group_rss.hpp:69
Options for skip_nan::group_rss().
Definition group_rss.hpp:32
int num_threads
Definition group_rss.hpp:37
Results of skip_nan::group_rss().
Definition group_rss.hpp:455
std::vector< std::vector< Output_ > > rss
Definition group_rss.hpp:468
std::vector< std::vector< Count_ > > count
Definition group_rss.hpp:475
std::vector< std::vector< Output_ > > mean
Definition group_rss.hpp:461