21#include <unordered_map>
22#include <unordered_set>
38inline constexpr double NA = std::numeric_limits<double>::quiet_NaN();
56std::vector<std::vector<T>>
dropna(
const std::vector<std::vector<T>>& data)
58 std::vector<std::vector<T>> result;
59 result.reserve(data.size());
61 for (
const auto& row : data) {
63 for (
const auto& val : row) {
64 if constexpr (std::is_floating_point_v<T>) {
65 if (std::isnan(
static_cast<double>(val))) {
72 result.push_back(row);
85std::vector<T>
dropna(
const std::vector<T>& data)
87 std::vector<T> result;
88 result.reserve(data.size());
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);
96 result.push_back(val);
110std::vector<T>
fillna(
const std::vector<T>& data, T fill_value)
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))) {
128inline std::vector<double>
fillna_mean(
const std::vector<double>& data)
130 std::vector<double> non_na;
131 non_na.reserve(data.size());
133 for (
double val : data) {
134 if (!std::isnan(val)) {
135 non_na.push_back(val);
139 if (non_na.empty()) {
143 double m =
mean(non_na.begin(), non_na.end());
154 std::vector<double> non_na;
155 non_na.reserve(data.size());
157 for (
double val : data) {
158 if (!std::isnan(val)) {
159 non_na.push_back(val);
163 if (non_na.empty()) {
167 double med =
median(non_na.begin(), non_na.end());
176inline std::vector<double>
fillna_ffill(
const std::vector<double>& data)
178 std::vector<double> result = data;
179 double last_valid =
NA;
181 for (
auto& val : result) {
182 if (!std::isnan(val)) {
184 }
else if (!std::isnan(last_valid)) {
196inline std::vector<double>
fillna_bfill(
const std::vector<double>& data)
198 std::vector<double> result = data;
199 double next_valid =
NA;
201 for (
auto it = result.rbegin(); it != result.rend(); ++it) {
202 if (!std::isnan(*it)) {
204 }
else if (!std::isnan(next_valid)) {
218 std::vector<double> result = data;
219 std::size_t n = result.size();
221 for (std::size_t i = 0; i < n; ++i) {
222 if (std::isnan(result[i])) {
224 std::size_t prev_idx = i;
225 std::size_t next_idx = i;
228 while (prev_idx > 0 && std::isnan(result[prev_idx])) {
231 if (std::isnan(result[prev_idx])) {
236 while (next_idx < n - 1 && std::isnan(result[next_idx])) {
239 if (std::isnan(result[next_idx])) {
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);
265template <
typename T,
typename Predicate>
266std::vector<T>
filter(
const std::vector<T>& data, Predicate pred)
268 std::vector<T> result;
269 result.reserve(data.size());
271 for (
const auto& val : data) {
273 result.push_back(val);
287template <
typename T,
typename Predicate>
288std::vector<std::vector<T>>
filter_rows(
const std::vector<std::vector<T>>& data, Predicate pred)
290 std::vector<std::vector<T>> result;
291 result.reserve(data.size());
293 for (
const auto& row : data) {
295 result.push_back(row);
310std::vector<T>
filter_range(
const std::vector<T>& data, T min_val, T max_val)
312 return filter(data, [min_val, max_val](
const T& val) {
313 return val >= min_val && val <= max_val;
328 std::vector<double> result;
329 result.reserve(data.size());
331 for (
double val : data) {
333 result.push_back(
NA);
335 result.push_back(std::log(val));
348 std::vector<double> result;
349 result.reserve(data.size());
351 for (
double val : data) {
353 result.push_back(
NA);
355 result.push_back(std::log1p(val));
368 std::vector<double> result;
369 result.reserve(data.size());
371 for (
double val : data) {
373 result.push_back(
NA);
375 result.push_back(std::sqrt(val));
392 std::vector<double> result;
393 result.reserve(data.size());
395 for (
double val : data) {
397 result.push_back(
NA);
398 }
else if (std::abs(lambda) < 1e-10) {
399 result.push_back(std::log(val));
401 result.push_back((std::pow(val, lambda) - 1.0) / lambda);
417 std::size_t n = data.size();
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);
432 std::vector<double> ranks(n, std::numeric_limits<double>::quiet_NaN());
434 if (valid_indices.empty()) {
439 std::sort(valid_indices.begin(), valid_indices.end(),
440 [&data](std::size_t a, std::size_t b) { return data[a] < data[b]; });
444 std::size_t valid_n = valid_indices.size();
445 while (i < valid_n) {
448 while (j < valid_n && data[valid_indices[j]] == data[valid_indices[i]]) {
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;
470template <
typename K,
typename V>
493template <
typename K,
typename V>
496 if (keys.size() != values.size()) {
497 throw std::invalid_argument(
"statcpp::group_by: keys and values must have same size");
501 for (std::size_t i = 0; i < keys.size(); ++i) {
502 result.
groups[keys[i]].push_back(values[i]);
517 auto groups =
group_by(keys, values);
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()));
537 auto groups =
group_by(keys, values);
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()));
557 auto groups =
group_by(keys, values);
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()));
579std::vector<T>
sort_values(
const std::vector<T>& data,
bool ascending =
true)
581 std::vector<T> result = data;
583 std::sort(result.begin(), result.end());
585 std::sort(result.begin(), result.end(), std::greater<T>());
598std::vector<std::size_t>
argsort(
const std::vector<T>& data,
bool ascending =
true)
600 std::vector<std::size_t> indices(data.size());
601 std::iota(indices.begin(), indices.end(), 0);
604 std::sort(indices.begin(), indices.end(),
605 [&data](std::size_t i, std::size_t j) { return data[i] < data[j]; });
607 std::sort(indices.begin(), indices.end(),
608 [&data](std::size_t i, std::size_t j) { return data[i] > data[j]; });
628 throw std::invalid_argument(
"statcpp::sample_with_replacement: empty data");
631 std::vector<T> result;
635 std::uniform_int_distribution<std::size_t> dist(0, data.size() - 1);
637 for (std::size_t i = 0; i < n; ++i) {
638 result.push_back(data[dist(rng)]);
654 throw std::invalid_argument(
"statcpp::sample_without_replacement: empty data");
656 if (n > data.size()) {
657 throw std::invalid_argument(
"statcpp::sample_without_replacement: n > data.size()");
660 std::vector<T> pool = data;
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)]);
669 return std::vector<T>(pool.begin(), pool.begin() +
static_cast<std::ptrdiff_t
>(n));
681template <
typename K,
typename V>
683 const std::vector<V>& data,
686 if (strata.size() != data.size()) {
687 throw std::invalid_argument(
"statcpp::stratified_sample: strata and data must have same size");
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]");
694 auto groups =
group_by(strata, data);
696 std::vector<V> result;
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());
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]);
725 std::vector<T> result;
726 std::unordered_set<T> seen;
728 for (
const auto& val : data) {
729 if (seen.find(val) == seen.end()) {
730 result.push_back(val);
746 std::map<T, std::size_t> counts;
747 for (
const auto& val : data) {
762 std::unordered_map<T, std::size_t> counts;
763 for (
const auto& val : data) {
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);
788inline std::vector<double>
rolling_mean(
const std::vector<double>& data, std::size_t window)
790 if (window == 0 || window > data.size()) {
791 throw std::invalid_argument(
"statcpp::rolling_mean: invalid window size");
794 std::vector<double> result;
795 result.reserve(data.size() - window + 1);
801 std::size_t nan_count = 0;
802 for (std::size_t i = 0; i < window; ++i) {
803 if (std::isnan(data[i])) {
809 result.push_back(nan_count > 0 ?
NA :
sum /
static_cast<double>(window));
811 for (std::size_t i = window; i < data.size(); ++i) {
812 if (std::isnan(data[i])) {
817 if (std::isnan(data[i - window])) {
820 sum -= data[i - window];
822 result.push_back(nan_count > 0 ?
NA :
sum /
static_cast<double>(window));
833inline std::vector<double>
rolling_std(
const std::vector<double>& data, std::size_t window)
835 if (window < 2 || window > data.size()) {
836 throw std::invalid_argument(
"statcpp::rolling_std: invalid window size");
839 std::vector<double> result;
840 result.reserve(data.size() - window + 1);
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);
847 for (
auto it = start; it != end; ++it) {
848 double diff = *it - m;
851 result.push_back(std::sqrt(
var /
static_cast<double>(window - 1)));
862inline std::vector<double>
rolling_min(
const std::vector<double>& data, std::size_t window)
864 if (window == 0 || window > data.size()) {
865 throw std::invalid_argument(
"statcpp::rolling_min: invalid window size");
868 std::vector<double> result;
869 result.reserve(data.size() - window + 1);
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));
885inline std::vector<double>
rolling_max(
const std::vector<double>& data, std::size_t window)
887 if (window == 0 || window > data.size()) {
888 throw std::invalid_argument(
"statcpp::rolling_max: invalid window size");
891 std::vector<double> result;
892 result.reserve(data.size() - window + 1);
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));
908inline std::vector<double>
rolling_sum(
const std::vector<double>& data, std::size_t window)
910 if (window == 0 || window > data.size()) {
911 throw std::invalid_argument(
"statcpp::rolling_sum: invalid window size");
914 std::vector<double> result;
915 result.reserve(data.size() - window + 1);
921 std::size_t nan_count = 0;
922 for (std::size_t i = 0; i < window; ++i) {
923 if (std::isnan(data[i])) {
929 result.push_back(nan_count > 0 ?
NA : s);
931 for (std::size_t i = window; i < data.size(); ++i) {
932 if (std::isnan(data[i])) {
937 if (std::isnan(data[i - window])) {
940 s -= data[i - window];
942 result.push_back(nan_count > 0 ?
NA : s);
972 std::map<T, std::size_t> mapping;
973 std::vector<T> classes;
975 result.
encoded.reserve(data.size());
977 for (
const auto& val : data) {
978 auto it = mapping.find(val);
979 if (it == mapping.end()) {
980 std::size_t idx = classes.size();
982 classes.push_back(val);
985 result.
encoded.push_back(it->second);
989 result.
mapping = std::move(mapping);
990 result.
classes = std::move(classes);
1000template <
typename T>
1004 std::size_t n_classes = label_result.classes.size();
1005 std::size_t n = data.size();
1007 std::vector<std::vector<double>> result(n, std::vector<double>(n_classes, 0.0));
1009 for (std::size_t i = 0; i < n; ++i) {
1010 result[i][label_result.encoded[i]] = 1.0;
1021inline std::vector<std::size_t>
bin_equal_width(
const std::vector<double>& data, std::size_t n_bins)
1024 throw std::invalid_argument(
"statcpp::bin_equal_width: n_bins must be > 0");
1030 double min_val = *std::min_element(data.begin(), data.end());
1031 double max_val = *std::max_element(data.begin(), data.end());
1033 if (min_val == max_val) {
1034 return std::vector<std::size_t>(data.size(), 0);
1037 double bin_width = (max_val - min_val) /
static_cast<double>(n_bins);
1039 std::vector<std::size_t> result;
1040 result.reserve(data.size());
1042 for (
double val : data) {
1043 auto bin =
static_cast<std::size_t
>((val - min_val) / bin_width);
1044 if (bin >= n_bins) {
1047 result.push_back(bin);
1058inline std::vector<std::size_t>
bin_equal_freq(
const std::vector<double>& data, std::size_t n_bins)
1061 throw std::invalid_argument(
"statcpp::bin_equal_freq: n_bins must be > 0");
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;
1072 std::vector<std::size_t> result(n);
1074 for (std::size_t i = 0; i < n; ++i) {
1075 std::size_t bin = i / bin_size;
1076 if (bin >= n_bins) {
1079 result[sorted_idx[i]] = bin;
1110 bool allow_missing =
false,
1111 bool allow_infinite =
false,
1112 bool allow_negative =
true)
1116 for (std::size_t i = 0; i < data.size(); ++i) {
1117 double val = data[i];
1119 if (std::isnan(val)) {
1122 if (!allow_missing) {
1125 }
else if (std::isinf(val)) {
1128 if (!allow_infinite) {
1131 }
else if (val < 0.0) {
1134 if (!allow_negative) {
1150 double min_val = -std::numeric_limits<double>::infinity(),
1151 double max_val = std::numeric_limits<double>::infinity())
1153 for (
double val : data) {
1154 if (std::isnan(val)) {
1157 if (val < min_val || val > max_val) {
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.
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.