tatami_stats
Matrix statistics for tatami
Loading...
Searching...
No Matches
group_rss.hpp
Go to the documentation of this file.
1#ifndef TATAMI_STATS_GROUP_RSS_HPP
2#define TATAMI_STATS_GROUP_RSS_HPP
3
4#include "utils.hpp"
5
6#include <vector>
7#include <algorithm>
8#include <cstddef>
9#include <optional>
10#include <cassert>
11#include <limits>
12#include <cmath>
13
14#include "tatami/tatami.hpp"
15#include "sanisizer/sanisizer.hpp"
16#include "quickstats/quickstats.hpp"
17
24namespace tatami_stats {
25
34 int num_threads = 1;
35};
36
44template<typename Output_, typename Count_>
51 std::vector<Output_*> mean;
52
58 std::vector<Output_*> rss;
59
64 Count_* count;
65};
66
70template<typename Count_, typename Output_, typename Index_>
71void group_rss_finish_means(
72 const std::size_t num_groups,
73 const Count_* const group_size,
74 std::vector<Output_>& means,
75 const Index_ i,
76 std::vector<Output_*>& output_means
77) {
78 for (std::size_t b = 0; b < num_groups; ++b) {
79 if (group_size[b]) {
80 means[b] /= group_size[b];
81 } else {
82 means[b] = std::numeric_limits<Output_>::quiet_NaN();
83 }
84 output_means[b][i] = means[b];
85 }
86}
87
88template<typename Value_, typename Index_, typename Group_, typename Output_, typename Count_>
89void group_rss_direct(
90 const bool row,
92 const Group_* const group,
93 const std::size_t num_groups,
94 GroupRssBuffers<Output_, Count_>& output,
95 const GroupRssOptions& opt
96) {
97 const auto dim = (row ? mat.nrow() : mat.ncol());
98 const auto otherdim = (row ? mat.ncol() : mat.nrow());
99
100 std::fill_n(output.count, num_groups, 0);
101 for (Index_ i = 0; i < otherdim; ++i) {
102 output.count[group[i]] += 1;
103 }
104
105 if (mat.sparse()) {
106 tatami::parallelize([&](int, Index_ s, Index_ l) -> void {
107 auto ext = tatami::consecutive_extractor<true>(mat, row, s, l);
110 auto cur_means = sanisizer::create<std::vector<Output_> >(num_groups);
111 auto cur_rss = sanisizer::create<std::vector<Output_> >(num_groups);
112 auto cur_non_zeros = sanisizer::create<std::vector<Index_> >(num_groups);
113
114 for (Index_ x = 0; x < l; ++x) {
115 auto range = ext->fetch(vbuffer.data(), ibuffer.data());
116
117 // Computing the mean first.
118 for (Index_ i = 0; i < range.number; ++i) {
119 const auto g = group[range.index[i]];
120 cur_means[g] += range.value[i];
121 ++cur_non_zeros[g];
122 }
123 group_rss_finish_means(num_groups, output.count, cur_means, static_cast<Index_>(s + x), output.mean);
124
125 // Now computing the RSS.
126 for (Index_ i = 0; i < range.number; ++i) {
127 const auto g = group[range.index[i]];
128 const auto delta = range.value[i] - cur_means[g];
129 cur_rss[g] += delta * delta;
130 }
131 for (std::size_t g = 0; g < num_groups; ++g) {
132 if (output.count[g] > 0) { // preserve RSS = 0 if the group is empty, otherwise the NaN mean causes problems.
133 const Output_ my_rss = cur_rss[g] + cur_means[g] * cur_means[g] * (output.count[g] - cur_non_zeros[g]);
134 output.rss[g][s + x] = my_rss;
135 } else {
136 output.rss[g][s + x] = 0;
137 }
138 }
139
140 std::fill(cur_means.begin(), cur_means.end(), 0);
141 std::fill(cur_rss.begin(), cur_rss.end(), 0);
142 std::fill(cur_non_zeros.begin(), cur_non_zeros.end(), 0);
143 }
144 }, dim, opt.num_threads);
145
146 } else {
147 tatami::parallelize([&](int, Index_ s, Index_ l) -> void {
148 auto ext = tatami::consecutive_extractor<false>(mat, row, s, l);
150 auto cur_means = sanisizer::create<std::vector<Output_> >(num_groups);
151 auto cur_rss = sanisizer::create<std::vector<Output_> >(num_groups);
152
153 for (Index_ x = 0; x < l; ++x) {
154 auto ptr = ext->fetch(buffer.data());
155
156 // Computing the mean first.
157 for (Index_ j = 0; j < otherdim; ++j) {
158 cur_means[group[j]] += ptr[j];
159 }
160 group_rss_finish_means(num_groups, output.count, cur_means, static_cast<Index_>(s + x), output.mean);
161
162 // Now computing the RSS.
163 for (Index_ j = 0; j < otherdim; ++j) {
164 const auto g = group[j];
165 const auto delta = ptr[j] - cur_means[g];
166 cur_rss[g] += delta * delta;
167 }
168 for (std::size_t g = 0; g < num_groups; ++g) {
169 output.rss[g][s + x] = cur_rss[g];
170 }
171
172 std::fill(cur_means.begin(), cur_means.end(), 0);
173 std::fill(cur_rss.begin(), cur_rss.end(), 0);
174 }
175 }, dim, opt.num_threads);
176 }
177}
178
179template<typename Value_, typename Index_, typename Group_, typename Output_, typename Count_>
180void group_rss_running(
181 const bool row,
183 const Group_* const group,
184 const std::size_t num_groups,
185 GroupRssBuffers<Output_, Count_>& output,
186 const GroupRssOptions& opt
187) {
188 const auto dim = (row ? mat.nrow() : mat.ncol());
189 const auto otherdim = (row ? mat.ncol() : mat.nrow());
190 const bool is_sparse = mat.is_sparse();
191
192 if (otherdim == 0) {
193 std::fill_n(output.count, num_groups, 0);
194 for (std::size_t g = 0; g < num_groups; ++g) {
195 std::fill_n(output.mean[g], dim, std::numeric_limits<Output_>::quiet_NaN());
196 std::fill_n(output.rss[g], dim, 0);
197 }
198 return;
199 }
200
201 const bool do_parallel = opt.num_threads > 1;
202 std::optional<std::vector<std::optional<std::vector<std::vector<Output_> > > > > all_partial_mean, all_partial_rss;
203 std::optional<std::vector<std::optional<std::vector<Count_> > > > all_partial_count;
204 if (do_parallel) {
205 // -1, as we'll repurpose the RSS output buffer to store the partial RSS of the first thread.
206 all_partial_rss.emplace(sanisizer::cast<I<decltype(all_partial_rss->size())> >(opt.num_threads - 1));
207 all_partial_mean.emplace(sanisizer::cast<I<decltype(all_partial_mean->size())> >(opt.num_threads));
208 all_partial_count.emplace(sanisizer::cast<I<decltype(all_partial_count->size())> >(opt.num_threads));
209 }
210
211 for (std::size_t g = 0; g < num_groups; ++g) {
212 std::fill_n(output.mean[g], dim, 0);
213 std::fill_n(output.rss[g], dim, 0);
214 }
215
216 const int nused = tatami::parallelize([&](int thread, Index_ s, Index_ l) -> void {
217 Output_** mean_ptrs;
218 Output_** rss_ptrs;
219 std::optional<std::vector<Output_*> > tmp_mean_ptrs, tmp_rss_ptrs;
220 std::optional<std::vector<std::vector<Output_> > > cur_mean, cur_rss;
221 Count_* count_ptr;
222 std::optional<std::vector<Count_> > cur_count;
223
224 if (!do_parallel) {
225 // Storing mean and RSS directly in the output vector to cut down two allocations if we're not working in parallel.
226 mean_ptrs = output.mean.data();
227 rss_ptrs = output.rss.data();
228 count_ptr = output.count;
229 } else {
230 // Storing the partial RSS directly in the output vectors to save ourselves an allocation if we're in the first thread.
231 // 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.
232 cur_mean.emplace(sanisizer::cast<I<decltype(cur_mean->size())> >(num_groups));
233 tmp_mean_ptrs.emplace(sanisizer::cast<I<decltype(tmp_mean_ptrs->size())> >(num_groups));
234 for (std::size_t g = 0; g < num_groups; ++g) {
235 tatami::resize_container_to_Index_size((*cur_mean)[g], dim);
236 (*tmp_mean_ptrs)[g] = (*cur_mean)[g].data();
237 }
238 mean_ptrs = tmp_mean_ptrs->data();
239
240 cur_count.emplace(sanisizer::cast<I<decltype(cur_count->size())> >(num_groups));
241 count_ptr = cur_count->data();
242
243 if (thread == 0) {
244 rss_ptrs = output.rss.data();
245 } else {
246 cur_rss.emplace(sanisizer::cast<I<decltype(cur_rss->size())> >(num_groups));
247 tmp_rss_ptrs.emplace(sanisizer::cast<I<decltype(tmp_rss_ptrs->size())> >(num_groups));
248 for (std::size_t g = 0; g < num_groups; ++g) {
249 tatami::resize_container_to_Index_size((*cur_rss)[g], dim);
250 (*tmp_rss_ptrs)[g] = (*cur_rss)[g].data();
251 }
252 rss_ptrs = tmp_rss_ptrs->data();
253 }
254 }
255
256 if (is_sparse) {
257 auto ext = tatami::consecutive_extractor<true>(mat, !row, s, l);
260
261 auto nonzeros = sanisizer::create<std::vector<std::vector<Index_> > >(num_groups);
262 std::vector<quickstats::RssRunningSparse<Index_, Value_, Output_> > runners;
263 sanisizer::reserve(runners, num_groups);
264 for (std::size_t g = 0; g < num_groups; ++g) {
266 runners.emplace_back(dim, mean_ptrs[g], rss_ptrs[g], nonzeros[g].data());
267 }
268
269 for (Index_ x = 0; x < l; ++x) {
270 auto out = ext->fetch(vbuffer.data(), ibuffer.data());
271 runners[group[x + s]].add(out.number, out.value, out.index);
272 }
273
274 for (std::size_t g = 0; g < num_groups; ++g) {
275 count_ptr[g] = runners[g].num_obs();
276 runners[g].finish();
277 }
278
279 } else {
280 auto ext = tatami::consecutive_extractor<false>(mat, !row, s, l);
282
283 std::vector<quickstats::RssRunningDense<Value_, Output_> > runners;
284 sanisizer::reserve(runners, num_groups);
285 for (std::size_t g = 0; g < num_groups; ++g) {
286 runners.emplace_back(dim, mean_ptrs[g], rss_ptrs[g]);
287 }
288
289 for (Index_ x = 0; x < l; ++x) {
290 auto out = ext->fetch(buffer.data());
291 runners[group[x + s]].add(out);
292 }
293
294 for (std::size_t g = 0; g < num_groups; ++g) {
295 count_ptr[g] = runners[g].num_obs();
296 runners[g].finish();
297 }
298 }
299
300 if (do_parallel) {
301 (*all_partial_count)[thread] = std::move(cur_count);
302 (*all_partial_mean)[thread] = std::move(cur_mean);
303 if (thread > 0) {
304 (*all_partial_rss)[thread - 1] = std::move(cur_rss);
305 }
306 }
307 }, otherdim, opt.num_threads);
308 assert(nused > 0);
309
310 if (do_parallel) {
311 const auto& ap_mean = *all_partial_mean;
312 const auto& ap_rss = *all_partial_rss;
313 const auto& ap_count = *all_partial_count;
314
315 std::fill_n(output.count, num_groups, 0);
316 for (int u = 0; u < nused; ++u) {
317 const auto& cur_count = *(ap_count[u]);
318 for (std::size_t g = 0; g < num_groups; ++g) {
319 output.count[g] += cur_count[g];
320 }
321 }
322
323 // Computing the global mean.
324 for (std::size_t g = 0; g < num_groups; ++g) {
325 const auto cur_output = output.mean[g];
326 const auto cur_global_count = output.count[g];
327 if (cur_global_count == 0) {
328 std::fill_n(cur_output, dim, std::numeric_limits<Output_>::quiet_NaN());
329 continue;
330 }
331
332 for (int u = 0; u < nused; ++u) {
333 const auto cur_count = (*((*all_partial_count)[u]))[g];
334 if (cur_count == 0) {
335 continue;
336 }
337
338 const auto& cur_mean = (*(ap_mean[u]))[g];
339 const Output_ mult = static_cast<Output_>(cur_count) / static_cast<Output_>(cur_global_count);
340 for (Index_ d = 0; d < dim; ++d) {
341 cur_output[d] += cur_mean[d] * mult;
342 }
343 }
344 }
345
346 // Combining the RSS.
347 for (std::size_t g = 0; g < num_groups; ++g) {
348 const auto& cur_global = output.mean[g];
349 const auto cur_output = output.rss[g];
350
351 for (int u = 0; u < nused; ++u) {
352 const auto cur_count = (*((*all_partial_count)[u]))[g];
353 if (cur_count == 0) { // This check allows us to use the unsafe RSS centering below.
354 continue;
355 }
356
357 const auto& cur_mean = (*(ap_mean[u]))[g];
358 if (u == 0) {
359 for (Index_ d = 0; d < dim; ++d) {
360 cur_output[d] = quickstats::recenter_rss_unsafe(cur_count, cur_output[d], cur_mean[d], cur_global[d]);
361 }
362 } else {
363 const auto& cur_rss = (*(ap_rss[u - 1]))[g];
364 for (Index_ d = 0; d < dim; ++d) {
365 cur_output[d] += quickstats::recenter_rss_unsafe(cur_count, cur_rss[d], cur_mean[d], cur_global[d]);
366 }
367 }
368 }
369 }
370 }
371}
396template<typename Value_, typename Index_, typename Group_, typename Output_, typename Count_>
398 bool row,
400 const Group_* const group,
401 const std::size_t num_groups,
403 const GroupRssOptions& opt
404) {
405 assert(sanisizer::is_equal(num_groups, output.mean.size()));
406 assert(sanisizer::is_equal(num_groups, output.rss.size()));
407 if (mat.prefer_rows() == row) {
408 group_rss_direct(row, mat, group, num_groups, output, opt);
409 } else {
410 group_rss_running(row, mat, group, num_groups, output, opt);
411 }
412}
413
421template<typename Output_, typename Count_>
428 std::vector<std::vector<Output_> > mean;
429
435 std::vector<std::vector<Output_> > rss;
436
440 std::vector<Count_> count;
441};
442
463template<typename Output_, typename Count_, typename Value_, typename Index_, typename Group_>
465 bool row,
467 const Group_* const group,
468 const std::size_t num_groups,
469 const GroupRssOptions& opt
470) {
472 sanisizer::resize(output.mean, num_groups);
473 sanisizer::resize(output.rss, num_groups);
474 sanisizer::resize(output.count, num_groups);
475
477 sanisizer::resize(buffers.mean, num_groups);
478 sanisizer::resize(buffers.rss, num_groups);
479 buffers.count = output.count.data();
480
481 const auto dim = (row ? mat.nrow() : mat.ncol());
482 for (std::size_t g = 0; g < num_groups; ++g) {
484#ifdef TATAMI_STATS_TEST_DIRTY
485 , -1
486#endif
487 );
488 buffers.mean[g] = output.mean[g].data();
490#ifdef TATAMI_STATS_TEST_DIRTY
491 , -1
492#endif
493 );
494 buffers.rss[g] = output.rss[g].data();
495 }
496
497 group_rss(row, mat, group, num_groups, buffers, opt);
498 return output;
499}
500
501}
502
503#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
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 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:397
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 group_rss().
Definition group_rss.hpp:45
std::vector< Output_ * > rss
Definition group_rss.hpp:58
Count_ * count
Definition group_rss.hpp:64
std::vector< Output_ * > mean
Definition group_rss.hpp:51
Options for group_rss().
Definition group_rss.hpp:29
int num_threads
Definition group_rss.hpp:34
Results of group_rss().
Definition group_rss.hpp:422
std::vector< Count_ > count
Definition group_rss.hpp:440
std::vector< std::vector< Output_ > > rss
Definition group_rss.hpp:435
std::vector< std::vector< Output_ > > mean
Definition group_rss.hpp:428