tatami_stats
Matrix statistics for tatami
Loading...
Searching...
No Matches
group_sum.hpp
Go to the documentation of this file.
1#ifndef TATAMI_STATS_GROUPED_SUMS_HPP
2#define TATAMI_STATS_GROUPED_SUMS_HPP
3
4#include "utils.hpp"
5#include "sum.hpp"
6
7#include <vector>
8#include <algorithm>
9#include <cstddef>
10
11#include "tatami/tatami.hpp"
12#include "sanisizer/sanisizer.hpp"
13
20namespace tatami_stats {
21
30 bool skip_nan = false;
31
36 int num_threads = 1;
37};
38
42template<typename Value_, typename Index_, typename Group_, typename Output_>
43void group_sum_direct(
44 bool row,
46 const Group_* group,
47 const std::size_t num_groups,
48 std::vector<Output_*>& output,
49 const GroupSumOptions& opt
50) {
51 const Index_ dim = (row ? mat.nrow() : mat.ncol());
52 const Index_ otherdim = (row ? mat.ncol() : mat.nrow());
53
54 if (mat.sparse()) {
55 tatami::parallelize([&](int, Index_ start, Index_ len) -> void {
56 auto ext = tatami::consecutive_extractor<true>(mat, row, start, len);
59 auto tmp = sanisizer::create<std::vector<Output_> >(num_groups);
60
61 for (Index_ x = 0; x < len; ++x) {
62 auto range = ext->fetch(xbuffer.data(), ibuffer.data());
63 std::fill(tmp.begin(), tmp.end(), static_cast<Output_>(0));
64
65 nanable_ifelse<Value_>(
66 opt.skip_nan,
67 [&]() -> void {
68 for (Index_ j = 0; j < range.number; ++j) {
69 const auto val = range.value[j];
70 if (!std::isnan(val)) {
71 tmp[group[range.index[j]]] += val;
72 }
73 }
74 },
75 [&]() -> void {
76 for (Index_ j = 0; j < range.number; ++j) {
77 tmp[group[range.index[j]]] += range.value[j];
78 }
79 }
80 );
81
82 for (I<decltype(num_groups)> g = 0; g < num_groups; ++g) {
83 output[g][start + x] = tmp[g];
84 }
85 }
86 }, dim, opt.num_threads);
87
88 } else {
89 tatami::parallelize([&](int, Index_ start, Index_ len) -> void {
90 auto ext = tatami::consecutive_extractor<false>(mat, row, start, len);
92 auto tmp = sanisizer::create<std::vector<Output_> >(num_groups);
93
94 for (Index_ x = 0; x < len; ++x) {
95 auto ptr = ext->fetch(xbuffer.data());
96 std::fill(tmp.begin(), tmp.end(), static_cast<Output_>(0));
97
98 nanable_ifelse<Value_>(
99 opt.skip_nan,
100 [&]() -> void {
101 for (Index_ j = 0; j < otherdim; ++j) {
102 const auto val = ptr[j];
103 if (!std::isnan(val)) {
104 tmp[group[j]] += val;
105 }
106 }
107 },
108 [&]() -> void {
109 for (Index_ j = 0; j < otherdim; ++j) {
110 tmp[group[j]] += ptr[j];
111 }
112 }
113 );
114
115 for (I<decltype(num_groups)> g = 0; g < num_groups; ++g) {
116 output[g][start + x] = tmp[g];
117 }
118 }
119 }, dim, opt.num_threads);
120 }
121}
122
123template<typename Value_, typename Index_, typename Group_, typename Output_>
124void group_sum_running(
125 bool row,
127 const Group_* group,
128 const std::size_t num_groups,
129 std::vector<Output_*>& output,
130 const GroupSumOptions& opt
131) {
132 const Index_ dim = (row ? mat.nrow() : mat.ncol());
133 const Index_ otherdim = (row ? mat.ncol() : mat.nrow());
134 const bool is_sparse = mat.is_sparse();
135
136 const auto do_parallel = opt.num_threads > 1;
137 std::optional<std::vector<std::optional<std::vector<std::vector<Output_> > > > > all_partial_sums;
138 if (do_parallel) {
139 all_partial_sums.emplace(sanisizer::cast<I<decltype(all_partial_sums->size())> >(opt.num_threads - 1));
140 }
141
142 for (std::size_t g = 0; g < num_groups; ++g) {
143 std::fill_n(output[g], dim, 0);
144 }
145
146 const auto nused = tatami::parallelize([&](int thread, Index_ start, Index_ len) -> void {
147 // If we can, directly dump it to the output pointers, otherwise put it into a temporary.
148 // This will eventually be moved to all_partial_sums but we use a local variable to try to mitigate false sharing.
149 Output_** sum_ptrs;
150 std::optional<std::vector<std::vector<Output_> > > cur_sums;
151 std::optional<std::vector<Output_*> > cur_ptrs;
152 if (!do_parallel) {
153 sum_ptrs = output.data();
154 } else {
155 if (thread == 0) {
156 sum_ptrs = output.data();
157 } else {
158 cur_sums.emplace(tatami::cast_Index_to_container_size<std::vector<std::vector<Output_> > >(num_groups));
159 cur_ptrs.emplace(tatami::cast_Index_to_container_size<std::vector<Output_*> >(num_groups));
160 for (std::size_t g = 0; g < num_groups; ++g) {
161 tatami::resize_container_to_Index_size((*cur_sums)[g], dim);
162 (*cur_ptrs)[g] = (*cur_sums)[g].data();
163 }
164 sum_ptrs = cur_ptrs->data();
165 }
166 }
167
168 if (is_sparse) {
169 // Order within each observed vector doesn't affect numerical precision of the outcome,
170 // as addition order for each objective vector is already well-defined for a running calculation.
171 tatami::Options topt;
172 topt.sparse_ordered_index = false;
173 auto ext = tatami::consecutive_extractor<true>(mat, !row, start, len, topt);
176
177 for (Index_ x = 0; x < len; ++x) {
178 auto range = ext->fetch(xbuffer.data(), ibuffer.data());
179 const auto sum_ptr = sum_ptrs[group[start + x]];
180
181 nanable_ifelse<Value_>(
182 opt.skip_nan,
183 [&]() -> void {
184 for (Index_ i = 0; i < range.number; ++i) {
185 const auto val = range.value[i];
186 if (!std::isnan(val)) {
187 sum_ptr[range.index[i]] += val;
188 }
189 }
190 },
191 [&]() -> void {
192 for (Index_ i = 0; i < range.number; ++i) {
193 sum_ptr[range.index[i]] += range.value[i];
194 }
195 }
196 );
197 }
198
199 } else {
200 auto ext = tatami::consecutive_extractor<false>(mat, !row, start, len);
202
203 for (Index_ x = 0; x < len; ++x) {
204 auto ptr = ext->fetch(buffer.data());
205 const auto sum_ptr = sum_ptrs[group[start + x]];
206
207 nanable_ifelse<Value_>(
208 opt.skip_nan,
209 [&]() -> void {
210 for (Index_ d = 0; d < dim; ++d) {
211 const auto val = ptr[d];
212 if (!std::isnan(val)) {
213 sum_ptr[d] += val;
214 }
215 }
216 },
217 [&]() -> void {
218 for (Index_ d = 0; d < dim; ++d) {
219 sum_ptr[d] += ptr[d];
220 }
221 }
222 );
223 }
224 }
225
226 if (do_parallel) {
227 if (thread > 0) {
228 (*all_partial_sums)[thread - 1] = std::move(cur_sums);
229 }
230 }
231 }, otherdim, opt.num_threads);
232
233 if (do_parallel) {
234 for (std::size_t g = 0; g < num_groups; ++g) {
235 const auto cur_out = output[g];
236 for (int u = 1; u < nused; ++u) {
237 const auto& cur_sum = (*((*all_partial_sums)[u - 1]))[g];
238 for (Index_ d = 0; d < dim; ++d) {
239 cur_out[d] += cur_sum[d];
240 }
241 }
242 }
243 }
244}
271template<typename Value_, typename Index_, typename Group_, typename Output_>
273 bool row,
275 const Group_* group,
276 const std::size_t num_groups,
277 std::vector<Output_*>& output,
278 const GroupSumOptions& opt
279) {
280 if (mat.prefer_rows() == row) {
281 group_sum_direct(row, mat, group, num_groups, output, opt);
282 } else {
283 group_sum_running(row, mat, group, num_groups, output, opt);
284 }
285}
286
310template<typename Output_ = double, typename Value_, typename Index_, typename Group_>
311std::vector<std::vector<Output_> > group_sum(
312 bool row,
314 const Group_* group,
315 const std::size_t num_groups,
316 const GroupSumOptions& opt
317) {
318 auto output = sanisizer::create<std::vector<std::vector<Output_> > >(num_groups);
319 auto ptrs = sanisizer::create<std::vector<Output_*> >(num_groups);
320 const Index_ dim = (row ? mat.nrow() : mat.ncol());
321 for (std::size_t g = 0; g < num_groups; ++g) {
323#ifdef TATAMI_STATS_TEST_DIRTY
324 , -1
325#endif
326 );
327 ptrs[g] = output[g].data();
328 }
329 group_sum(row, mat, group, num_groups, ptrs, opt);
330 return output;
331}
332
333}
334
335#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_sum(bool row, const tatami::Matrix< Value_, Index_ > &mat, const Group_ *group, const std::size_t num_groups, std::vector< Output_ * > &output, const GroupSumOptions &opt)
Definition group_sum.hpp:272
void resize_container_to_Index_size(Container_ &container, const Index_ x, Args_ &&... args)
int parallelize(Function_ fun, const Index_ tasks, const int workers)
I< decltype(std::declval< Container_ >().size())> cast_Index_to_container_size(const Index_ x)
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)
bool sparse_ordered_index
Options for group_sum().
Definition group_sum.hpp:25
bool skip_nan
Definition group_sum.hpp:30
int num_threads
Definition group_sum.hpp:36
Compute row and column sums from a tatami::Matrix.