quickstats
Quickly compute simple statistics
Loading...
Searching...
No Matches
median.hpp
Go to the documentation of this file.
1#ifndef QUICKSTATS_MEDIAN_HPP
2#define QUICKSTATS_MEDIAN_HPP
3
4#include <algorithm>
5#include <type_traits>
6#include <cassert>
7#include <cstddef>
8
9#include "utils.hpp"
10
16namespace quickstats {
17
22template<typename Output_ = double>
29
46template<typename Output_ = double, typename Input_>
47Output_ median(const std::size_t num_total, Input_* const ptr, const MedianOptions<Output_>& options) {
48 static_assert(std::is_floating_point<Output_>::value);
49
50 if (num_total == 0) {
51 return options.placeholder;
52 }
53
54 const std::size_t halfway = num_total / 2;
55 const bool is_even = (num_total % 2 == 0);
56
57 std::nth_element(ptr, ptr + halfway, ptr + num_total);
58 const Output_ medtmp = *(ptr + halfway);
59 if (!is_even) {
60 return medtmp;
61 }
62
63 // 'nth_element()' reorganizes 'ptr' so that everything below 'halfway' is
64 // less than or equal to 'ptr[halfway]', while everything above 'halfway'
65 // is greater than or equal to 'ptr[halfway]'. Thus, to get the element
66 // immediately before 'halfway' in the sort order, we just need to find the
67 // maximum from '[0, halfway)'.
68 const Output_ other = *std::max_element(ptr, ptr + halfway);
69
70 return interpolate<Output_>(medtmp, other, 0.5);
71}
72
76// We support an arbitrary zero_value so that the MAD calculation can re-use this for the sparse calculations, see mad.hpp for details.
77template<typename Output_, typename Input_>
78Output_ median_internal(const std::size_t num_total, const std::size_t num_non_zero, Input_* const non_zero_values, const Input_ zero_value, const MedianOptions<Output_>& options) {
79 assert(num_total >= num_non_zero);
80
81 // Fallback to the dense code if there are no structural zeros. This is not
82 // just for efficiency as the downstream averaging code assumes that there
83 // is at least one structural zero when considering its scenarios.
84 if (num_non_zero == num_total) {
85 return median<Output_>(num_total, non_zero_values, options);
86 }
87
88 // Is the number of non-zeros less than the number of zeros?
89 // If so, the median must be equal to the zero value.
90 // Note that we calculate it in this way to avoid overflow from 'num_non_zero * 2'.
91 if (num_non_zero < num_total - num_non_zero) {
92 return zero_value;
93 }
94
95 const std::size_t halfway = num_total / 2;
96 const bool is_even = (num_total % 2 == 0);
97
98 const std::size_t num_zero = num_total - num_non_zero;
99 std::size_t num_below = 0;
100 for (std::size_t i = 0; i < num_non_zero; ++i) {
101 num_below += (non_zero_values[i] < zero_value);
102 }
103
104 if (!is_even) {
105 if (num_below > halfway) {
106 std::nth_element(non_zero_values, non_zero_values + halfway, non_zero_values + num_non_zero);
107 return non_zero_values[halfway];
108
109 } else if (halfway >= num_below + num_zero) {
110 const std::size_t skip_zeros = halfway - num_zero;
111 std::nth_element(non_zero_values, non_zero_values + skip_zeros, non_zero_values + num_non_zero);
112 return non_zero_values[skip_zeros];
113
114 } else {
115 return zero_value;
116 }
117 }
118
119 Output_ baseline = zero_value, other = zero_value;
120 if (num_below > halfway) { // both halves of the median are below the zero value.
121 std::nth_element(non_zero_values, non_zero_values + halfway, non_zero_values + num_non_zero);
122 baseline = non_zero_values[halfway];
123 other = *(std::max_element(non_zero_values, non_zero_values + halfway)); // max_element gets the sorted value at halfway - 1, see explanation for the dense case.
124
125 } else if (num_below == halfway) { // the upper half is guaranteed to be the zero value.
126 const std::size_t below_halfway = halfway - 1;
127 std::nth_element(non_zero_values, non_zero_values + below_halfway, non_zero_values + num_non_zero);
128 other = non_zero_values[below_halfway]; // set to other so that, in the common case of zero_value = 0, addition/subtraction of baseline = 0 has no effect on precision.
129
130 } else if (num_below < halfway && num_below + num_zero > halfway) { // both halves are the zero value, so the zero value is the median.
131 ;
132
133 } else if (num_below + num_zero == halfway) { // the lower half is guaranteed to be the zero value.
134 const std::size_t skip_zeros = halfway - num_zero;
135 std::nth_element(non_zero_values, non_zero_values + skip_zeros, non_zero_values + num_non_zero);
136 other = non_zero_values[skip_zeros]; // set to other so that, in the common case of zero_value = 0, addition/subtraction of baseline = 0 has no effect on precision.
137
138 } else { // both halves of the median are above the zero value.
139 const std::size_t skip_zeros = halfway - num_zero;
140 std::nth_element(non_zero_values, non_zero_values + skip_zeros, non_zero_values + num_non_zero);
141 baseline = non_zero_values[skip_zeros];
142 other = *(std::max_element(non_zero_values, non_zero_values + skip_zeros)); // max_element gets the sorted value at skip_zeros - 1, see explanation for the dense case.
143 }
144
145 return interpolate<Output_>(baseline, other, 0.5);
146}
171template<typename Output_ = double, typename Input_>
172Output_ median(const std::size_t num_total, const std::size_t num_non_zero, Input_* const values, const MedianOptions<Output_>& options) {
173 return median_internal<Output_>(num_total, num_non_zero, values, static_cast<Input_>(0), options);
174}
175
179// Backwards compatibility.
180template<typename Output_ = double, typename Input_>
181Output_ median(const std::size_t num_total, Input_* const ptr) {
182 return median(num_total, ptr, MedianOptions<Output_>());
183}
184
185template<typename Output_ = double, typename Input_>
186Output_ median(const std::size_t num_total, const std::size_t num_non_zero, Input_* const values) {
187 return median(num_total, num_non_zero, values, MedianOptions<Output_>());
188}
193}
194
195#endif
Quickly compute simple statistics.
Definition mad.hpp:15
Output_ median(const std::size_t num_total, Input_ *const ptr, const MedianOptions< Output_ > &options)
Definition median.hpp:47
constexpr Value_ nan_if_available_else_zero()
Definition utils.hpp:63
Options for median().
Definition median.hpp:23
Output_ placeholder
Definition median.hpp:27
Miscellaneous utilities.