statcpp
C++17 Header-Only Statistics Library
Loading...
Searching...
No Matches
resampling.hpp
Go to the documentation of this file.
1
10#pragma once
11
17
18#include <algorithm>
19#include <cmath>
20#include <cstddef>
21#include <iterator>
22#include <numeric>
23#include <random>
24#include <stdexcept>
25#include <utility>
26#include <vector>
27
28namespace statcpp {
29
30// ============================================================================
31// Bootstrap Result Structure
32// ============================================================================
33
41 double estimate;
43 double ci_lower;
44 double ci_upper;
45 double bias;
46 std::vector<double> replicates;
47};
48
49// ============================================================================
50// Bootstrap Sampling
51// ============================================================================
52
67template <typename Iterator, typename Engine = default_random_engine>
68std::vector<typename std::iterator_traits<Iterator>::value_type>
69bootstrap_sample(Iterator first, Iterator last, Engine& engine)
70{
71 auto n = statcpp::count(first, last);
72 if (n == 0) {
73 throw std::invalid_argument("statcpp::bootstrap_sample: empty range");
74 }
75
76 using value_type = typename std::iterator_traits<Iterator>::value_type;
77 std::vector<value_type> original(first, last);
78 std::vector<value_type> sample(n);
79
80 std::uniform_int_distribution<std::size_t> dist(0, n - 1);
81
82 for (std::size_t i = 0; i < n; ++i) {
83 sample[i] = original[dist(engine)];
84 }
85
86 return sample;
87}
88
100template <typename Iterator>
101std::vector<typename std::iterator_traits<Iterator>::value_type>
102bootstrap_sample(Iterator first, Iterator last)
103{
104 return bootstrap_sample(first, last, get_random_engine());
105}
106
107// ============================================================================
108// Bootstrap Estimation
109// ============================================================================
110
138template <typename Iterator, typename Statistic, typename Engine = default_random_engine>
139bootstrap_result bootstrap(Iterator first, Iterator last, Statistic stat_func,
140 std::size_t n_bootstrap = 1000, double confidence = 0.95,
141 Engine& engine = get_random_engine())
142{
143 if (confidence <= 0.0 || confidence >= 1.0) {
144 throw std::invalid_argument("statcpp::bootstrap: confidence must be in (0, 1)");
145 }
146
147 if (n_bootstrap < 2) {
148 throw std::invalid_argument("statcpp::bootstrap: n_bootstrap must be at least 2");
149 }
150
151 auto n = statcpp::count(first, last);
152 if (n < 2) {
153 throw std::invalid_argument("statcpp::bootstrap: need at least 2 elements");
154 }
155
156 using value_type = typename std::iterator_traits<Iterator>::value_type;
157 std::vector<value_type> original(first, last);
158
159 // Original statistic
160 double theta_hat = stat_func(original.begin(), original.end());
161
162 // Bootstrap replicates
163 std::vector<double> replicates(n_bootstrap);
164
165 for (std::size_t b = 0; b < n_bootstrap; ++b) {
166 auto sample = bootstrap_sample(original.begin(), original.end(), engine);
167 replicates[b] = stat_func(sample.begin(), sample.end());
168 }
169
170 // Sort replicates for percentile CI
171 std::vector<double> sorted_replicates = replicates;
172 std::sort(sorted_replicates.begin(), sorted_replicates.end());
173
174 // Standard error
175 double mean_rep = std::accumulate(replicates.begin(), replicates.end(), 0.0) / n_bootstrap;
176 double se = 0.0;
177 for (double rep : replicates) {
178 se += (rep - mean_rep) * (rep - mean_rep);
179 }
180 se = std::sqrt(se / (n_bootstrap - 1));
181
182 // Bias
183 double bias = mean_rep - theta_hat;
184
185 // Percentile CI (0-indexed)
186 // lower_idx corresponds to the (alpha/2)-th percentile
187 // upper_idx corresponds to the (1 - alpha/2)-th percentile
188 double alpha = 1.0 - confidence;
189 std::size_t lower_idx = static_cast<std::size_t>(std::floor(alpha / 2.0 * static_cast<double>(n_bootstrap)));
190 std::size_t upper_idx = static_cast<std::size_t>(std::floor((1.0 - alpha / 2.0) * static_cast<double>(n_bootstrap)));
191 if (upper_idx > 0) upper_idx -= 1;
192
193 // Boundary check
194 if (lower_idx >= n_bootstrap) lower_idx = n_bootstrap - 1;
195 if (upper_idx >= n_bootstrap) upper_idx = n_bootstrap - 1;
196
197 double ci_lower = sorted_replicates[lower_idx];
198 double ci_upper = sorted_replicates[upper_idx];
199
200 return {theta_hat, se, ci_lower, ci_upper, bias, std::move(replicates)};
201}
202
220template <typename Iterator, typename Engine = default_random_engine>
221bootstrap_result bootstrap_mean(Iterator first, Iterator last,
222 std::size_t n_bootstrap = 1000, double confidence = 0.95,
223 Engine& engine = get_random_engine())
224{
225 auto stat_func = [](auto f, auto l) { return statcpp::mean(f, l); };
226 return bootstrap(first, last, stat_func, n_bootstrap, confidence, engine);
227}
228
246template <typename Iterator, typename Engine = default_random_engine>
247bootstrap_result bootstrap_median(Iterator first, Iterator last,
248 std::size_t n_bootstrap = 1000, double confidence = 0.95,
249 Engine& engine = get_random_engine())
250{
251 auto stat_func = [](auto f, auto l) {
252 std::vector<typename std::iterator_traits<decltype(f)>::value_type> sorted(f, l);
253 std::sort(sorted.begin(), sorted.end());
254 return statcpp::median(sorted.begin(), sorted.end());
255 };
256 return bootstrap(first, last, stat_func, n_bootstrap, confidence, engine);
257}
258
276template <typename Iterator, typename Engine = default_random_engine>
277bootstrap_result bootstrap_stddev(Iterator first, Iterator last,
278 std::size_t n_bootstrap = 1000, double confidence = 0.95,
279 Engine& engine = get_random_engine())
280{
281 auto stat_func = [](auto f, auto l) { return statcpp::sample_stddev(f, l); };
282 return bootstrap(first, last, stat_func, n_bootstrap, confidence, engine);
283}
284
285// ============================================================================
286// BCa Bootstrap (Bias-corrected and accelerated)
287// ============================================================================
288
309template <typename Iterator, typename Statistic, typename Engine = default_random_engine>
310bootstrap_result bootstrap_bca(Iterator first, Iterator last, Statistic stat_func,
311 std::size_t n_bootstrap = 1000, double confidence = 0.95,
312 Engine& engine = get_random_engine())
313{
314 if (confidence <= 0.0 || confidence >= 1.0) {
315 throw std::invalid_argument("statcpp::bootstrap_bca: confidence must be in (0, 1)");
316 }
317
318 if (n_bootstrap < 2) {
319 throw std::invalid_argument("statcpp::bootstrap_bca: n_bootstrap must be at least 2");
320 }
321
322 auto n = statcpp::count(first, last);
323 if (n < 3) {
324 throw std::invalid_argument("statcpp::bootstrap_bca: need at least 3 elements");
325 }
326
327 using value_type = typename std::iterator_traits<Iterator>::value_type;
328 std::vector<value_type> original(first, last);
329
330 // Original statistic
331 double theta_hat = stat_func(original.begin(), original.end());
332
333 // Bootstrap replicates
334 std::vector<double> replicates(n_bootstrap);
335
336 for (std::size_t b = 0; b < n_bootstrap; ++b) {
337 auto sample = bootstrap_sample(original.begin(), original.end(), engine);
338 replicates[b] = stat_func(sample.begin(), sample.end());
339 }
340
341 // Bias correction factor z0
342 std::size_t count_less = 0;
343 for (double rep : replicates) {
344 if (rep < theta_hat) ++count_less;
345 }
346 // Clip to [1, B-1] to avoid norm_quantile(0) = -inf or norm_quantile(1) = +inf
347 if (count_less == 0) count_less = 1;
348 if (count_less >= n_bootstrap) count_less = n_bootstrap - 1;
349 double z0 = norm_quantile(static_cast<double>(count_less) / n_bootstrap);
350
351 // Acceleration factor using jackknife
352 std::vector<double> jackknife_estimates(n);
353 for (std::size_t i = 0; i < n; ++i) {
354 std::vector<value_type> jackknife_sample;
355 jackknife_sample.reserve(n - 1);
356 for (std::size_t j = 0; j < n; ++j) {
357 if (j != i) {
358 jackknife_sample.push_back(original[j]);
359 }
360 }
361 jackknife_estimates[i] = stat_func(jackknife_sample.begin(), jackknife_sample.end());
362 }
363
364 double jack_mean = std::accumulate(jackknife_estimates.begin(), jackknife_estimates.end(), 0.0) / n;
365 double sum_cubed = 0.0;
366 double sum_squared = 0.0;
367 for (double j : jackknife_estimates) {
368 double d = jack_mean - j;
369 sum_squared += d * d;
370 sum_cubed += d * d * d;
371 }
372
373 double a = (sum_squared == 0.0) ? 0.0 : sum_cubed / (6.0 * std::pow(sum_squared, 1.5));
374
375 // Adjusted percentiles
376 double alpha = 1.0 - confidence;
377 double z_alpha_lower = norm_quantile(alpha / 2.0);
378 double z_alpha_upper = norm_quantile(1.0 - alpha / 2.0);
379
380 double alpha1 = norm_cdf(z0 + (z0 + z_alpha_lower) / (1.0 - a * (z0 + z_alpha_lower)));
381 double alpha2 = norm_cdf(z0 + (z0 + z_alpha_upper) / (1.0 - a * (z0 + z_alpha_upper)));
382
383 // Sort replicates
384 std::vector<double> sorted_replicates = replicates;
385 std::sort(sorted_replicates.begin(), sorted_replicates.end());
386
387 auto clamp_index = [&](double a_val) -> std::size_t {
388 double idx = a_val * static_cast<double>(n_bootstrap);
389 if (idx < 0.0) return 0;
390 if (idx >= static_cast<double>(n_bootstrap)) return n_bootstrap - 1;
391 return static_cast<std::size_t>(idx);
392 };
393 std::size_t lower_idx = clamp_index(alpha1);
394 std::size_t upper_idx = clamp_index(alpha2);
395
396 double ci_lower = sorted_replicates[lower_idx];
397 double ci_upper = sorted_replicates[upper_idx];
398
399 // Standard error
400 double mean_rep = std::accumulate(replicates.begin(), replicates.end(), 0.0) / n_bootstrap;
401 double se = 0.0;
402 for (double rep : replicates) {
403 se += (rep - mean_rep) * (rep - mean_rep);
404 }
405 se = std::sqrt(se / (n_bootstrap - 1));
406
407 double bias = mean_rep - theta_hat;
408
409 return {theta_hat, se, ci_lower, ci_upper, bias, std::move(replicates)};
410}
411
412// ============================================================================
413// Permutation Test Result
414// ============================================================================
415
424 double p_value;
425 std::size_t n_permutations;
426 std::vector<double> permutation_distribution;
427};
428
429// ============================================================================
430// Permutation Test (Two-Sample)
431// ============================================================================
432
456template <typename Iterator1, typename Iterator2, typename Engine = default_random_engine>
457permutation_result permutation_test_two_sample(Iterator1 first1, Iterator1 last1,
458 Iterator2 first2, Iterator2 last2,
459 std::size_t n_permutations = 10000,
460 Engine& engine = get_random_engine())
461{
462 auto n1 = statcpp::count(first1, last1);
463 auto n2 = statcpp::count(first2, last2);
464
465 if (n1 == 0 || n2 == 0) {
466 throw std::invalid_argument("statcpp::permutation_test_two_sample: empty sample");
467 }
468
469 // Combine samples
470 std::vector<double> combined;
471 combined.reserve(n1 + n2);
472 for (auto it = first1; it != last1; ++it) {
473 combined.push_back(static_cast<double>(*it));
474 }
475 for (auto it = first2; it != last2; ++it) {
476 combined.push_back(static_cast<double>(*it));
477 }
478
479 // Observed statistic
480 double mean1 = statcpp::mean(first1, last1);
481 double mean2 = statcpp::mean(first2, last2);
482 double observed = mean1 - mean2;
483
484 // Permutation distribution
485 std::vector<double> perm_stats(n_permutations);
486
487 for (std::size_t p = 0; p < n_permutations; ++p) {
488 std::shuffle(combined.begin(), combined.end(), engine);
489
490 double perm_mean1 = std::accumulate(combined.begin(), combined.begin() + n1, 0.0) / n1;
491 double perm_mean2 = std::accumulate(combined.begin() + n1, combined.end(), 0.0) / n2;
492 perm_stats[p] = perm_mean1 - perm_mean2;
493 }
494
495 // Two-sided p-value (inclusive method)
496 // Include the observed statistic as part of the null distribution
497 double abs_observed = std::abs(observed);
498 std::size_t count_extreme = 1; // Count the observed statistic itself
499 for (double stat : perm_stats) {
500 if (std::abs(stat) >= abs_observed) {
501 ++count_extreme;
502 }
503 }
504
505 double p_value = static_cast<double>(count_extreme) / static_cast<double>(n_permutations + 1);
506
507 return {observed, p_value, n_permutations, std::move(perm_stats)};
508}
509
510// ============================================================================
511// Permutation Test (Paired)
512// ============================================================================
513
537template <typename Iterator1, typename Iterator2, typename Engine = default_random_engine>
538permutation_result permutation_test_paired(Iterator1 first1, Iterator1 last1,
539 Iterator2 first2, Iterator2 last2,
540 std::size_t n_permutations = 10000,
541 Engine& engine = get_random_engine())
542{
543 auto n1 = statcpp::count(first1, last1);
544 auto n2 = statcpp::count(first2, last2);
545
546 if (n1 != n2) {
547 throw std::invalid_argument("statcpp::permutation_test_paired: samples must have equal length");
548 }
549 if (n1 == 0) {
550 throw std::invalid_argument("statcpp::permutation_test_paired: empty samples");
551 }
552
553 // Compute differences
554 std::vector<double> diffs;
555 diffs.reserve(n1);
556
557 auto it1 = first1;
558 auto it2 = first2;
559 while (it1 != last1) {
560 diffs.push_back(static_cast<double>(*it1) - static_cast<double>(*it2));
561 ++it1;
562 ++it2;
563 }
564
565 // Observed statistic (mean of differences)
566 double observed = statcpp::mean(diffs.begin(), diffs.end());
567
568 // Permutation by randomly flipping signs
569 std::vector<double> perm_stats(n_permutations);
570 std::uniform_int_distribution<int> coin(0, 1);
571
572 for (std::size_t p = 0; p < n_permutations; ++p) {
573 double sum = 0.0;
574 for (double d : diffs) {
575 sum += (coin(engine) == 0) ? d : -d;
576 }
577 perm_stats[p] = sum / static_cast<double>(n1);
578 }
579
580 // Two-sided p-value (inclusive method)
581 double abs_observed = std::abs(observed);
582 std::size_t count_extreme = 1; // Count the observed statistic itself
583 for (double stat : perm_stats) {
584 if (std::abs(stat) >= abs_observed) {
585 ++count_extreme;
586 }
587 }
588
589 double p_value = static_cast<double>(count_extreme) / static_cast<double>(n_permutations + 1);
590
591 return {observed, p_value, n_permutations, std::move(perm_stats)};
592}
593
594// ============================================================================
595// Permutation Test for Correlation
596// ============================================================================
597
624template <typename Iterator1, typename Iterator2, typename Engine = default_random_engine>
625permutation_result permutation_test_correlation(Iterator1 first1, Iterator1 last1,
626 Iterator2 first2, Iterator2 last2,
627 std::size_t n_permutations = 10000,
628 Engine& engine = get_random_engine())
629{
630 auto n1 = statcpp::count(first1, last1);
631 auto n2 = statcpp::count(first2, last2);
632
633 if (n1 != n2) {
634 throw std::invalid_argument("statcpp::permutation_test_correlation: samples must have equal length");
635 }
636 if (n1 < 3) {
637 throw std::invalid_argument("statcpp::permutation_test_correlation: need at least 3 pairs");
638 }
639
640 // Copy data
641 std::vector<double> x, y;
642 x.reserve(n1);
643 y.reserve(n1);
644
645 for (auto it = first1; it != last1; ++it) {
646 x.push_back(static_cast<double>(*it));
647 }
648 for (auto it = first2; it != last2; ++it) {
649 y.push_back(static_cast<double>(*it));
650 }
651
652 // Helper to compute correlation
653 auto compute_corr = [n1](const std::vector<double>& a, const std::vector<double>& b) {
654 double mean_a = std::accumulate(a.begin(), a.end(), 0.0) / n1;
655 double mean_b = std::accumulate(b.begin(), b.end(), 0.0) / n1;
656
657 double cov = 0.0, var_a = 0.0, var_b = 0.0;
658 for (std::size_t i = 0; i < n1; ++i) {
659 double da = a[i] - mean_a;
660 double db = b[i] - mean_b;
661 cov += da * db;
662 var_a += da * da;
663 var_b += db * db;
664 }
665
666 return cov / std::sqrt(var_a * var_b);
667 };
668
669 // Observed correlation
670 double observed = compute_corr(x, y);
671
672 // Permutation distribution
673 std::vector<double> perm_stats(n_permutations);
674 std::vector<double> y_perm = y;
675
676 for (std::size_t p = 0; p < n_permutations; ++p) {
677 std::shuffle(y_perm.begin(), y_perm.end(), engine);
678 perm_stats[p] = compute_corr(x, y_perm);
679 }
680
681 // Two-sided p-value (inclusive method)
682 // Include the observed statistic as part of the null distribution
683 double abs_observed = std::abs(observed);
684 std::size_t count_extreme = 1; // Count the observed statistic itself
685 for (double stat : perm_stats) {
686 if (std::abs(stat) >= abs_observed) {
687 ++count_extreme;
688 }
689 }
690
691 double p_value = static_cast<double>(count_extreme) / static_cast<double>(n_permutations + 1);
692
693 return {observed, p_value, n_permutations, std::move(perm_stats)};
694}
695
696} // namespace statcpp
Basic statistical computation functions.
Continuous distribution functions.
Dispersion and variance calculation functions.
permutation_result permutation_test_paired(Iterator1 first1, Iterator1 last1, Iterator2 first2, Iterator2 last2, std::size_t n_permutations=10000, Engine &engine=get_random_engine())
Perform paired permutation test.
double sample_stddev(Iterator first, Iterator last)
Sample standard deviation.
auto sum(Iterator first, Iterator last)
Sum.
double norm_cdf(double x)
Standard normal CDF.
bootstrap_result bootstrap(Iterator first, Iterator last, Statistic stat_func, std::size_t n_bootstrap=1000, double confidence=0.95, Engine &engine=get_random_engine())
Perform general bootstrap estimation.
permutation_result permutation_test_correlation(Iterator1 first1, Iterator1 last1, Iterator2 first2, Iterator2 last2, std::size_t n_permutations=10000, Engine &engine=get_random_engine())
Perform permutation test for correlation.
double norm_quantile(double p)
Standard normal quantile function.
bootstrap_result bootstrap_stddev(Iterator first, Iterator last, std::size_t n_bootstrap=1000, double confidence=0.95, Engine &engine=get_random_engine())
Perform bootstrap estimation of the standard deviation.
bootstrap_result bootstrap_median(Iterator first, Iterator last, std::size_t n_bootstrap=1000, double confidence=0.95, Engine &engine=get_random_engine())
Perform bootstrap estimation of the median.
bootstrap_result bootstrap_mean(Iterator first, Iterator last, std::size_t n_bootstrap=1000, double confidence=0.95, Engine &engine=get_random_engine())
Perform bootstrap estimation of the mean.
bootstrap_result bootstrap_bca(Iterator first, Iterator last, Statistic stat_func, std::size_t n_bootstrap=1000, double confidence=0.95, Engine &engine=get_random_engine())
Compute BCa (bias-corrected and accelerated) bootstrap confidence interval.
double mean(Iterator first, Iterator last)
Arithmetic mean.
default_random_engine & get_random_engine()
Singleton accessor for global random engine.
permutation_result permutation_test_two_sample(Iterator1 first1, Iterator1 last1, Iterator2 first2, Iterator2 last2, std::size_t n_permutations=10000, Engine &engine=get_random_engine())
Perform two-sample permutation test (test of difference in means)
double median(Iterator first, Iterator last)
Median (accepts a sorted range)
std::vector< typename std::iterator_traits< Iterator >::value_type > bootstrap_sample(Iterator first, Iterator last, Engine &engine)
Generate a single bootstrap sample.
std::size_t count(Iterator first, Iterator last)
Data count.
Order statistics implementation.
Random engine wrapper and utilities.
Structure to store bootstrap estimation results.
double ci_lower
Lower bound of confidence interval.
double ci_upper
Upper bound of confidence interval.
double estimate
Estimated statistic computed from original data.
std::vector< double > replicates
All bootstrap replicate statistics.
double standard_error
Bootstrap standard error.
double bias
Bias (replicate mean - estimate)
Structure to store permutation test results.
std::vector< double > permutation_distribution
Distribution of permutation statistics.
double p_value
Two-sided p-value.
double observed_statistic
Observed test statistic.
std::size_t n_permutations
Number of permutations performed.