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));
312template <
typename Iterator>
317 throw std::invalid_argument(
"statcpp::lilliefors_test: need at least 2 elements");
325 throw std::invalid_argument(
"statcpp::lilliefors_test: zero variance");
328 std::vector<double> standardized;
329 standardized.reserve(n);
330 for (
auto it = first; it != last; ++it) {
331 standardized.push_back((
static_cast<double>(*it) - mean_val) / sd);
333 std::sort(standardized.begin(), standardized.end());
337 double d_minus = 0.0;
339 for (std::size_t i = 0; i < n; ++i) {
340 double f_x =
norm_cdf(standardized[i]);
341 double f_n_upper =
static_cast<double>(i + 1) /
static_cast<double>(n);
342 double f_n_lower =
static_cast<double>(i) /
static_cast<double>(n);
344 d_plus = std::max(d_plus, f_n_upper - f_x);
345 d_minus = std::max(d_minus, f_x - f_n_lower);
348 double d = std::max(d_plus, d_minus);
359 const double n_d =
static_cast<double>(n);
360 const double a = n_d + 2.78019;
365 const double d_vertex = 2.99587 / (2.0 * 7.01256 * std::sqrt(a));
366 const double d_eff = std::max(d, d_vertex);
368 double p_value = std::exp(-7.01256 * d_eff * d_eff * a + 2.99587 * d_eff * std::sqrt(a) -
369 0.122119 + 0.974598 / std::sqrt(n_d) + 1.67997 / n_d);
370 p_value = std::max(0.0, std::min(1.0, p_value));
382template <
typename Iterator>
383[[deprecated(
"Use lilliefors_test() instead. ks_test_normal() will be removed in a future version.")]]
411 std::size_t k = groups.size();
413 throw std::invalid_argument(
"statcpp::levene_test: need at least 2 groups");
417 std::vector<std::vector<double>> z_values(k);
418 std::size_t total_n = 0;
420 for (std::size_t i = 0; i < k; ++i) {
421 if (groups[i].size() < 2) {
422 throw std::invalid_argument(
"statcpp::levene_test: each group needs at least 2 elements");
425 std::vector<double> sorted = groups[i];
426 std::sort(sorted.begin(), sorted.end());
429 for (
double x : groups[i]) {
430 z_values[i].push_back(std::abs(x - med));
432 total_n += groups[i].size();
436 std::vector<double> z_means(k);
437 double z_grand_mean = 0.0;
439 for (std::size_t i = 0; i < k; ++i) {
440 z_means[i] =
statcpp::mean(z_values[i].begin(), z_values[i].end());
441 z_grand_mean += z_means[i] *
static_cast<double>(z_values[i].size());
443 z_grand_mean /=
static_cast<double>(total_n);
446 double ss_between = 0.0;
447 double ss_within = 0.0;
449 for (std::size_t i = 0; i < k; ++i) {
450 double ni =
static_cast<double>(z_values[i].size());
451 ss_between += ni * (z_means[i] - z_grand_mean) * (z_means[i] - z_grand_mean);
453 for (
double z : z_values[i]) {
454 ss_within += (z - z_means[i]) * (z - z_means[i]);
458 double df1 =
static_cast<double>(k - 1);
459 double df2 =
static_cast<double>(total_n - k);
463 if (ss_within == 0.0) {
467 double f = (ss_between / df1) / (ss_within / df2);
468 double p_value = 1.0 -
f_cdf(f, df1, df2);
496 std::size_t k = groups.size();
498 throw std::invalid_argument(
"statcpp::bartlett_test: need at least 2 groups");
501 std::vector<double> vars(k);
502 std::vector<std::size_t> ns(k);
503 std::size_t total_n = 0;
504 double pooled_var_num = 0.0;
506 for (std::size_t i = 0; i < k; ++i) {
507 if (groups[i].size() < 2) {
508 throw std::invalid_argument(
"statcpp::bartlett_test: each group needs at least 2 elements");
510 ns[i] = groups[i].size();
514 if (vars[i] <= 0.0) {
515 throw std::invalid_argument(
"statcpp::bartlett_test: zero or negative variance in group");
518 pooled_var_num += (ns[i] - 1) * vars[i];
521 double pooled_var = pooled_var_num /
static_cast<double>(total_n - k);
524 double sum_log = 0.0;
525 double sum_inv = 0.0;
527 for (std::size_t i = 0; i < k; ++i) {
528 double df_i =
static_cast<double>(ns[i] - 1);
529 sum_log += df_i * std::log(vars[i]);
530 sum_inv += 1.0 / df_i;
533 double df_total =
static_cast<double>(total_n - k);
534 double chi2 = df_total * std::log(pooled_var) - sum_log;
537 double c = 1.0 + (sum_inv - 1.0 / df_total) / (3.0 * (k - 1));
540 double df =
static_cast<double>(k - 1);
541 double p_value = 1.0 -
chisq_cdf(chi2, df);
570template <
typename Iterator>
576 throw std::invalid_argument(
"statcpp::wilcoxon_signed_rank_test: need at least 2 elements");
580 std::vector<double> diffs;
581 for (
auto it = first; it != last; ++it) {
582 double d =
static_cast<double>(*it) - mu0;
588 std::size_t n_nonzero = diffs.size();
590 throw std::invalid_argument(
"statcpp::wilcoxon_signed_rank_test: need at least 2 non-zero differences");
594 std::vector<double> abs_diffs(n_nonzero);
595 for (std::size_t i = 0; i < n_nonzero; ++i) {
596 abs_diffs[i] = std::abs(diffs[i]);
604 for (std::size_t i = 0; i < n_nonzero; ++i) {
605 if (diffs[i] > 0.0) {
611 double nn =
static_cast<double>(n_nonzero);
612 double mean_w = nn * (nn + 1.0) / 4.0;
613 double var_w = nn * (nn + 1.0) * (2.0 * nn + 1.0) / 24.0;
617 std::vector<double> sorted_abs(abs_diffs.begin(), abs_diffs.end());
618 std::sort(sorted_abs.begin(), sorted_abs.end());
621 double tie_correction = 0.0;
622 for (
auto t : tie_groups) {
623 double td =
static_cast<double>(t);
624 tie_correction += td * td * td - td;
626 var_w -= tie_correction / 48.0;
628 double se = std::sqrt(std::max(0.0, var_w));
631 return {w, 1.0,
static_cast<double>(n), alt};
639 z = (w - mean_w + 0.5) / se;
643 z = (w - mean_w - 0.5) / se;
648 z = (w - mean_w) / se;
649 if (z < 0) z = (w - mean_w + 0.5) / se;
650 else z = (w - mean_w - 0.5) / se;
655 return {w, p_value,
static_cast<double>(n_nonzero), alt};
683template <
typename Iterator1,
typename Iterator2>
685 Iterator2 first2, Iterator2 last2,
692 if (n1 < 2 || n2 < 2) {
693 throw std::invalid_argument(
"statcpp::mann_whitney_u_test: need at least 2 elements in each sample");
697 std::vector<std::pair<double, int>> combined;
698 combined.reserve(n1 + n2);
700 for (
auto it = first1; it != last1; ++it) {
701 combined.push_back({
static_cast<double>(*it), 1});
703 for (
auto it = first2; it != last2; ++it) {
704 combined.push_back({
static_cast<double>(*it), 2});
708 std::sort(combined.begin(), combined.end(),
709 [](
const auto& a,
const auto& b) { return a.first < b.first; });
712 std::size_t total_n = n1 + n2;
713 std::vector<double> ranks(total_n);
715 while (i < total_n) {
717 while (j < total_n && combined[j].first == combined[i].first) {
720 double avg_rank = (
static_cast<double>(i + 1) +
static_cast<double>(j)) / 2.0;
721 for (std::size_t k = i; k < j; ++k) {
729 for (std::size_t k = 0; k < total_n; ++k) {
730 if (combined[k].second == 1) {
736 double u1 = r1 - n1 * (n1 + 1.0) / 2.0;
737 (void)(
static_cast<double>(n1 * n2) - u1);
740 double N =
static_cast<double>(total_n);
741 double mean_u =
static_cast<double>(n1 * n2) / 2.0;
742 double var_u =
static_cast<double>(n1 * n2) / 12.0 * (N + 1.0);
746 std::vector<double> sorted_vals;
747 sorted_vals.reserve(total_n);
748 for (
const auto& p : combined) {
749 sorted_vals.push_back(p.first);
753 double tie_sum = 0.0;
754 for (
auto t : tie_groups) {
755 double td =
static_cast<double>(t);
756 tie_sum += td * td * td - td;
760 if (N > 1.0 && tie_sum > 0.0) {
761 var_u =
static_cast<double>(n1 * n2) / (12.0 * N * (N - 1.0))
762 * (N * N * N - N - tie_sum);
766 double se = std::sqrt(std::max(0.0, var_u));
769 return {u1, 1.0,
static_cast<double>(n1 + n2), alt};
773 double diff = u1 - mean_u;
778 double z =
diff / se;
794 return {u1, p_value,
static_cast<double>(n1 + n2), alt};
819 std::size_t k = groups.size();
821 throw std::invalid_argument(
"statcpp::kruskal_wallis_test: need at least 2 groups");
825 std::vector<std::pair<double, std::size_t>> combined;
826 std::vector<std::size_t> ns(k);
827 std::size_t total_n = 0;
829 for (std::size_t i = 0; i < k; ++i) {
830 if (groups[i].empty()) {
831 throw std::invalid_argument(
"statcpp::kruskal_wallis_test: empty group");
833 ns[i] = groups[i].size();
835 for (
double x : groups[i]) {
836 combined.push_back({x, i});
841 std::sort(combined.begin(), combined.end(),
842 [](
const auto& a,
const auto& b) { return a.first < b.first; });
845 std::vector<double> ranks(total_n);
847 while (i < total_n) {
849 while (j < total_n && combined[j].first == combined[i].first) {
852 double avg_rank = (
static_cast<double>(i + 1) +
static_cast<double>(j)) / 2.0;
853 for (std::size_t m = i; m < j; ++m) {
860 std::vector<double> rank_sums(k, 0.0);
861 for (std::size_t m = 0; m < total_n; ++m) {
862 rank_sums[combined[m].second] += ranks[m];
866 double n_d =
static_cast<double>(total_n);
867 double sum_term = 0.0;
868 for (std::size_t g = 0; g < k; ++g) {
869 double r_bar = rank_sums[g] /
static_cast<double>(ns[g]);
870 sum_term +=
static_cast<double>(ns[g]) * r_bar * r_bar;
873 double h = (12.0 / (n_d * (n_d + 1.0))) * sum_term - 3.0 * (n_d + 1.0);
877 std::vector<double> sorted_vals;
878 sorted_vals.reserve(total_n);
879 for (
const auto& p : combined) {
880 sorted_vals.push_back(p.first);
883 double tie_sum = 0.0;
884 for (
auto t : tie_groups) {
885 double td =
static_cast<double>(t);
886 tie_sum += td * td * td - td;
888 double denom = n_d * n_d * n_d - n_d;
889 if (tie_sum > 0.0 && denom > 0.0) {
890 double correction = 1.0 - tie_sum / denom;
891 if (correction > 0.0) {
901 double df =
static_cast<double>(k - 1);
938 std::uint64_t c, std::uint64_t d,
947 std::uint64_t n = a + b + c + d;
948 std::uint64_t row1 = a + b;
950 std::uint64_t col1 = a + c;
951 std::uint64_t col2 = b + d;
958 double p_obs = std::exp(log_p_obs);
960 double p_value = 0.0;
963 std::uint64_t a_min = (row1 > col2) ? row1 - col2 : 0;
964 std::uint64_t a_max = std::min(row1, col1);
968 for (std::uint64_t x = a_min; x <= a; ++x) {
970 p_value += std::exp(log_p);
974 for (std::uint64_t x = a; x <= a_max; ++x) {
976 p_value += std::exp(log_p);
982 for (std::uint64_t x = a_min; x <= a_max; ++x) {
984 double p = std::exp(log_p);
985 if (p <= p_obs + 1e-10) {
992 p_value = std::min(1.0, p_value);
995 double odds_ratio = (b == 0 || c == 0) ? std::numeric_limits<double>::infinity()
996 : (
static_cast<double>(a) * d) / (
static_cast<double>(b) * c);
998 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.
double norm_sf(double x)
Standard normal survival function.
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.