statcpp
C++17 Header-Only Statistics Library
Loading...
Searching...
No Matches
data_wrangling.hpp
Go to the documentation of this file.
1
9#pragma once
10
11#include <algorithm>
12#include <cmath>
13#include <cstddef>
14#include <functional>
15#include <limits>
16#include <map>
17#include <numeric>
18#include <random>
19#include <stdexcept>
20#include <type_traits>
21#include <unordered_map>
22#include <unordered_set>
23#include <utility>
24#include <vector>
25
28
29namespace statcpp {
30
31// ============================================================================
32// Missing Data Handling
33// ============================================================================
34
38inline constexpr double NA = std::numeric_limits<double>::quiet_NaN();
39
45inline bool is_na(double x) {
46 return std::isnan(x);
47}
48
55template <typename T>
56std::vector<std::vector<T>> dropna(const std::vector<std::vector<T>>& data)
57{
58 std::vector<std::vector<T>> result;
59 result.reserve(data.size());
60
61 for (const auto& row : data) {
62 bool has_na = false;
63 for (const auto& val : row) {
64 if constexpr (std::is_floating_point_v<T>) {
65 if (std::isnan(static_cast<double>(val))) {
66 has_na = true;
67 break;
68 }
69 }
70 }
71 if (!has_na) {
72 result.push_back(row);
73 }
74 }
75 return result;
76}
77
84template <typename T>
85std::vector<T> dropna(const std::vector<T>& data)
86{
87 std::vector<T> result;
88 result.reserve(data.size());
89
90 for (const auto& val : data) {
91 if constexpr (std::is_floating_point_v<T>) {
92 if (!std::isnan(static_cast<double>(val))) {
93 result.push_back(val);
94 }
95 } else {
96 result.push_back(val);
97 }
98 }
99 return result;
100}
101
109template <typename T>
110std::vector<T> fillna(const std::vector<T>& data, T fill_value)
111{
112 std::vector<T> result = data;
113 for (auto& val : result) {
114 if constexpr (std::is_floating_point_v<T>) {
115 if (std::isnan(static_cast<double>(val))) {
116 val = fill_value;
117 }
118 }
119 }
120 return result;
121}
122
128inline std::vector<double> fillna_mean(const std::vector<double>& data)
129{
130 std::vector<double> non_na;
131 non_na.reserve(data.size());
132
133 for (double val : data) {
134 if (!std::isnan(val)) {
135 non_na.push_back(val);
136 }
137 }
138
139 if (non_na.empty()) {
140 return data; // Return as-is if all values are NA
141 }
142
143 double m = mean(non_na.begin(), non_na.end());
144 return fillna(data, m);
145}
146
152inline std::vector<double> fillna_median(const std::vector<double>& data)
153{
154 std::vector<double> non_na;
155 non_na.reserve(data.size());
156
157 for (double val : data) {
158 if (!std::isnan(val)) {
159 non_na.push_back(val);
160 }
161 }
162
163 if (non_na.empty()) {
164 return data;
165 }
166
167 double med = median(non_na.begin(), non_na.end());
168 return fillna(data, med);
169}
170
176inline std::vector<double> fillna_ffill(const std::vector<double>& data)
177{
178 std::vector<double> result = data;
179 double last_valid = NA;
180
181 for (auto& val : result) {
182 if (!std::isnan(val)) {
183 last_valid = val;
184 } else if (!std::isnan(last_valid)) {
185 val = last_valid;
186 }
187 }
188 return result;
189}
190
196inline std::vector<double> fillna_bfill(const std::vector<double>& data)
197{
198 std::vector<double> result = data;
199 double next_valid = NA;
200
201 for (auto it = result.rbegin(); it != result.rend(); ++it) {
202 if (!std::isnan(*it)) {
203 next_valid = *it;
204 } else if (!std::isnan(next_valid)) {
205 *it = next_valid;
206 }
207 }
208 return result;
209}
210
216inline std::vector<double> fillna_interpolate(const std::vector<double>& data)
217{
218 std::vector<double> result = data;
219 std::size_t n = result.size();
220
221 for (std::size_t i = 0; i < n; ++i) {
222 if (std::isnan(result[i])) {
223 // Find valid values before and after
224 std::size_t prev_idx = i;
225 std::size_t next_idx = i;
226
227 // Forward direction
228 while (prev_idx > 0 && std::isnan(result[prev_idx])) {
229 --prev_idx;
230 }
231 if (std::isnan(result[prev_idx])) {
232 continue; // No valid value before
233 }
234
235 // Backward direction
236 while (next_idx < n - 1 && std::isnan(result[next_idx])) {
237 ++next_idx;
238 }
239 if (std::isnan(result[next_idx])) {
240 continue; // No valid value after
241 }
242
243 // Linear interpolation
244 double prev_val = result[prev_idx];
245 double next_val = result[next_idx];
246 double ratio = static_cast<double>(i - prev_idx) / static_cast<double>(next_idx - prev_idx);
247 result[i] = prev_val + ratio * (next_val - prev_val);
248 }
249 }
250 return result;
251}
252
253// ============================================================================
254// Filtering
255// ============================================================================
256
265template <typename T, typename Predicate>
266std::vector<T> filter(const std::vector<T>& data, Predicate pred)
267{
268 std::vector<T> result;
269 result.reserve(data.size());
270
271 for (const auto& val : data) {
272 if (pred(val)) {
273 result.push_back(val);
274 }
275 }
276 return result;
277}
278
287template <typename T, typename Predicate>
288std::vector<std::vector<T>> filter_rows(const std::vector<std::vector<T>>& data, Predicate pred)
289{
290 std::vector<std::vector<T>> result;
291 result.reserve(data.size());
292
293 for (const auto& row : data) {
294 if (pred(row)) {
295 result.push_back(row);
296 }
297 }
298 return result;
299}
300
309template <typename T>
310std::vector<T> filter_range(const std::vector<T>& data, T min_val, T max_val)
311{
312 return filter(data, [min_val, max_val](const T& val) {
313 return val >= min_val && val <= max_val;
314 });
315}
316
317// ============================================================================
318// Transformations
319// ============================================================================
320
326inline std::vector<double> log_transform(const std::vector<double>& data)
327{
328 std::vector<double> result;
329 result.reserve(data.size());
330
331 for (double val : data) {
332 if (val <= 0.0) {
333 result.push_back(NA);
334 } else {
335 result.push_back(std::log(val));
336 }
337 }
338 return result;
339}
340
346inline std::vector<double> log1p_transform(const std::vector<double>& data)
347{
348 std::vector<double> result;
349 result.reserve(data.size());
350
351 for (double val : data) {
352 if (val < -1.0) {
353 result.push_back(NA);
354 } else {
355 result.push_back(std::log1p(val));
356 }
357 }
358 return result;
359}
360
366inline std::vector<double> sqrt_transform(const std::vector<double>& data)
367{
368 std::vector<double> result;
369 result.reserve(data.size());
370
371 for (double val : data) {
372 if (val < 0.0) {
373 result.push_back(NA);
374 } else {
375 result.push_back(std::sqrt(val));
376 }
377 }
378 return result;
379}
380
390inline std::vector<double> boxcox_transform(const std::vector<double>& data, double lambda)
391{
392 std::vector<double> result;
393 result.reserve(data.size());
394
395 for (double val : data) {
396 if (val <= 0.0) {
397 result.push_back(NA);
398 } else if (std::abs(lambda) < 1e-10) {
399 result.push_back(std::log(val));
400 } else {
401 result.push_back((std::pow(val, lambda) - 1.0) / lambda);
402 }
403 }
404 return result;
405}
406
415inline std::vector<double> rank_transform(const std::vector<double>& data)
416{
417 std::size_t n = data.size();
418 if (n == 0) {
419 return {};
420 }
421
422 // Extract non-NaN indices
423 std::vector<std::size_t> valid_indices;
424 valid_indices.reserve(n);
425 for (std::size_t i = 0; i < n; ++i) {
426 if (!std::isnan(data[i])) {
427 valid_indices.push_back(i);
428 }
429 }
430
431 // Initialize result vector with NaN
432 std::vector<double> ranks(n, std::numeric_limits<double>::quiet_NaN());
433
434 if (valid_indices.empty()) {
435 return ranks;
436 }
437
438 // Sort non-NaN indices by value
439 std::sort(valid_indices.begin(), valid_indices.end(),
440 [&data](std::size_t a, std::size_t b) { return data[a] < data[b]; });
441
442 // Assign ranks (average rank for ties)
443 std::size_t i = 0;
444 std::size_t valid_n = valid_indices.size();
445 while (i < valid_n) {
446 std::size_t j = i;
447 // Find elements with the same value
448 while (j < valid_n && data[valid_indices[j]] == data[valid_indices[i]]) {
449 ++j;
450 }
451 // Compute average rank
452 double avg_rank = (static_cast<double>(i) + static_cast<double>(j) - 1.0) / 2.0 + 1.0;
453 for (std::size_t k = i; k < j; ++k) {
454 ranks[valid_indices[k]] = avg_rank;
455 }
456 i = j;
457 }
458 return ranks;
459}
460
461// ============================================================================
462// Group-by and Aggregation
463// ============================================================================
464
470template <typename K, typename V>
472 std::map<K, std::vector<V>> groups;
473};
474
479template <typename K>
481 std::vector<K> keys;
482 std::vector<double> values;
483};
484
493template <typename K, typename V>
494group_result<K, V> group_by(const std::vector<K>& keys, const std::vector<V>& values)
495{
496 if (keys.size() != values.size()) {
497 throw std::invalid_argument("statcpp::group_by: keys and values must have same size");
498 }
499
500 group_result<K, V> result;
501 for (std::size_t i = 0; i < keys.size(); ++i) {
502 result.groups[keys[i]].push_back(values[i]);
503 }
504 return result;
505}
506
514template <typename K>
515aggregation_result<K> group_mean(const std::vector<K>& keys, const std::vector<double>& values)
516{
517 auto groups = group_by(keys, values);
519
520 for (const auto& pair : groups.groups) {
521 result.keys.push_back(pair.first);
522 result.values.push_back(mean(pair.second.begin(), pair.second.end()));
523 }
524 return result;
525}
526
534template <typename K>
535aggregation_result<K> group_sum(const std::vector<K>& keys, const std::vector<double>& values)
536{
537 auto groups = group_by(keys, values);
539
540 for (const auto& pair : groups.groups) {
541 result.keys.push_back(pair.first);
542 result.values.push_back(sum(pair.second.begin(), pair.second.end()));
543 }
544 return result;
545}
546
554template <typename K>
555aggregation_result<K> group_count(const std::vector<K>& keys, const std::vector<double>& values)
556{
557 auto groups = group_by(keys, values);
559
560 for (const auto& pair : groups.groups) {
561 result.keys.push_back(pair.first);
562 result.values.push_back(static_cast<double>(pair.second.size()));
563 }
564 return result;
565}
566
567// ============================================================================
568// Sorting
569// ============================================================================
570
578template <typename T>
579std::vector<T> sort_values(const std::vector<T>& data, bool ascending = true)
580{
581 std::vector<T> result = data;
582 if (ascending) {
583 std::sort(result.begin(), result.end());
584 } else {
585 std::sort(result.begin(), result.end(), std::greater<T>());
586 }
587 return result;
588}
589
597template <typename T>
598std::vector<std::size_t> argsort(const std::vector<T>& data, bool ascending = true)
599{
600 std::vector<std::size_t> indices(data.size());
601 std::iota(indices.begin(), indices.end(), 0);
602
603 if (ascending) {
604 std::sort(indices.begin(), indices.end(),
605 [&data](std::size_t i, std::size_t j) { return data[i] < data[j]; });
606 } else {
607 std::sort(indices.begin(), indices.end(),
608 [&data](std::size_t i, std::size_t j) { return data[i] > data[j]; });
609 }
610 return indices;
611}
612
613// ============================================================================
614// Sampling
615// ============================================================================
616
624template <typename T>
625std::vector<T> sample_with_replacement(const std::vector<T>& data, std::size_t n)
626{
627 if (data.empty()) {
628 throw std::invalid_argument("statcpp::sample_with_replacement: empty data");
629 }
630
631 std::vector<T> result;
632 result.reserve(n);
633
634 auto& rng = get_random_engine();
635 std::uniform_int_distribution<std::size_t> dist(0, data.size() - 1);
636
637 for (std::size_t i = 0; i < n; ++i) {
638 result.push_back(data[dist(rng)]);
639 }
640 return result;
641}
642
650template <typename T>
651std::vector<T> sample_without_replacement(const std::vector<T>& data, std::size_t n)
652{
653 if (data.empty()) {
654 throw std::invalid_argument("statcpp::sample_without_replacement: empty data");
655 }
656 if (n > data.size()) {
657 throw std::invalid_argument("statcpp::sample_without_replacement: n > data.size()");
658 }
659
660 std::vector<T> pool = data;
661 auto& rng = get_random_engine();
662
663 // Execute only the first n iterations of Fisher-Yates shuffle
664 for (std::size_t i = 0; i < n; ++i) {
665 std::uniform_int_distribution<std::size_t> dist(i, pool.size() - 1);
666 std::swap(pool[i], pool[dist(rng)]);
667 }
668
669 return std::vector<T>(pool.begin(), pool.begin() + static_cast<std::ptrdiff_t>(n));
670}
671
681template <typename K, typename V>
682std::vector<V> stratified_sample(const std::vector<K>& strata,
683 const std::vector<V>& data,
684 double sample_ratio)
685{
686 if (strata.size() != data.size()) {
687 throw std::invalid_argument("statcpp::stratified_sample: strata and data must have same size");
688 }
689 if (sample_ratio <= 0.0 || sample_ratio > 1.0) {
690 throw std::invalid_argument("statcpp::stratified_sample: sample_ratio must be in (0, 1]");
691 }
692
693 // Group by stratum
694 auto groups = group_by(strata, data);
695
696 std::vector<V> result;
697 auto& rng = get_random_engine();
698
699 for (auto& pair : groups.groups) {
700 std::size_t n = static_cast<std::size_t>(std::ceil(pair.second.size() * sample_ratio));
701 n = std::min(n, pair.second.size());
702
703 // Shuffle and take first n elements
704 std::shuffle(pair.second.begin(), pair.second.end(), rng);
705 for (std::size_t i = 0; i < n; ++i) {
706 result.push_back(pair.second[i]);
707 }
708 }
709 return result;
710}
711
712// ============================================================================
713// Duplicate Handling
714// ============================================================================
715
722template <typename T>
723std::vector<T> drop_duplicates(const std::vector<T>& data)
724{
725 std::vector<T> result;
726 std::unordered_set<T> seen;
727
728 for (const auto& val : data) {
729 if (seen.find(val) == seen.end()) {
730 result.push_back(val);
731 seen.insert(val);
732 }
733 }
734 return result;
735}
736
743template <typename T>
744std::map<T, std::size_t> value_counts(const std::vector<T>& data)
745{
746 std::map<T, std::size_t> counts;
747 for (const auto& val : data) {
748 ++counts[val];
749 }
750 return counts;
751}
752
759template <typename T>
760std::vector<T> get_duplicates(const std::vector<T>& data)
761{
762 std::unordered_map<T, std::size_t> counts;
763 for (const auto& val : data) {
764 ++counts[val];
765 }
766
767 std::vector<T> result;
768 std::unordered_set<T> added;
769 for (const auto& pair : counts) {
770 if (pair.second > 1 && added.find(pair.first) == added.end()) {
771 result.push_back(pair.first);
772 added.insert(pair.first);
773 }
774 }
775 return result;
776}
777
778// ============================================================================
779// Rolling Aggregations
780// ============================================================================
781
788inline std::vector<double> rolling_mean(const std::vector<double>& data, std::size_t window)
789{
790 if (window == 0 || window > data.size()) {
791 throw std::invalid_argument("statcpp::rolling_mean: invalid window size");
792 }
793
794 std::vector<double> result;
795 result.reserve(data.size() - window + 1);
796
797 // Track the running sum over non-NaN values plus the NaN count in the current
798 // window, so a window containing NaN yields NaN without permanently corrupting
799 // the running sum (consistent with rolling_std/min/max).
800 double sum = 0.0;
801 std::size_t nan_count = 0;
802 for (std::size_t i = 0; i < window; ++i) {
803 if (std::isnan(data[i])) {
804 ++nan_count;
805 } else {
806 sum += data[i];
807 }
808 }
809 result.push_back(nan_count > 0 ? NA : sum / static_cast<double>(window));
810
811 for (std::size_t i = window; i < data.size(); ++i) {
812 if (std::isnan(data[i])) {
813 ++nan_count;
814 } else {
815 sum += data[i];
816 }
817 if (std::isnan(data[i - window])) {
818 --nan_count;
819 } else {
820 sum -= data[i - window];
821 }
822 result.push_back(nan_count > 0 ? NA : sum / static_cast<double>(window));
823 }
824 return result;
825}
826
833inline std::vector<double> rolling_std(const std::vector<double>& data, std::size_t window)
834{
835 if (window < 2 || window > data.size()) {
836 throw std::invalid_argument("statcpp::rolling_std: invalid window size");
837 }
838
839 std::vector<double> result;
840 result.reserve(data.size() - window + 1);
841
842 for (std::size_t i = 0; i <= data.size() - window; ++i) {
843 auto start = data.begin() + static_cast<std::ptrdiff_t>(i);
844 auto end = start + static_cast<std::ptrdiff_t>(window);
845 double m = mean(start, end);
846 double var = 0.0;
847 for (auto it = start; it != end; ++it) {
848 double diff = *it - m;
849 var += diff * diff;
850 }
851 result.push_back(std::sqrt(var / static_cast<double>(window - 1)));
852 }
853 return result;
854}
855
862inline std::vector<double> rolling_min(const std::vector<double>& data, std::size_t window)
863{
864 if (window == 0 || window > data.size()) {
865 throw std::invalid_argument("statcpp::rolling_min: invalid window size");
866 }
867
868 std::vector<double> result;
869 result.reserve(data.size() - window + 1);
870
871 for (std::size_t i = 0; i <= data.size() - window; ++i) {
872 auto start = data.begin() + static_cast<std::ptrdiff_t>(i);
873 auto end = start + static_cast<std::ptrdiff_t>(window);
874 result.push_back(*std::min_element(start, end));
875 }
876 return result;
877}
878
885inline std::vector<double> rolling_max(const std::vector<double>& data, std::size_t window)
886{
887 if (window == 0 || window > data.size()) {
888 throw std::invalid_argument("statcpp::rolling_max: invalid window size");
889 }
890
891 std::vector<double> result;
892 result.reserve(data.size() - window + 1);
893
894 for (std::size_t i = 0; i <= data.size() - window; ++i) {
895 auto start = data.begin() + static_cast<std::ptrdiff_t>(i);
896 auto end = start + static_cast<std::ptrdiff_t>(window);
897 result.push_back(*std::max_element(start, end));
898 }
899 return result;
900}
901
908inline std::vector<double> rolling_sum(const std::vector<double>& data, std::size_t window)
909{
910 if (window == 0 || window > data.size()) {
911 throw std::invalid_argument("statcpp::rolling_sum: invalid window size");
912 }
913
914 std::vector<double> result;
915 result.reserve(data.size() - window + 1);
916
917 // Track the running sum over non-NaN values plus the NaN count in the current
918 // window, so a window containing NaN yields NaN without permanently corrupting
919 // the running sum (consistent with rolling_std/min/max).
920 double s = 0.0;
921 std::size_t nan_count = 0;
922 for (std::size_t i = 0; i < window; ++i) {
923 if (std::isnan(data[i])) {
924 ++nan_count;
925 } else {
926 s += data[i];
927 }
928 }
929 result.push_back(nan_count > 0 ? NA : s);
930
931 for (std::size_t i = window; i < data.size(); ++i) {
932 if (std::isnan(data[i])) {
933 ++nan_count;
934 } else {
935 s += data[i];
936 }
937 if (std::isnan(data[i - window])) {
938 --nan_count;
939 } else {
940 s -= data[i - window];
941 }
942 result.push_back(nan_count > 0 ? NA : s);
943 }
944 return result;
945}
946
947// ============================================================================
948// Categorical Encoding
949// ============================================================================
950
955template <typename T>
957 std::vector<std::size_t> encoded;
958 std::map<T, std::size_t> mapping;
959 std::vector<T> classes;
960};
961
968template <typename T>
969label_encoding_result<T> label_encode(const std::vector<T>& data)
970{
972 std::map<T, std::size_t> mapping;
973 std::vector<T> classes;
974
975 result.encoded.reserve(data.size());
976
977 for (const auto& val : data) {
978 auto it = mapping.find(val);
979 if (it == mapping.end()) {
980 std::size_t idx = classes.size();
981 mapping[val] = idx;
982 classes.push_back(val);
983 result.encoded.push_back(idx);
984 } else {
985 result.encoded.push_back(it->second);
986 }
987 }
988
989 result.mapping = std::move(mapping);
990 result.classes = std::move(classes);
991 return result;
992}
993
1000template <typename T>
1001std::vector<std::vector<double>> one_hot_encode(const std::vector<T>& data)
1002{
1003 auto label_result = label_encode(data);
1004 std::size_t n_classes = label_result.classes.size();
1005 std::size_t n = data.size();
1006
1007 std::vector<std::vector<double>> result(n, std::vector<double>(n_classes, 0.0));
1008
1009 for (std::size_t i = 0; i < n; ++i) {
1010 result[i][label_result.encoded[i]] = 1.0;
1011 }
1012 return result;
1013}
1014
1021inline std::vector<std::size_t> bin_equal_width(const std::vector<double>& data, std::size_t n_bins)
1022{
1023 if (n_bins == 0) {
1024 throw std::invalid_argument("statcpp::bin_equal_width: n_bins must be > 0");
1025 }
1026 if (data.empty()) {
1027 return {};
1028 }
1029
1030 double min_val = *std::min_element(data.begin(), data.end());
1031 double max_val = *std::max_element(data.begin(), data.end());
1032
1033 if (min_val == max_val) {
1034 return std::vector<std::size_t>(data.size(), 0);
1035 }
1036
1037 double bin_width = (max_val - min_val) / static_cast<double>(n_bins);
1038
1039 std::vector<std::size_t> result;
1040 result.reserve(data.size());
1041
1042 for (double val : data) {
1043 auto bin = static_cast<std::size_t>((val - min_val) / bin_width);
1044 if (bin >= n_bins) {
1045 bin = n_bins - 1;
1046 }
1047 result.push_back(bin);
1048 }
1049 return result;
1050}
1051
1058inline std::vector<std::size_t> bin_equal_freq(const std::vector<double>& data, std::size_t n_bins)
1059{
1060 if (n_bins == 0) {
1061 throw std::invalid_argument("statcpp::bin_equal_freq: n_bins must be > 0");
1062 }
1063 if (data.empty()) {
1064 return {};
1065 }
1066
1067 // Get sorted indices
1068 auto sorted_idx = argsort(data);
1069 std::size_t n = data.size();
1070 std::size_t bin_size = (n + n_bins - 1) / n_bins; // Ceiling
1071
1072 std::vector<std::size_t> result(n);
1073
1074 for (std::size_t i = 0; i < n; ++i) {
1075 std::size_t bin = i / bin_size;
1076 if (bin >= n_bins) {
1077 bin = n_bins - 1;
1078 }
1079 result[sorted_idx[i]] = bin;
1080 }
1081 return result;
1082}
1083
1084// ============================================================================
1085// Data Validation
1086// ============================================================================
1087
1092 bool is_valid = true;
1093 std::size_t n_missing = 0;
1094 std::size_t n_infinite = 0;
1095 std::size_t n_negative = 0;
1096 std::vector<std::size_t> missing_indices;
1097 std::vector<std::size_t> infinite_indices;
1098 std::vector<std::size_t> negative_indices;
1099};
1100
1109inline validation_result validate_data(const std::vector<double>& data,
1110 bool allow_missing = false,
1111 bool allow_infinite = false,
1112 bool allow_negative = true)
1113{
1114 validation_result result;
1115
1116 for (std::size_t i = 0; i < data.size(); ++i) {
1117 double val = data[i];
1118
1119 if (std::isnan(val)) {
1120 ++result.n_missing;
1121 result.missing_indices.push_back(i);
1122 if (!allow_missing) {
1123 result.is_valid = false;
1124 }
1125 } else if (std::isinf(val)) {
1126 ++result.n_infinite;
1127 result.infinite_indices.push_back(i);
1128 if (!allow_infinite) {
1129 result.is_valid = false;
1130 }
1131 } else if (val < 0.0) {
1132 ++result.n_negative;
1133 result.negative_indices.push_back(i);
1134 if (!allow_negative) {
1135 result.is_valid = false;
1136 }
1137 }
1138 }
1139 return result;
1140}
1141
1149inline bool validate_range(const std::vector<double>& data,
1150 double min_val = -std::numeric_limits<double>::infinity(),
1151 double max_val = std::numeric_limits<double>::infinity())
1152{
1153 for (double val : data) {
1154 if (std::isnan(val)) {
1155 continue; // NA is not considered out of range
1156 }
1157 if (val < min_val || val > max_val) {
1158 return false;
1159 }
1160 }
1161 return true;
1162}
1163
1164} // namespace statcpp
Basic statistical computation functions.
std::vector< T > fillna(const std::vector< T > &data, T fill_value)
Fill NA with a specified value.
std::vector< double > rolling_min(const std::vector< double > &data, std::size_t window)
Moving minimum.
std::map< T, std::size_t > value_counts(const std::vector< T > &data)
Count duplicates.
std::vector< double > rank_transform(const std::vector< double > &data)
Rank transformation.
group_result< K, V > group_by(const std::vector< K > &keys, const std::vector< V > &values)
Group by.
std::vector< T > sample_without_replacement(const std::vector< T > &data, std::size_t n)
Random sampling (without replacement)
std::vector< V > stratified_sample(const std::vector< K > &strata, const std::vector< V > &data, double sample_ratio)
Stratified sampling.
label_encoding_result< T > label_encode(const std::vector< T > &data)
Label encoding.
std::vector< double > rolling_std(const std::vector< double > &data, std::size_t window)
Moving standard deviation.
auto sum(Iterator first, Iterator last)
Sum.
std::vector< double > log_transform(const std::vector< double > &data)
Logarithmic transformation (natural logarithm)
std::vector< double > rolling_mean(const std::vector< double > &data, std::size_t window)
Moving average.
double var(Iterator first, Iterator last, std::size_t ddof=0)
Variance (ddof = Delta Degrees of Freedom)
std::vector< double > fillna_median(const std::vector< double > &data)
Fill NA with median.
std::vector< double > log1p_transform(const std::vector< double > &data)
Logarithmic transformation (log1p: log(1 + x))
std::vector< T > filter_range(const std::vector< T > &data, T min_val, T max_val)
Filter values within a range.
std::vector< T > filter(const std::vector< T > &data, Predicate pred)
Filter elements that match a condition.
std::vector< T > get_duplicates(const std::vector< T > &data)
Get duplicate values.
std::vector< double > fillna_bfill(const std::vector< double > &data)
Fill NA with backward fill.
std::vector< T > sample_with_replacement(const std::vector< T > &data, std::size_t n)
Random sampling (with replacement)
std::vector< T > drop_duplicates(const std::vector< T > &data)
Drop duplicates.
constexpr double NA
Constant representing NA (NaN)
std::vector< double > boxcox_transform(const std::vector< double > &data, double lambda)
Box-Cox transformation.
std::vector< std::vector< T > > dropna(const std::vector< std::vector< T > > &data)
Drop rows containing NA.
double mean(Iterator first, Iterator last)
Arithmetic mean.
aggregation_result< K > group_count(const std::vector< K > &keys, const std::vector< double > &values)
Count per group.
aggregation_result< K > group_mean(const std::vector< K > &keys, const std::vector< double > &values)
Mean per group.
std::vector< double > diff(Iterator first, Iterator last, std::size_t order=1)
Difference series (first-order or d-th order differencing)
std::vector< double > fillna_interpolate(const std::vector< double > &data)
Fill NA with linear interpolation.
std::vector< std::vector< double > > one_hot_encode(const std::vector< T > &data)
One-hot encoding.
default_random_engine & get_random_engine()
Singleton accessor for global random engine.
double median(Iterator first, Iterator last)
Median (accepts a sorted range)
std::vector< double > fillna_ffill(const std::vector< double > &data)
Fill NA with forward fill.
validation_result validate_data(const std::vector< double > &data, bool allow_missing=false, bool allow_infinite=false, bool allow_negative=true)
Data validation.
std::vector< double > rolling_max(const std::vector< double > &data, std::size_t window)
Moving maximum.
std::vector< std::size_t > bin_equal_freq(const std::vector< double > &data, std::size_t n_bins)
Binning (equal frequency)
bool validate_range(const std::vector< double > &data, double min_val=-std::numeric_limits< double >::infinity(), double max_val=std::numeric_limits< double >::infinity())
Range validation.
std::vector< double > sqrt_transform(const std::vector< double > &data)
Square root transformation.
aggregation_result< K > group_sum(const std::vector< K > &keys, const std::vector< double > &values)
Sum per group.
bool is_na(double x)
Check if a value is NA.
std::vector< std::size_t > bin_equal_width(const std::vector< double > &data, std::size_t n_bins)
Binning (equal width)
std::vector< double > fillna_mean(const std::vector< double > &data)
Fill NA with mean.
std::vector< T > sort_values(const std::vector< T > &data, bool ascending=true)
Return a sorted vector (ascending)
std::vector< std::vector< T > > filter_rows(const std::vector< std::vector< T > > &data, Predicate pred)
Filter rows that match a condition (2-dimensional)
std::vector< std::size_t > argsort(const std::vector< T > &data, bool ascending=true)
Return indices in sorted order.
std::vector< double > rolling_sum(const std::vector< double > &data, std::size_t window)
Moving sum.
Random engine wrapper and utilities.
Aggregation result per group.
std::vector< K > keys
Vector of keys.
std::vector< double > values
Vector of aggregated values.
std::map< K, std::vector< V > > groups
Values for each group.
std::map< T, std::size_t > mapping
Mapping from original values to encoded values.
std::vector< T > classes
List of classes.
std::vector< std::size_t > encoded
Encoded values.
Data validation result.
std::size_t n_infinite
Number of infinite values.
std::vector< std::size_t > negative_indices
Indices of negative values.
bool is_valid
Whether data is valid.
std::vector< std::size_t > infinite_indices
Indices of infinite values.
std::vector< std::size_t > missing_indices
Indices of missing values.
std::size_t n_missing
Number of missing values.
std::size_t n_negative
Number of negative values.