quickstats
Quickly compute simple statistics
Loading...
Searching...
No Matches
SingleQuantile.hpp
Go to the documentation of this file.
1#ifndef QUICKSTATS_SINGLE_QUANTILE_HPP
2#define QUICKSTATS_SINGLE_QUANTILE_HPP
3
4#include <cmath>
5#include <cassert>
6#include <type_traits>
7#include <stdexcept>
8#include <optional>
9#include <limits>
10#include <cstddef>
11#include <vector>
12
13#include "sanisizer/sanisizer.hpp"
14
15#include "utils.hpp"
16
22namespace quickstats {
23
27template<typename Output_>
28void configure_single_quantile(
29 const Output_ quantile,
30 const std::size_t num_m1,
31 std::size_t& upper_index,
32 Output_& upper_fraction,
33 bool& skip_lower
34) {
35 if (quantile < 0 || quantile > 1) {
36 throw std::out_of_range("'quantile' should lie in [0, 1]");
37 }
38
39 const Output_ raw_index = static_cast<Output_>(num_m1) * quantile;
40 const Output_ raw_upper_index = std::ceil(raw_index);
41 const Output_ raw_lower_index = std::floor(raw_index);
42
43 // Protect the cast from conversion imprecision between size_t and Float_,
44 // e.g., if it rounds up or if it converts it to an Inf.
45 const auto converted_upper_index = sanisizer::from_float<std::size_t>(raw_upper_index);
46 if (converted_upper_index <= num_m1) {
47 upper_index = converted_upper_index;
48 upper_fraction = raw_index - raw_lower_index;
49 skip_lower = (raw_upper_index == raw_lower_index);
50 } else {
51 // Just gently cap it at the maximum value.
52 upper_index = num_m1;
53 upper_fraction = 0;
54 skip_lower = true;
55 }
56}
68template<typename Output_ = double>
70public:
76 SingleQuantileFixedNumber(const std::size_t num_total, const Output_ quantile) : my_num_total(num_total) {
77 static_assert(std::is_floating_point<Output_>::value);
78
79 if (num_total <= 0) {
80 throw std::out_of_range("'num' should be positive");
81 }
82 const std::size_t num_m1 = num_total - 1;
83
84 configure_single_quantile(quantile, num_m1, my_upper_index, my_upper_fraction, my_skip_lower);
85 }
86
87private:
88 std::size_t my_num_total, my_upper_index;
89 Output_ my_upper_fraction;
90 bool my_skip_lower;
91
92public:
96 std::size_t get_num_total() const {
97 return my_num_total;
98 }
99
100public:
115 template<typename Input_>
116 Output_ operator()(Input_* const ptr) const {
117 const auto target = ptr + my_upper_index;
118 std::nth_element(ptr, target, ptr + my_num_total);
119
120 // Avoid an extra memory access to get the lower index if we don't need it; this would also fail if upper_index = 0.
121 const Output_ upper = *target;
122 if (my_skip_lower) {
123 return upper;
124 }
125
126 const Output_ lower = *std::max_element(ptr, target);
127 return interpolate(lower, upper, my_upper_fraction);
128 }
129
147 template<typename Input_>
148 Output_ operator()(const std::size_t num_non_zero, Input_* const ptr) const {
149 assert(num_non_zero <= my_num_total);
150 if (num_non_zero == my_num_total) {
151 return operator()(ptr);
152 } else if (num_non_zero == 0) {
153 return 0;
154 }
155
156 std::size_t num_negative = 0;
157 for (std::size_t i = 0; i < num_non_zero; ++i) {
158 num_negative += (ptr[i] < 0);
159 }
160
161 if (my_upper_index < num_negative) {
162 const auto target = ptr + my_upper_index;
163 std::nth_element(ptr, target, ptr + num_non_zero);
164
165 const Output_ upper = *target;
166 if (my_skip_lower) {
167 return upper;
168 }
169
170 const Output_ lower = *std::max_element(ptr, target);
171 return interpolate(lower, upper, my_upper_fraction);
172 }
173
174 if (num_negative && my_upper_index == num_negative) {
175 // The upper value is zero. It can't be positive, as that would
176 // imply that there are no structural zeros and num_non_zero == my_num_total.
177 if (my_skip_lower) {
178 return 0;
179 }
180
181 const auto target = ptr + (my_upper_index - 1);
182 std::nth_element(ptr, target, ptr + num_non_zero);
183 return static_cast<Output_>(*target) * (1 - my_upper_fraction);
184 }
185
186 const std::size_t num_zeros = my_num_total - num_non_zero;
187 const std::size_t num_not_positive = num_zeros + num_negative;
188 if (my_upper_index < num_not_positive) {
189 return 0;
190 }
191
192 const auto target = ptr + (my_upper_index - num_zeros);
193 std::nth_element(ptr, target, ptr + num_non_zero);
194 const Output_ upper = *target;
195 if (my_skip_lower) {
196 return upper;
197 }
198
199 if (my_upper_index == num_not_positive) {
200 return upper * my_upper_fraction;
201 }
202
203 const Output_ lower = *std::max_element(ptr, target);
204 return interpolate(lower, upper, my_upper_fraction);
205 }
206};
207
212template<typename Output_ = double>
219
227template<typename Output_>
229public:
236 SingleQuantileVariableNumber(const std::size_t max_num_total, const Output_ quantile, const SingleQuantileVariableNumberOptions<Output_>& options) :
237 my_quantile(quantile),
238 my_placeholder(options.placeholder)
239 {
240 if (max_num_total >= 2) {
241 sanisizer::resize(my_choices, max_num_total - 1);
242 }
243 }
244
248 SingleQuantileVariableNumber(const std::size_t max_num_total, const Output_ quantile) : SingleQuantileVariableNumber(max_num_total, quantile, {}) {}
253private:
254 std::vector<std::optional<SingleQuantileFixedNumber<Output_> > > my_choices;
255 Output_ my_quantile;
256 Output_ my_placeholder;
257
258public:
262 std::size_t get_max_num_total() const {
263 return sanisizer::sum_unsafe<std::size_t>(my_choices.size(), 1);
264 }
265
266public:
286 template<typename Input_>
287 Output_ operator()(const std::size_t num_total, Input_* ptr) {
288 if (num_total == 0) {
289 return my_placeholder;
290 } else if (num_total == 1) {
291 return *ptr;
292 } else {
293 assert(sanisizer::is_less_than_or_equal(num_total - 1, my_choices.size()));
294 auto& ocalc = my_choices[num_total - 2];
295 if (!ocalc.has_value()) { // Instantiating them on demand.
296 ocalc.emplace(num_total, my_quantile);
297 }
298 return (*ocalc)(ptr);
299 }
300 }
301
325 template<typename Input_>
326 Output_ operator()(const std::size_t num_total, const std::size_t num_non_zero, Input_* const values) {
327 if (num_total == 0) {
328 return my_placeholder;
329 } else if (num_total == 1) {
330 return (num_non_zero ? *values : 0);
331 } else {
332 assert(sanisizer::is_less_than_or_equal(num_total - 1, my_choices.size()));
333 auto& ocalc = my_choices[num_total - 2];
334 if (!ocalc.has_value()) { // Instantiating them on demand.
335 ocalc.emplace(num_total, my_quantile);
336 }
337 return (*ocalc)(num_non_zero, values);
338 }
339 }
340};
341
342}
343
344#endif
Calculate a single quantile from a fixed number of elements.
Definition SingleQuantile.hpp:69
std::size_t get_num_total() const
Definition SingleQuantile.hpp:96
Output_ operator()(const std::size_t num_non_zero, Input_ *const ptr) const
Definition SingleQuantile.hpp:148
Output_ operator()(Input_ *const ptr) const
Definition SingleQuantile.hpp:116
SingleQuantileFixedNumber(const std::size_t num_total, const Output_ quantile)
Definition SingleQuantile.hpp:76
Calculate a quantile from a variable number of elements.
Definition SingleQuantile.hpp:228
std::size_t get_max_num_total() const
Definition SingleQuantile.hpp:262
Output_ operator()(const std::size_t num_total, const std::size_t num_non_zero, Input_ *const values)
Definition SingleQuantile.hpp:326
Output_ operator()(const std::size_t num_total, Input_ *ptr)
Definition SingleQuantile.hpp:287
SingleQuantileVariableNumber(const std::size_t max_num_total, const Output_ quantile, const SingleQuantileVariableNumberOptions< Output_ > &options)
Definition SingleQuantile.hpp:236
Quickly compute simple statistics.
Definition mad.hpp:15
constexpr Value_ nan_if_available_else_zero()
Definition utils.hpp:63
Options for SingleQuantileVariableNumber.
Definition SingleQuantile.hpp:213
Output_ placeholder
Definition SingleQuantile.hpp:217
Miscellaneous utilities.