55template <
typename Iterator>
59 if (n == 0)
return {};
62 std::vector<std::pair<double, std::size_t>> indexed(n);
64 for (
auto it = first; it != last; ++it, ++i) {
65 indexed[i] = {
static_cast<double>(*it), i};
69 std::sort(indexed.begin(), indexed.end(),
70 [](
const auto& a,
const auto& b) { return a.first < b.first; });
73 std::vector<double> ranks(n);
78 while (k < n && indexed[k].first == indexed[j].first) {
82 double avg_rank = (
static_cast<double>(j + 1) +
static_cast<double>(k)) / 2.0;
83 for (std::size_t m = j; m < k; ++m) {
84 ranks[indexed[m].second] = avg_rank;
103 std::vector<std::size_t> tie_groups;
104 std::size_t n = sorted_values.size();
108 while (j < n && sorted_values[j] == sorted_values[i]) {
111 std::size_t t = j - i;
113 tie_groups.push_back(t);
143template <
typename Iterator>
148 throw std::invalid_argument(
"statcpp::shapiro_wilk_test: need at least 3 elements");
151 throw std::invalid_argument(
"statcpp::shapiro_wilk_test: n > 5000 not supported");
155 std::vector<double> sorted_data;
156 sorted_data.reserve(n);
157 for (
auto it = first; it != last; ++it) {
158 sorted_data.push_back(
static_cast<double>(*it));
160 std::sort(sorted_data.begin(), sorted_data.end());
162 double mean_val =
statcpp::mean(sorted_data.begin(), sorted_data.end());
166 for (
double x : sorted_data) {
167 double d = x - mean_val;
172 throw std::invalid_argument(
"statcpp::shapiro_wilk_test: zero variance");
177 std::vector<double> m_vals(n);
178 for (std::size_t i = 0; i < n; ++i) {
179 double p = (
static_cast<double>(i + 1) - 0.375) / (
static_cast<double>(n) + 0.25);
185 for (std::size_t i = 0; i < n; ++i) {
186 sum_m2 += m_vals[i] * m_vals[i];
190 std::vector<double> a(n, 0.0);
194 double sqrt_sum_m2 = std::sqrt(sum_m2);
195 for (std::size_t i = 0; i < n; ++i) {
196 a[i] = m_vals[i] / sqrt_sum_m2;
200 double sqrt_n = std::sqrt(
static_cast<double>(n));
201 double u = 1.0 / sqrt_n;
204 double a_n = -2.706056 * std::pow(u, 5) + 4.434685 * std::pow(u, 4)
205 - 2.071190 * std::pow(u, 3) - 0.147981 * std::pow(u, 2)
206 + 0.221157 * u + m_vals[n - 1] / std::sqrt(sum_m2);
209 double a_n1 = -3.582633 * std::pow(u, 5) + 5.682633 * std::pow(u, 4)
210 - 1.752461 * std::pow(u, 3) - 0.293762 * std::pow(u, 2)
211 + 0.042981 * u + m_vals[n - 2] / std::sqrt(sum_m2);
222 double phi = (sum_m2 - 2.0 * m_vals[n - 1] * m_vals[n - 1]
223 - 2.0 * m_vals[n - 2] * m_vals[n - 2])
224 / (1.0 - 2.0 * a_n * a_n - 2.0 * a_n1 * a_n1);
227 double sqrt_phi = std::sqrt(phi);
228 for (std::size_t i = 2; i < n - 2; ++i) {
229 a[i] = m_vals[i] / sqrt_phi;
236 for (std::size_t i = 0; i < n; ++i) {
237 b += a[i] * sorted_data[i];
240 double w = (b * b) / ss;
243 w = std::max(0.0, std::min(1.0, w));
246 double ln_n = std::log(
static_cast<double>(n));
247 double mu, sigma, gamma;
250 gamma = 0.459 *
static_cast<double>(n) - 2.273;
251 mu = -0.0006714 * std::pow(
static_cast<double>(n), 3)
252 + 0.025054 * std::pow(
static_cast<double>(n), 2)
253 - 0.39978 *
static_cast<double>(n) + 0.5440;
254 sigma = std::exp(-0.0020322 * std::pow(
static_cast<double>(n), 3)
255 + 0.062767 * std::pow(
static_cast<double>(n), 2)
256 - 0.77857 *
static_cast<double>(n) + 1.3822);
259 mu = 0.0038915 * std::pow(ln_n, 3) - 0.083751 * std::pow(ln_n, 2)
260 - 0.31082 * ln_n - 1.5861;
261 sigma = std::exp(0.0030302 * std::pow(ln_n, 2) - 0.082676 * ln_n - 0.4803);
265 if (gamma != 0.0 && w < 1.0) {
266 double arg = gamma - std::log(1.0 - w);
268 z = (-std::log(arg) - mu) / sigma;
272 }
else if (w < 1.0) {
273 z = (std::log(1.0 - w) - mu) / sigma;
279 p_value = std::max(0.0, std::min(1.0, p_value));
311template <
typename Iterator>
316 throw std::invalid_argument(
"statcpp::lilliefors_test: need at least 2 elements");
324 throw std::invalid_argument(
"statcpp::lilliefors_test: zero variance");
327 std::vector<double> standardized;
328 standardized.reserve(n);
329 for (
auto it = first; it != last; ++it) {
330 standardized.push_back((
static_cast<double>(*it) - mean_val) / sd);
332 std::sort(standardized.begin(), standardized.end());
336 double d_minus = 0.0;
338 for (std::size_t i = 0; i < n; ++i) {
339 double f_x =
norm_cdf(standardized[i]);
340 double f_n_upper =
static_cast<double>(i + 1) /
static_cast<double>(n);
341 double f_n_lower =
static_cast<double>(i) /
static_cast<double>(n);
343 d_plus = std::max(d_plus, f_n_upper - f_x);
344 d_minus = std::max(d_minus, f_x - f_n_lower);
347 double d = std::max(d_plus, d_minus);
353 double sqrt_n = std::sqrt(
static_cast<double>(n));
354 double d_adj = (d - 0.01 + 0.85 / sqrt_n) * (sqrt_n + 0.05 + 0.82 / sqrt_n);
357 double p_value = 2.0 * std::exp(-2.0 * d_adj * d_adj);
358 p_value = std::max(0.0, std::min(1.0, p_value));
370template <
typename Iterator>
371[[deprecated(
"Use lilliefors_test() instead. ks_test_normal() will be removed in a future version.")]]
399 std::size_t k = groups.size();
401 throw std::invalid_argument(
"statcpp::levene_test: need at least 2 groups");
405 std::vector<std::vector<double>> z_values(k);
406 std::size_t total_n = 0;
408 for (std::size_t i = 0; i < k; ++i) {
409 if (groups[i].size() < 2) {
410 throw std::invalid_argument(
"statcpp::levene_test: each group needs at least 2 elements");
413 std::vector<double> sorted = groups[i];
414 std::sort(sorted.begin(), sorted.end());
417 for (
double x : groups[i]) {
418 z_values[i].push_back(std::abs(x - med));
420 total_n += groups[i].size();
424 std::vector<double> z_means(k);
425 double z_grand_mean = 0.0;
427 for (std::size_t i = 0; i < k; ++i) {
428 z_means[i] =
statcpp::mean(z_values[i].begin(), z_values[i].end());
429 z_grand_mean += z_means[i] *
static_cast<double>(z_values[i].size());
431 z_grand_mean /=
static_cast<double>(total_n);
434 double ss_between = 0.0;
435 double ss_within = 0.0;
437 for (std::size_t i = 0; i < k; ++i) {
438 double ni =
static_cast<double>(z_values[i].size());
439 ss_between += ni * (z_means[i] - z_grand_mean) * (z_means[i] - z_grand_mean);
441 for (
double z : z_values[i]) {
442 ss_within += (z - z_means[i]) * (z - z_means[i]);
446 double df1 =
static_cast<double>(k - 1);
447 double df2 =
static_cast<double>(total_n - k);
451 if (ss_within == 0.0) {
455 double f = (ss_between / df1) / (ss_within / df2);
456 double p_value = 1.0 -
f_cdf(f, df1, df2);
484 std::size_t k = groups.size();
486 throw std::invalid_argument(
"statcpp::bartlett_test: need at least 2 groups");
489 std::vector<double> vars(k);
490 std::vector<std::size_t> ns(k);
491 std::size_t total_n = 0;
492 double pooled_var_num = 0.0;
494 for (std::size_t i = 0; i < k; ++i) {
495 if (groups[i].size() < 2) {
496 throw std::invalid_argument(
"statcpp::bartlett_test: each group needs at least 2 elements");
498 ns[i] = groups[i].size();
502 if (vars[i] <= 0.0) {
503 throw std::invalid_argument(
"statcpp::bartlett_test: zero or negative variance in group");
506 pooled_var_num += (ns[i] - 1) * vars[i];
509 double pooled_var = pooled_var_num /
static_cast<double>(total_n - k);
512 double sum_log = 0.0;
513 double sum_inv = 0.0;
515 for (std::size_t i = 0; i < k; ++i) {
516 double df_i =
static_cast<double>(ns[i] - 1);
517 sum_log += df_i * std::log(vars[i]);
518 sum_inv += 1.0 / df_i;
521 double df_total =
static_cast<double>(total_n - k);
522 double chi2 = df_total * std::log(pooled_var) - sum_log;
525 double c = 1.0 + (sum_inv - 1.0 / df_total) / (3.0 * (k - 1));
528 double df =
static_cast<double>(k - 1);
529 double p_value = 1.0 -
chisq_cdf(chi2, df);
558template <
typename Iterator>
564 throw std::invalid_argument(
"statcpp::wilcoxon_signed_rank_test: need at least 2 elements");
568 std::vector<double> diffs;
569 for (
auto it = first; it != last; ++it) {
570 double d =
static_cast<double>(*it) - mu0;
576 std::size_t n_nonzero = diffs.size();
578 throw std::invalid_argument(
"statcpp::wilcoxon_signed_rank_test: need at least 2 non-zero differences");
582 std::vector<double> abs_diffs(n_nonzero);
583 for (std::size_t i = 0; i < n_nonzero; ++i) {
584 abs_diffs[i] = std::abs(diffs[i]);
592 for (std::size_t i = 0; i < n_nonzero; ++i) {
593 if (diffs[i] > 0.0) {
599 double nn =
static_cast<double>(n_nonzero);
600 double mean_w = nn * (nn + 1.0) / 4.0;
601 double var_w = nn * (nn + 1.0) * (2.0 * nn + 1.0) / 24.0;
605 std::vector<double> sorted_abs(abs_diffs.begin(), abs_diffs.end());
606 std::sort(sorted_abs.begin(), sorted_abs.end());
609 double tie_correction = 0.0;
610 for (
auto t : tie_groups) {
611 double td =
static_cast<double>(t);
612 tie_correction += td * td * td - td;
614 var_w -= tie_correction / 48.0;
616 double se = std::sqrt(std::max(0.0, var_w));
619 return {w, 1.0,
static_cast<double>(n), alt};
627 z = (w - mean_w + 0.5) / se;
631 z = (w - mean_w - 0.5) / se;
636 z = (w - mean_w) / se;
637 if (z < 0) z = (w - mean_w + 0.5) / se;
638 else z = (w - mean_w - 0.5) / se;
643 return {w, p_value,
static_cast<double>(n_nonzero), alt};
671template <
typename Iterator1,
typename Iterator2>
673 Iterator2 first2, Iterator2 last2,
680 if (n1 < 2 || n2 < 2) {
681 throw std::invalid_argument(
"statcpp::mann_whitney_u_test: need at least 2 elements in each sample");
685 std::vector<std::pair<double, int>> combined;
686 combined.reserve(n1 + n2);
688 for (
auto it = first1; it != last1; ++it) {
689 combined.push_back({
static_cast<double>(*it), 1});
691 for (
auto it = first2; it != last2; ++it) {
692 combined.push_back({
static_cast<double>(*it), 2});
696 std::sort(combined.begin(), combined.end(),
697 [](
const auto& a,
const auto& b) { return a.first < b.first; });
700 std::size_t total_n = n1 + n2;
701 std::vector<double> ranks(total_n);
703 while (i < total_n) {
705 while (j < total_n && combined[j].first == combined[i].first) {
708 double avg_rank = (
static_cast<double>(i + 1) +
static_cast<double>(j)) / 2.0;
709 for (std::size_t k = i; k < j; ++k) {
717 for (std::size_t k = 0; k < total_n; ++k) {
718 if (combined[k].second == 1) {
724 double u1 = r1 - n1 * (n1 + 1.0) / 2.0;
725 (void)(
static_cast<double>(n1 * n2) - u1);
728 double N =
static_cast<double>(total_n);
729 double mean_u =
static_cast<double>(n1 * n2) / 2.0;
730 double var_u =
static_cast<double>(n1 * n2) / 12.0 * (N + 1.0);
734 std::vector<double> sorted_vals;
735 sorted_vals.reserve(total_n);
736 for (
const auto& p : combined) {
737 sorted_vals.push_back(p.first);
741 double tie_sum = 0.0;
742 for (
auto t : tie_groups) {
743 double td =
static_cast<double>(t);
744 tie_sum += td * td * td - td;
748 if (N > 1.0 && tie_sum > 0.0) {
749 var_u =
static_cast<double>(n1 * n2) / (12.0 * N * (N - 1.0))
750 * (N * N * N - N - tie_sum);
754 double se = std::sqrt(std::max(0.0, var_u));
757 return {u1, 1.0,
static_cast<double>(n1 + n2), alt};
761 double diff = u1 - mean_u;
766 double z =
diff / se;
782 return {u1, p_value,
static_cast<double>(n1 + n2), alt};
807 std::size_t k = groups.size();
809 throw std::invalid_argument(
"statcpp::kruskal_wallis_test: need at least 2 groups");
813 std::vector<std::pair<double, std::size_t>> combined;
814 std::vector<std::size_t> ns(k);
815 std::size_t total_n = 0;
817 for (std::size_t i = 0; i < k; ++i) {
818 if (groups[i].empty()) {
819 throw std::invalid_argument(
"statcpp::kruskal_wallis_test: empty group");
821 ns[i] = groups[i].size();
823 for (
double x : groups[i]) {
824 combined.push_back({x, i});
829 std::sort(combined.begin(), combined.end(),
830 [](
const auto& a,
const auto& b) { return a.first < b.first; });
833 std::vector<double> ranks(total_n);
835 while (i < total_n) {
837 while (j < total_n && combined[j].first == combined[i].first) {
840 double avg_rank = (
static_cast<double>(i + 1) +
static_cast<double>(j)) / 2.0;
841 for (std::size_t m = i; m < j; ++m) {
848 std::vector<double> rank_sums(k, 0.0);
849 for (std::size_t m = 0; m < total_n; ++m) {
850 rank_sums[combined[m].second] += ranks[m];
854 double n_d =
static_cast<double>(total_n);
855 double sum_term = 0.0;
856 for (std::size_t g = 0; g < k; ++g) {
857 double r_bar = rank_sums[g] /
static_cast<double>(ns[g]);
858 sum_term +=
static_cast<double>(ns[g]) * r_bar * r_bar;
861 double h = (12.0 / (n_d * (n_d + 1.0))) * sum_term - 3.0 * (n_d + 1.0);
865 std::vector<double> sorted_vals;
866 sorted_vals.reserve(total_n);
867 for (
const auto& p : combined) {
868 sorted_vals.push_back(p.first);
871 double tie_sum = 0.0;
872 for (
auto t : tie_groups) {
873 double td =
static_cast<double>(t);
874 tie_sum += td * td * td - td;
876 double denom = n_d * n_d * n_d - n_d;
877 if (tie_sum > 0.0 && denom > 0.0) {
878 double correction = 1.0 - tie_sum / denom;
879 if (correction > 0.0) {
889 double df =
static_cast<double>(k - 1);
926 std::uint64_t c, std::uint64_t d,
935 std::uint64_t n = a + b + c + d;
936 std::uint64_t row1 = a + b;
938 std::uint64_t col1 = a + c;
939 std::uint64_t col2 = b + d;
946 double p_obs = std::exp(log_p_obs);
948 double p_value = 0.0;
951 std::uint64_t a_min = (row1 > col2) ? row1 - col2 : 0;
952 std::uint64_t a_max = std::min(row1, col1);
956 for (std::uint64_t x = a_min; x <= a; ++x) {
958 p_value += std::exp(log_p);
962 for (std::uint64_t x = a; x <= a_max; ++x) {
964 p_value += std::exp(log_p);
970 for (std::uint64_t x = a_min; x <= a_max; ++x) {
972 double p = std::exp(log_p);
973 if (p <= p_obs + 1e-10) {
980 p_value = std::min(1.0, p_value);
983 double odds_ratio = (b == 0 || c == 0) ? std::numeric_limits<double>::infinity()
984 : (
static_cast<double>(a) * d) / (
static_cast<double>(b) * c);
986 return {
odds_ratio, p_value, std::numeric_limits<double>::quiet_NaN(), alt};
Basic statistical computation functions.
Continuous distribution functions.
Discrete probability distribution functions.
alternative_hypothesis
Enumeration representing the type of alternative hypothesis.
@ greater
One-sided test (greater than)
@ two_sided
Two-sided test.
@ less
One-sided test (less than)
double log_binomial_coef(std::uint64_t n, std::uint64_t k)
Calculate log binomial coefficient.
std::vector< std::size_t > compute_tie_groups(const std::vector< double > &sorted_values)
Compute tie group sizes from sorted data.
test_result bartlett_test(const std::vector< std::vector< double > > &groups)
Perform Bartlett test (homogeneity of variance test)
double sample_stddev(Iterator first, Iterator last)
Sample standard deviation.
double sample_variance(Iterator first, Iterator last)
Sample variance (unbiased variance)
double chisq_cdf(double x, double df)
Chi-square distribution cumulative distribution function (CDF)
odds_ratio_result odds_ratio(const std::vector< std::vector< std::size_t > > &table)
Calculate odds ratio from a 2x2 contingency table.
double norm_cdf(double x)
Standard normal CDF.
double norm_quantile(double p)
Standard normal quantile function.
test_result levene_test(const std::vector< std::vector< double > > &groups)
Perform Levene test (homogeneity of variance test)
test_result lilliefors_test(Iterator first, Iterator last)
Perform Lilliefors test for normality.
double mean(Iterator first, Iterator last)
Arithmetic mean.
std::vector< double > compute_ranks_with_ties(Iterator first, Iterator last)
Compute ranks with tie handling.
std::vector< double > diff(Iterator first, Iterator last, std::size_t order=1)
Difference series (first-order or d-th order differencing)
test_result shapiro_wilk_test(Iterator first, Iterator last)
Perform Shapiro-Wilk test.
test_result kruskal_wallis_test(const std::vector< std::vector< double > > &groups)
Perform Kruskal-Wallis test (k-sample)
test_result mann_whitney_u_test(Iterator1 first1, Iterator1 last1, Iterator2 first2, Iterator2 last2, alternative_hypothesis alt=alternative_hypothesis::two_sided, bool correct=true)
Perform Mann-Whitney U test (two-sample)
double median(Iterator first, Iterator last)
Median (accepts a sorted range)
test_result fisher_exact_test(std::uint64_t a, std::uint64_t b, std::uint64_t c, std::uint64_t d, alternative_hypothesis alt=alternative_hypothesis::two_sided)
Perform Fisher's exact test (2x2 contingency table)
test_result ks_test_normal(Iterator first, Iterator last)
Perform Kolmogorov-Smirnov test for normality (deprecated)
std::size_t count(Iterator first, Iterator last)
Data count.
double f_cdf(double x, double df1, double df2)
F-distribution cumulative distribution function (CDF)
test_result wilcoxon_signed_rank_test(Iterator first, Iterator last, double mu0=0.0, alternative_hypothesis alt=alternative_hypothesis::two_sided)
Perform Wilcoxon signed-rank test (one-sample)
Order statistics implementation.
Parametric test functions.
Structure to store statistical test results.