statcpp
C++17 Header-Only Statistics Library
Loading...
Searching...
No Matches
basic_statistics.hpp
Go to the documentation of this file.
1
15#pragma once
16
17#include <algorithm>
18#include <cmath>
19#include <cstddef>
20#include <functional>
21#include <iterator>
22#include <limits>
23#include <map>
24#include <numeric>
25#include <stdexcept>
26#include <type_traits>
27#include <vector>
28
29namespace statcpp {
30
31// ============================================================================
32// Sum
33// ============================================================================
34
45template <typename Iterator>
46auto sum(Iterator first, Iterator last)
47{
48 using value_type = typename std::iterator_traits<Iterator>::value_type;
49 return std::accumulate(first, last, value_type{});
50}
51
64template <typename Iterator, typename Projection>
65auto sum(Iterator first, Iterator last, Projection proj)
66{
67 using result_type = std::invoke_result_t<Projection,
68 typename std::iterator_traits<Iterator>::value_type>;
69 result_type total{};
70 for (auto it = first; it != last; ++it) {
71 total += std::invoke(proj, *it);
72 }
73 return total;
74}
75
76// ============================================================================
77// Count
78// ============================================================================
79
90template <typename Iterator>
91std::size_t count(Iterator first, Iterator last)
92{
93 return static_cast<std::size_t>(std::distance(first, last));
94}
95
96// ============================================================================
97// Mean (Arithmetic Mean)
98// ============================================================================
99
111template <typename Iterator>
112double mean(Iterator first, Iterator last)
113{
114 auto n = statcpp::count(first, last);
115 if (n == 0) {
116 throw std::invalid_argument("statcpp::mean: empty range");
117 }
118 double total = 0.0;
119 for (auto it = first; it != last; ++it) {
120 total += static_cast<double>(*it);
121 }
122 return total / static_cast<double>(n);
123}
124
138template <typename Iterator, typename Projection>
139double mean(Iterator first, Iterator last, Projection proj)
140{
141 auto n = statcpp::count(first, last);
142 if (n == 0) {
143 throw std::invalid_argument("statcpp::mean: empty range");
144 }
145 double total = 0.0;
146 for (auto it = first; it != last; ++it) {
147 total += static_cast<double>(std::invoke(proj, *it));
148 }
149 return total / static_cast<double>(n);
150}
151
152// ============================================================================
153// Median
154// ============================================================================
155
168template <typename Iterator>
169double median(Iterator first, Iterator last)
170{
171 static_assert(
172 std::is_base_of_v<
173 std::random_access_iterator_tag,
174 typename std::iterator_traits<Iterator>::iterator_category>,
175 "statcpp::median requires random access iterators");
176
177 auto n = statcpp::count(first, last);
178 if (n == 0) {
179 throw std::invalid_argument("statcpp::median: empty range");
180 }
181
182 auto mid = n / 2;
183 if (n % 2 == 0) {
184 return (static_cast<double>(*(first + (mid - 1))) + static_cast<double>(*(first + mid))) / 2.0;
185 }
186 return static_cast<double>(*(first + mid));
187}
188
203template <typename Iterator, typename Projection>
204double median(Iterator first, Iterator last, Projection proj)
205{
206 static_assert(
207 std::is_base_of_v<
208 std::random_access_iterator_tag,
209 typename std::iterator_traits<Iterator>::iterator_category>,
210 "statcpp::median requires random access iterators");
211
212 auto n = statcpp::count(first, last);
213 if (n == 0) {
214 throw std::invalid_argument("statcpp::median: empty range");
215 }
216
217 auto mid = n / 2;
218 if (n % 2 == 0) {
219 return (static_cast<double>(std::invoke(proj, *(first + (mid - 1))))
220 + static_cast<double>(std::invoke(proj, *(first + mid)))) / 2.0;
221 }
222 return static_cast<double>(std::invoke(proj, *(first + mid)));
223}
224
225// ============================================================================
226// Mode
227// ============================================================================
228
241template <typename Iterator>
242auto mode(Iterator first, Iterator last)
243{
244 using value_type = typename std::iterator_traits<Iterator>::value_type;
245 if (first == last) {
246 throw std::invalid_argument("statcpp::mode: empty range");
247 }
248
249 std::map<value_type, std::size_t> freq;
250 for (auto it = first; it != last; ++it) {
251 // Skip NaN values (floating-point types only)
252 if constexpr (std::is_floating_point_v<value_type>) {
253 if (std::isnan(*it)) continue;
254 }
255 ++freq[*it];
256 }
257
258 if (freq.empty()) {
259 throw std::invalid_argument("statcpp::mode: all values are NaN");
260 }
261
262 auto best = freq.begin();
263 for (auto it = freq.begin(); it != freq.end(); ++it) {
264 if (it->second > best->second) {
265 best = it;
266 }
267 }
268 return best->first;
269}
270
285template <typename Iterator, typename Projection>
286auto mode(Iterator first, Iterator last, Projection proj)
287{
288 using result_type = std::invoke_result_t<Projection,
289 typename std::iterator_traits<Iterator>::value_type>;
290 if (first == last) {
291 throw std::invalid_argument("statcpp::mode: empty range");
292 }
293
294 std::map<result_type, std::size_t> freq;
295 for (auto it = first; it != last; ++it) {
296 // Skip NaN values (floating-point types only)
297 auto val = std::invoke(proj, *it);
298 if constexpr (std::is_floating_point_v<result_type>) {
299 if (std::isnan(val)) continue;
300 }
301 ++freq[val];
302 }
303
304 if (freq.empty()) {
305 throw std::invalid_argument("statcpp::mode: all values are NaN");
306 }
307
308 auto best = freq.begin();
309 for (auto it = freq.begin(); it != freq.end(); ++it) {
310 if (it->second > best->second) {
311 best = it;
312 }
313 }
314 return best->first;
315}
316
317// ============================================================================
318// Modes (multiple modes)
319// ============================================================================
320
333template <typename Iterator>
334auto modes(Iterator first, Iterator last)
335 -> std::vector<typename std::iterator_traits<Iterator>::value_type>
336{
337 using value_type = typename std::iterator_traits<Iterator>::value_type;
338 if (first == last) {
339 throw std::invalid_argument("statcpp::modes: empty range");
340 }
341
342 std::map<value_type, std::size_t> freq;
343 for (auto it = first; it != last; ++it) {
344 // Skip NaN values (floating-point types only)
345 if constexpr (std::is_floating_point_v<value_type>) {
346 if (std::isnan(*it)) continue;
347 }
348 ++freq[*it];
349 }
350
351 if (freq.empty()) {
352 throw std::invalid_argument("statcpp::modes: all values are NaN");
353 }
354
355 // Find maximum frequency
356 std::size_t max_freq = 0;
357 for (const auto& pair : freq) {
358 if (pair.second > max_freq) {
359 max_freq = pair.second;
360 }
361 }
362
363 // Collect all values with maximum frequency (ascending order due to map)
364 std::vector<value_type> result;
365 for (const auto& pair : freq) {
366 if (pair.second == max_freq) {
367 result.push_back(pair.first);
368 }
369 }
370
371 return result;
372}
373
388template <typename Iterator, typename Projection>
389auto modes(Iterator first, Iterator last, Projection proj)
390 -> std::vector<std::invoke_result_t<Projection,
391 typename std::iterator_traits<Iterator>::value_type>>
392{
393 using result_type = std::invoke_result_t<Projection,
394 typename std::iterator_traits<Iterator>::value_type>;
395 if (first == last) {
396 throw std::invalid_argument("statcpp::modes: empty range");
397 }
398
399 std::map<result_type, std::size_t> freq;
400 for (auto it = first; it != last; ++it) {
401 // Skip NaN values (floating-point types only)
402 auto val = std::invoke(proj, *it);
403 if constexpr (std::is_floating_point_v<result_type>) {
404 if (std::isnan(val)) continue;
405 }
406 ++freq[val];
407 }
408
409 if (freq.empty()) {
410 throw std::invalid_argument("statcpp::modes: all values are NaN");
411 }
412
413 // Find maximum frequency
414 std::size_t max_freq = 0;
415 for (const auto& pair : freq) {
416 if (pair.second > max_freq) {
417 max_freq = pair.second;
418 }
419 }
420
421 // Collect all values with maximum frequency (ascending order due to map)
422 std::vector<result_type> result;
423 for (const auto& pair : freq) {
424 if (pair.second == max_freq) {
425 result.push_back(pair.first);
426 }
427 }
428
429 return result;
430}
431
432// ============================================================================
433// Geometric Mean
434// ============================================================================
435
448template <typename Iterator>
449double geometric_mean(Iterator first, Iterator last)
450{
451 auto n = statcpp::count(first, last);
452 if (n == 0) {
453 throw std::invalid_argument("statcpp::geometric_mean: empty range");
454 }
455 double log_sum = 0.0;
456 for (auto it = first; it != last; ++it) {
457 if (static_cast<double>(*it) <= 0.0) {
458 throw std::invalid_argument("statcpp::geometric_mean: all values must be positive");
459 }
460 log_sum += std::log(static_cast<double>(*it));
461 }
462 return std::exp(log_sum / static_cast<double>(n));
463}
464
479template <typename Iterator, typename Projection>
480double geometric_mean(Iterator first, Iterator last, Projection proj)
481{
482 auto n = statcpp::count(first, last);
483 if (n == 0) {
484 throw std::invalid_argument("statcpp::geometric_mean: empty range");
485 }
486 double log_sum = 0.0;
487 for (auto it = first; it != last; ++it) {
488 double val = static_cast<double>(std::invoke(proj, *it));
489 if (val <= 0.0) {
490 throw std::invalid_argument("statcpp::geometric_mean: all values must be positive");
491 }
492 log_sum += std::log(val);
493 }
494 return std::exp(log_sum / static_cast<double>(n));
495}
496
497// ============================================================================
498// Harmonic Mean
499// ============================================================================
500
518template <typename Iterator>
519double harmonic_mean(Iterator first, Iterator last)
520{
521 auto n = statcpp::count(first, last);
522 if (n == 0) {
523 throw std::invalid_argument("statcpp::harmonic_mean: empty range");
524 }
525 double reciprocal_sum = 0.0;
526 for (auto it = first; it != last; ++it) {
527 double val = static_cast<double>(*it);
528 // Reject both exact zero and values so small that 1/val overflows to infinity.
529 // std::numeric_limits<double>::min() is the smallest positive normal double (~2.2e-308),
530 // so checking |val| < min() catches subnormals and zero without false positives on
531 // legitimate small values that are still representable.
532 if (std::abs(val) < std::numeric_limits<double>::min()) {
533 throw std::invalid_argument("statcpp::harmonic_mean: zero or near-zero value encountered");
534 }
535 reciprocal_sum += 1.0 / val;
536 }
537 return static_cast<double>(n) / reciprocal_sum;
538}
539
554template <typename Iterator, typename Projection>
555double harmonic_mean(Iterator first, Iterator last, Projection proj)
556{
557 auto n = statcpp::count(first, last);
558 if (n == 0) {
559 throw std::invalid_argument("statcpp::harmonic_mean: empty range");
560 }
561 double reciprocal_sum = 0.0;
562 for (auto it = first; it != last; ++it) {
563 double val = static_cast<double>(std::invoke(proj, *it));
564 if (std::abs(val) < std::numeric_limits<double>::min()) {
565 throw std::invalid_argument("statcpp::harmonic_mean: zero or near-zero value encountered");
566 }
567 reciprocal_sum += 1.0 / val;
568 }
569 return static_cast<double>(n) / reciprocal_sum;
570}
571
572// ============================================================================
573// Trimmed Mean
574// ============================================================================
575
589template <typename Iterator>
590double trimmed_mean(Iterator first, Iterator last, double proportion)
591{
592 static_assert(
593 std::is_base_of_v<
594 std::random_access_iterator_tag,
595 typename std::iterator_traits<Iterator>::iterator_category>,
596 "statcpp::trimmed_mean requires random access iterators");
597
598 if (proportion < 0.0 || proportion >= 0.5) {
599 throw std::invalid_argument("statcpp::trimmed_mean: proportion must be in [0.0, 0.5)");
600 }
601 auto n = statcpp::count(first, last);
602 if (n == 0) {
603 throw std::invalid_argument("statcpp::trimmed_mean: empty range");
604 }
605
606 auto trim_count = static_cast<std::size_t>(static_cast<double>(n) * proportion);
607 if (n - 2 * trim_count == 0) {
608 throw std::invalid_argument("statcpp::trimmed_mean: all elements trimmed");
609 }
610
611 double total = 0.0;
612 for (std::size_t i = trim_count; i < n - trim_count; ++i) {
613 total += static_cast<double>(*(first + i));
614 }
615 return total / static_cast<double>(n - 2 * trim_count);
616}
617
633template <typename Iterator, typename Projection>
634double trimmed_mean(Iterator first, Iterator last, double proportion, Projection proj)
635{
636 static_assert(
637 std::is_base_of_v<
638 std::random_access_iterator_tag,
639 typename std::iterator_traits<Iterator>::iterator_category>,
640 "statcpp::trimmed_mean requires random access iterators");
641
642 if (proportion < 0.0 || proportion >= 0.5) {
643 throw std::invalid_argument("statcpp::trimmed_mean: proportion must be in [0.0, 0.5)");
644 }
645 auto n = statcpp::count(first, last);
646 if (n == 0) {
647 throw std::invalid_argument("statcpp::trimmed_mean: empty range");
648 }
649
650 auto trim_count = static_cast<std::size_t>(static_cast<double>(n) * proportion);
651 if (n - 2 * trim_count == 0) {
652 throw std::invalid_argument("statcpp::trimmed_mean: all elements trimmed");
653 }
654
655 double total = 0.0;
656 for (std::size_t i = trim_count; i < n - trim_count; ++i) {
657 total += static_cast<double>(std::invoke(proj, *(first + i)));
658 }
659 return total / static_cast<double>(n - 2 * trim_count);
660}
661
662// ============================================================================
663// Weighted Mean
664// ============================================================================
665
679template <typename Iterator, typename WeightIterator>
680double weighted_mean(Iterator first, Iterator last, WeightIterator weight_first, WeightIterator weight_last)
681{
682 if (std::distance(first, last) != std::distance(weight_first, weight_last)) {
683 throw std::invalid_argument("statcpp::weighted_mean: data and weight ranges must have the same size");
684 }
685 auto n = statcpp::count(first, last);
686 if (n == 0) {
687 throw std::invalid_argument("statcpp::weighted_mean: empty range");
688 }
689
690 double sum_weighted = 0.0;
691 double sum_weights = 0.0;
692 auto weight_it = weight_first;
693
694 for (auto it = first; it != last; ++it, ++weight_it) {
695 double value = static_cast<double>(*it);
696 double weight = static_cast<double>(*weight_it);
697
698 if (weight < 0.0) {
699 throw std::invalid_argument("statcpp::weighted_mean: negative weight");
700 }
701
702 sum_weighted += value * weight;
703 sum_weights += weight;
704 }
705
706 if (sum_weights == 0.0) {
707 throw std::invalid_argument("statcpp::weighted_mean: sum of weights is zero");
708 }
709
710 return sum_weighted / sum_weights;
711}
712
713template <typename Iterator, typename WeightIterator>
714[[deprecated("Use weighted_mean(first, last, weight_first, weight_last) overload for range safety")]]
715double weighted_mean(Iterator first, Iterator last, WeightIterator weight_first)
716{
717 auto n = statcpp::count(first, last);
718 if (n == 0) {
719 throw std::invalid_argument("statcpp::weighted_mean: empty range");
720 }
721
722 double sum_weighted = 0.0;
723 double sum_weights = 0.0;
724 auto weight_it = weight_first;
725
726 for (auto it = first; it != last; ++it, ++weight_it) {
727 double value = static_cast<double>(*it);
728 double weight = static_cast<double>(*weight_it);
729
730 if (weight < 0.0) {
731 throw std::invalid_argument("statcpp::weighted_mean: negative weight");
732 }
733
734 sum_weighted += value * weight;
735 sum_weights += weight;
736 }
737
738 if (sum_weights == 0.0) {
739 throw std::invalid_argument("statcpp::weighted_mean: sum of weights is zero");
740 }
741
742 return sum_weighted / sum_weights;
743}
744
760template <typename Iterator, typename WeightIterator, typename Projection>
761double weighted_mean(Iterator first, Iterator last, WeightIterator weight_first, WeightIterator weight_last, Projection proj)
762{
763 if (std::distance(first, last) != std::distance(weight_first, weight_last)) {
764 throw std::invalid_argument("statcpp::weighted_mean: data and weight ranges must have the same size");
765 }
766 auto n = statcpp::count(first, last);
767 if (n == 0) {
768 throw std::invalid_argument("statcpp::weighted_mean: empty range");
769 }
770
771 double sum_weighted = 0.0;
772 double sum_weights = 0.0;
773 auto weight_it = weight_first;
774
775 for (auto it = first; it != last; ++it, ++weight_it) {
776 double value = static_cast<double>(std::invoke(proj, *it));
777 double weight = static_cast<double>(*weight_it);
778
779 if (weight < 0.0) {
780 throw std::invalid_argument("statcpp::weighted_mean: negative weight");
781 }
782
783 sum_weighted += value * weight;
784 sum_weights += weight;
785 }
786
787 if (sum_weights == 0.0) {
788 throw std::invalid_argument("statcpp::weighted_mean: sum of weights is zero");
789 }
790
791 return sum_weighted / sum_weights;
792}
793
794template <typename Iterator, typename WeightIterator, typename Projection>
795[[deprecated("Use weighted_mean(first, last, weight_first, weight_last, proj) overload for range safety")]]
796double weighted_mean(Iterator first, Iterator last, WeightIterator weight_first, Projection proj)
797{
798 auto n = statcpp::count(first, last);
799 if (n == 0) {
800 throw std::invalid_argument("statcpp::weighted_mean: empty range");
801 }
802
803 double sum_weighted = 0.0;
804 double sum_weights = 0.0;
805 auto weight_it = weight_first;
806
807 for (auto it = first; it != last; ++it, ++weight_it) {
808 double value = static_cast<double>(std::invoke(proj, *it));
809 double weight = static_cast<double>(*weight_it);
810
811 if (weight < 0.0) {
812 throw std::invalid_argument("statcpp::weighted_mean: negative weight");
813 }
814
815 sum_weighted += value * weight;
816 sum_weights += weight;
817 }
818
819 if (sum_weights == 0.0) {
820 throw std::invalid_argument("statcpp::weighted_mean: sum of weights is zero");
821 }
822
823 return sum_weighted / sum_weights;
824}
825
826// ============================================================================
827// Logarithmic Mean
828// ============================================================================
829
844template <typename T1, typename T2>
845double logarithmic_mean(T1 a, T2 b)
846{
847 double x = static_cast<double>(a);
848 double y = static_cast<double>(b);
849
850 if (x <= 0.0 || y <= 0.0) {
851 throw std::invalid_argument("statcpp::logarithmic_mean: arguments must be positive");
852 }
853
854 // If values are approximately equal, return x directly.
855 // Use relative difference to avoid misclassifying large values as distinct
856 // when the relative gap is tiny (e.g., x = 1e15, y = 1e15 + 1e5).
857 if (std::abs(x - y) <= 1e-10 * std::max(x, y)) {
858 return x;
859 }
860
861 // Standard logarithmic mean: (y - x) / (ln(y) - ln(x))
862 return (y - x) / (std::log(y) - std::log(x));
863}
864
865// ============================================================================
866// Weighted Harmonic Mean
867// ============================================================================
868
884template <typename Iterator, typename WeightIterator>
885double weighted_harmonic_mean(Iterator first, Iterator last, WeightIterator weight_first, WeightIterator weight_last)
886{
887 if (std::distance(first, last) != std::distance(weight_first, weight_last)) {
888 throw std::invalid_argument("statcpp::weighted_harmonic_mean: data and weight ranges must have the same size");
889 }
890
891 auto n = statcpp::count(first, last);
892 if (n == 0) {
893 throw std::invalid_argument("statcpp::weighted_harmonic_mean: empty range");
894 }
895
896 double sum_weighted = 0.0;
897 double sum_weights = 0.0;
898 auto weight_it = weight_first;
899
900 for (auto it = first; it != last; ++it, ++weight_it) {
901 double value = static_cast<double>(*it);
902 double weight = static_cast<double>(*weight_it);
903
904 if (weight < 0.0) {
905 throw std::invalid_argument("statcpp::weighted_harmonic_mean: negative weight");
906 }
907
908 if (std::abs(value) < std::numeric_limits<double>::min()) {
909 throw std::invalid_argument("statcpp::weighted_harmonic_mean: zero value");
910 }
911
912 sum_weighted += weight / value;
913 sum_weights += weight;
914 }
915
916 if (sum_weights == 0.0) {
917 throw std::invalid_argument("statcpp::weighted_harmonic_mean: sum of weights is zero");
918 }
919
920 return sum_weights / sum_weighted;
921}
922
937template <typename Iterator, typename WeightIterator>
938[[deprecated("Use weighted_harmonic_mean(first, last, weight_first, weight_last) overload for range safety")]]
939double weighted_harmonic_mean(Iterator first, Iterator last, WeightIterator weight_first)
940{
941 auto n = statcpp::count(first, last);
942 if (n == 0) {
943 throw std::invalid_argument("statcpp::weighted_harmonic_mean: empty range");
944 }
945
946 double sum_weighted = 0.0;
947 double sum_weights = 0.0;
948 auto weight_it = weight_first;
949
950 for (auto it = first; it != last; ++it, ++weight_it) {
951 double value = static_cast<double>(*it);
952 double weight = static_cast<double>(*weight_it);
953
954 if (weight < 0.0) {
955 throw std::invalid_argument("statcpp::weighted_harmonic_mean: negative weight");
956 }
957
958 if (std::abs(value) < std::numeric_limits<double>::min()) {
959 throw std::invalid_argument("statcpp::weighted_harmonic_mean: zero value");
960 }
961
962 sum_weighted += weight / value;
963 sum_weights += weight;
964 }
965
966 if (sum_weights == 0.0) {
967 throw std::invalid_argument("statcpp::weighted_harmonic_mean: sum of weights is zero");
968 }
969
970 return sum_weights / sum_weighted;
971}
972
990template <typename Iterator, typename WeightIterator, typename Projection>
991double weighted_harmonic_mean(Iterator first, Iterator last, WeightIterator weight_first, WeightIterator weight_last, Projection proj)
992{
993 if (std::distance(first, last) != std::distance(weight_first, weight_last)) {
994 throw std::invalid_argument("statcpp::weighted_harmonic_mean: data and weight ranges must have the same size");
995 }
996
997 auto n = statcpp::count(first, last);
998 if (n == 0) {
999 throw std::invalid_argument("statcpp::weighted_harmonic_mean: empty range");
1000 }
1001
1002 double sum_weighted = 0.0;
1003 double sum_weights = 0.0;
1004 auto weight_it = weight_first;
1005
1006 for (auto it = first; it != last; ++it, ++weight_it) {
1007 double value = static_cast<double>(std::invoke(proj, *it));
1008 double weight = static_cast<double>(*weight_it);
1009
1010 if (weight < 0.0) {
1011 throw std::invalid_argument("statcpp::weighted_harmonic_mean: negative weight");
1012 }
1013
1014 if (std::abs(value) < std::numeric_limits<double>::min()) {
1015 throw std::invalid_argument("statcpp::weighted_harmonic_mean: zero value");
1016 }
1017
1018 sum_weighted += weight / value;
1019 sum_weights += weight;
1020 }
1021
1022 if (sum_weights == 0.0) {
1023 throw std::invalid_argument("statcpp::weighted_harmonic_mean: sum of weights is zero");
1024 }
1025
1026 return sum_weights / sum_weighted;
1027}
1028
1045template <typename Iterator, typename WeightIterator, typename Projection>
1046[[deprecated("Use weighted_harmonic_mean(first, last, weight_first, weight_last, proj) overload for range safety")]]
1047double weighted_harmonic_mean(Iterator first, Iterator last, WeightIterator weight_first, Projection proj)
1048{
1049 auto n = statcpp::count(first, last);
1050 if (n == 0) {
1051 throw std::invalid_argument("statcpp::weighted_harmonic_mean: empty range");
1052 }
1053
1054 double sum_weighted = 0.0;
1055 double sum_weights = 0.0;
1056 auto weight_it = weight_first;
1057
1058 for (auto it = first; it != last; ++it, ++weight_it) {
1059 double value = static_cast<double>(std::invoke(proj, *it));
1060 double weight = static_cast<double>(*weight_it);
1061
1062 if (weight < 0.0) {
1063 throw std::invalid_argument("statcpp::weighted_harmonic_mean: negative weight");
1064 }
1065
1066 if (std::abs(value) < std::numeric_limits<double>::min()) {
1067 throw std::invalid_argument("statcpp::weighted_harmonic_mean: zero value");
1068 }
1069
1070 sum_weighted += weight / value;
1071 sum_weights += weight;
1072 }
1073
1074 if (sum_weights == 0.0) {
1075 throw std::invalid_argument("statcpp::weighted_harmonic_mean: sum of weights is zero");
1076 }
1077
1078 return sum_weights / sum_weighted;
1079}
1080
1081// ============================================================================
1082// Argmin / Argmax
1083// ============================================================================
1084
1096template <typename Iterator>
1097std::size_t argmin(Iterator first, Iterator last)
1098{
1099 auto n = statcpp::count(first, last);
1100 if (n == 0) {
1101 throw std::invalid_argument("statcpp::argmin: empty range");
1102 }
1103
1104 auto min_it = std::min_element(first, last);
1105 return static_cast<std::size_t>(std::distance(first, min_it));
1106}
1107
1121template <typename Iterator, typename Projection>
1122std::size_t argmin(Iterator first, Iterator last, Projection proj)
1123{
1124 auto n = statcpp::count(first, last);
1125 if (n == 0) {
1126 throw std::invalid_argument("statcpp::argmin: empty range");
1127 }
1128
1129 auto min_it = std::min_element(first, last,
1130 [&proj](const auto& a, const auto& b) {
1131 return std::invoke(proj, a) < std::invoke(proj, b);
1132 });
1133
1134 return static_cast<std::size_t>(std::distance(first, min_it));
1135}
1136
1148template <typename Iterator>
1149std::size_t argmax(Iterator first, Iterator last)
1150{
1151 auto n = statcpp::count(first, last);
1152 if (n == 0) {
1153 throw std::invalid_argument("statcpp::argmax: empty range");
1154 }
1155
1156 auto max_it = std::max_element(first, last);
1157 return static_cast<std::size_t>(std::distance(first, max_it));
1158}
1159
1173template <typename Iterator, typename Projection>
1174std::size_t argmax(Iterator first, Iterator last, Projection proj)
1175{
1176 auto n = statcpp::count(first, last);
1177 if (n == 0) {
1178 throw std::invalid_argument("statcpp::argmax: empty range");
1179 }
1180
1181 auto max_it = std::max_element(first, last,
1182 [&proj](const auto& a, const auto& b) {
1183 return std::invoke(proj, a) < std::invoke(proj, b);
1184 });
1185
1186 return static_cast<std::size_t>(std::distance(first, max_it));
1187}
1188
1189} // namespace statcpp
auto sum(Iterator first, Iterator last)
Sum.
std::size_t argmin(Iterator first, Iterator last)
Returns the index of the minimum value.
double harmonic_mean(Iterator first, Iterator last)
Harmonic mean.
double trimmed_mean(Iterator first, Iterator last, double proportion)
Trimmed mean (accepts a sorted range. proportion: exclusion ratio per side, 0.0 to less than 0....
auto mode(Iterator first, Iterator last)
Mode (returns the smallest value when there are multiple modes: guarantees deterministic behavior)
double mean(Iterator first, Iterator last)
Arithmetic mean.
auto modes(Iterator first, Iterator last) -> std::vector< typename std::iterator_traits< Iterator >::value_type >
Returns all modes (returns a vector sorted in ascending order)
std::size_t argmax(Iterator first, Iterator last)
Returns the index of the maximum value.
double weighted_harmonic_mean(Iterator first, Iterator last, WeightIterator weight_first, WeightIterator weight_last)
Weighted harmonic mean (safe range version)
double median(Iterator first, Iterator last)
Median (accepts a sorted range)
double weighted_mean(Iterator first, Iterator last, WeightIterator weight_first, WeightIterator weight_last)
Weighted mean.
double geometric_mean(Iterator first, Iterator last)
Geometric mean.
std::size_t count(Iterator first, Iterator last)
Data count.
double logarithmic_mean(T1 a, T2 b)
Logarithmic Mean.