50 double df2 = std::numeric_limits<double>::quiet_NaN();
71template <
typename Iterator>
76 throw std::invalid_argument(
"statcpp::z_test: sigma must be positive");
81 throw std::invalid_argument(
"statcpp::z_test: empty range");
85 double se = sigma / std::sqrt(
static_cast<double>(n));
86 double z = (mean_val - mu0) / se;
98 p_value = 2.0 * (1.0 -
norm_cdf(std::abs(z)));
102 return {z, p_value, std::numeric_limits<double>::infinity(), alt};
124 if (p0 <= 0.0 || p0 >= 1.0) {
125 throw std::invalid_argument(
"statcpp::z_test_proportion: p0 must be in (0, 1)");
128 throw std::invalid_argument(
"statcpp::z_test_proportion: trials must be positive");
130 if (successes > trials) {
131 throw std::invalid_argument(
"statcpp::z_test_proportion: successes cannot exceed trials");
134 double n =
static_cast<double>(trials);
135 double p_hat =
static_cast<double>(successes) / n;
136 double se = std::sqrt(p0 * (1.0 - p0) / n);
137 double z = (p_hat - p0) / se;
149 p_value = 2.0 * (1.0 -
norm_cdf(std::abs(z)));
153 return {z, p_value, std::numeric_limits<double>::infinity(), alt};
170 std::size_t successes2, std::size_t trials2,
173 if (trials1 == 0 || trials2 == 0) {
174 throw std::invalid_argument(
"statcpp::z_test_proportion_two_sample: trials must be positive");
176 if (successes1 > trials1 || successes2 > trials2) {
177 throw std::invalid_argument(
"statcpp::z_test_proportion_two_sample: successes cannot exceed trials");
180 double n1 =
static_cast<double>(trials1);
181 double n2 =
static_cast<double>(trials2);
182 double p1 =
static_cast<double>(successes1) / n1;
183 double p2 =
static_cast<double>(successes2) / n2;
186 double p_pooled =
static_cast<double>(successes1 + successes2) / (n1 + n2);
187 double se = std::sqrt(p_pooled * (1.0 - p_pooled) * (1.0 / n1 + 1.0 / n2));
191 return {0.0, 1.0,
static_cast<double>(trials1 + trials2), alt};
194 double z = (p1 - p2) / se;
206 p_value = 2.0 * (1.0 -
norm_cdf(std::abs(z)));
210 return {z, p_value, std::numeric_limits<double>::infinity(), alt};
230template <
typename Iterator>
236 throw std::invalid_argument(
"statcpp::t_test: need at least 2 elements");
243 throw std::invalid_argument(
"statcpp::t_test: zero variance");
246 double se = s / std::sqrt(
static_cast<double>(n));
247 double t = (mean_val - mu0) / se;
248 double df =
static_cast<double>(n - 1);
253 p_value =
t_cdf(t, df);
256 p_value = 1.0 -
t_cdf(t, df);
260 p_value = 2.0 * (1.0 -
t_cdf(std::abs(t), df));
264 return {t, p_value, df, alt};
283template <
typename Iterator1,
typename Iterator2>
285 Iterator2 first2, Iterator2 last2,
291 if (n1 < 2 || n2 < 2) {
292 throw std::invalid_argument(
"statcpp::t_test_two_sample: need at least 2 elements in each sample");
301 double df =
static_cast<double>(n1 + n2 - 2);
302 double sp2 = ((n1 - 1) * var1 + (n2 - 1) * var2) / df;
303 double se = std::sqrt(sp2 * (1.0 / n1 + 1.0 / n2));
306 throw std::invalid_argument(
"statcpp::t_test_two_sample: zero variance");
309 double t = (mean1 - mean2) / se;
314 p_value =
t_cdf(t, df);
317 p_value = 1.0 -
t_cdf(t, df);
321 p_value = 2.0 * (1.0 -
t_cdf(std::abs(t), df));
325 return {t, p_value, df, alt};
344template <
typename Iterator1,
typename Iterator2>
346 Iterator2 first2, Iterator2 last2,
352 if (n1 < 2 || n2 < 2) {
353 throw std::invalid_argument(
"statcpp::t_test_welch: need at least 2 elements in each sample");
361 double se1 = var1 / n1;
362 double se2 = var2 / n2;
363 double se = std::sqrt(se1 + se2);
366 throw std::invalid_argument(
"statcpp::t_test_welch: zero variance");
370 double num = (se1 + se2) * (se1 + se2);
371 double denom = (se1 * se1) / (n1 - 1) + (se2 * se2) / (n2 - 1);
375 throw std::invalid_argument(
"statcpp::t_test_welch: cannot compute degrees of freedom with zero variances");
378 double df = num / denom;
380 double t = (mean1 - mean2) / se;
385 p_value =
t_cdf(t, df);
388 p_value = 1.0 -
t_cdf(t, df);
392 p_value = 2.0 * (1.0 -
t_cdf(std::abs(t), df));
396 return {t, p_value, df, alt};
414template <
typename Iterator1,
typename Iterator2>
416 Iterator2 first2, Iterator2 last2,
423 throw std::invalid_argument(
"statcpp::t_test_paired: samples must have equal length");
426 throw std::invalid_argument(
"statcpp::t_test_paired: need at least 2 pairs");
430 std::vector<double> diffs;
435 while (it1 != last1) {
436 diffs.push_back(
static_cast<double>(*it1) -
static_cast<double>(*it2));
442 return t_test(diffs.begin(), diffs.end(), 0.0, alt);
463template <
typename Iterator1,
typename Iterator2>
465 Iterator2 expected_first, Iterator2 expected_last)
470 if (n_obs != n_exp) {
471 throw std::invalid_argument(
"statcpp::chisq_test_gof: observed and expected must have same length");
474 throw std::invalid_argument(
"statcpp::chisq_test_gof: need at least 2 categories");
478 auto it_obs = observed_first;
479 auto it_exp = expected_first;
481 while (it_obs != observed_last) {
482 double o =
static_cast<double>(*it_obs);
483 double e =
static_cast<double>(*it_exp);
486 throw std::invalid_argument(
"statcpp::chisq_test_gof: expected values must be positive");
489 chi2 += (o - e) * (o - e) / e;
495 double df =
static_cast<double>(n_obs - 1);
496 double p_value = 1.0 -
chisq_cdf(chi2, df);
512template <
typename Iterator>
517 throw std::invalid_argument(
"statcpp::chisq_test_gof_uniform: need at least 2 categories");
521 for (
auto it = observed_first; it != observed_last; ++it) {
522 total +=
static_cast<double>(*it);
526 throw std::invalid_argument(
"statcpp::chisq_test_gof_uniform: total of observed frequencies is zero");
529 double expected = total /
static_cast<double>(n);
532 for (
auto it = observed_first; it != observed_last; ++it) {
533 double o =
static_cast<double>(*it);
534 chi2 += (o - expected) * (o - expected) / expected;
537 double df =
static_cast<double>(n - 1);
538 double p_value = 1.0 -
chisq_cdf(chi2, df);
560 throw std::invalid_argument(
"statcpp::chisq_test_independence: need at least 2 rows");
565 throw std::invalid_argument(
"statcpp::chisq_test_independence: need at least 2 columns");
570 if (row.size() != cols) {
571 throw std::invalid_argument(
"statcpp::chisq_test_independence: inconsistent column count");
576 std::vector<double> row_totals(rows, 0.0);
577 std::vector<double> col_totals(cols, 0.0);
578 double grand_total = 0.0;
580 for (std::size_t i = 0; i < rows; ++i) {
581 for (std::size_t j = 0; j < cols; ++j) {
584 throw std::invalid_argument(
"statcpp::chisq_test_independence: negative cell value");
586 row_totals[i] += val;
587 col_totals[j] += val;
592 if (grand_total == 0.0) {
593 throw std::invalid_argument(
"statcpp::chisq_test_independence: empty table");
598 for (std::size_t i = 0; i < rows; ++i) {
599 for (std::size_t j = 0; j < cols; ++j) {
600 double expected = row_totals[i] * col_totals[j] / grand_total;
601 if (expected > 0.0) {
603 chi2 += (observed - expected) * (observed - expected) / expected;
608 double df =
static_cast<double>((rows - 1) * (cols - 1));
609 double p_value = 1.0 -
chisq_cdf(chi2, df);
633template <
typename Iterator1,
typename Iterator2>
635 Iterator2 first2, Iterator2 last2,
641 if (n1 < 2 || n2 < 2) {
642 throw std::invalid_argument(
"statcpp::f_test: need at least 2 elements in each sample");
649 throw std::invalid_argument(
"statcpp::f_test: second sample has zero variance");
652 double f = var1 / var2;
653 double df1 =
static_cast<double>(n1 - 1);
654 double df2 =
static_cast<double>(n2 - 1);
659 p_value =
f_cdf(f, df1, df2);
662 p_value = 1.0 -
f_cdf(f, df1, df2);
667 double p1 =
f_cdf(f, df1, df2);
668 double p2 = 1.0 - p1;
669 p_value = 2.0 * std::min(p1, p2);
674 return {f, p_value, df1, alt, df2};
692 std::size_t n = p_values.size();
693 std::vector<double> adjusted(n);
695 for (std::size_t i = 0; i < n; ++i) {
696 adjusted[i] = std::min(1.0, p_values[i] *
static_cast<double>(n));
713 std::size_t n = p_values.size();
714 if (n == 0)
return {};
717 std::vector<std::pair<std::size_t, double>> indexed(n);
718 for (std::size_t i = 0; i < n; ++i) {
719 indexed[i] = {i, p_values[i]};
722 std::sort(indexed.begin(), indexed.end(),
723 [](
const auto& a,
const auto& b) { return a.second < b.second; });
725 std::vector<double> adjusted(n);
728 double prev_adj = 1.0;
729 for (std::size_t i = n; i > 0; --i) {
730 std::size_t idx = indexed[i - 1].first;
731 double p = indexed[i - 1].second;
732 double adj = p *
static_cast<double>(n) /
static_cast<double>(i);
733 adj = std::min(adj, prev_adj);
734 adj = std::min(adj, 1.0);
753 std::size_t n = p_values.size();
754 if (n == 0)
return {};
757 std::vector<std::pair<std::size_t, double>> indexed(n);
758 for (std::size_t i = 0; i < n; ++i) {
759 indexed[i] = {i, p_values[i]};
762 std::sort(indexed.begin(), indexed.end(),
763 [](
const auto& a,
const auto& b) { return a.second < b.second; });
765 std::vector<double> adjusted(n);
767 double max_adj = 0.0;
768 for (std::size_t i = 0; i < n; ++i) {
769 std::size_t idx = indexed[i].first;
770 double p = indexed[i].second;
771 double adj = p *
static_cast<double>(n - i);
772 adj = std::max(adj, max_adj);
773 adj = std::min(adj, 1.0);
Basic statistical computation functions.
Continuous distribution functions.
Dispersion and variance calculation 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)
contingency_table_result contingency_table(const std::vector< std::size_t > &row_data, const std::vector< std::size_t > &col_data)
Create a contingency table.
double sample_stddev(Iterator first, Iterator last)
Sample standard deviation.
double sample_variance(Iterator first, Iterator last)
Sample variance (unbiased variance)
test_result t_test(Iterator first, Iterator last, double mu0, alternative_hypothesis alt=alternative_hypothesis::two_sided)
One-sample t-test.
double chisq_cdf(double x, double df)
Chi-square distribution cumulative distribution function (CDF)
double t_cdf(double x, double df)
t-distribution cumulative distribution function (CDF)
test_result chisq_test_independence(const std::vector< std::vector< double > > &contingency_table)
Chi-square test for independence.
double norm_cdf(double x)
Standard normal CDF.
std::vector< double > holm_correction(const std::vector< double > &p_values)
Holm correction (step-down Bonferroni method)
double mean(Iterator first, Iterator last)
Arithmetic mean.
test_result z_test(Iterator first, Iterator last, double mu0, double sigma, alternative_hypothesis alt=alternative_hypothesis::two_sided)
One-sample z-test (known variance)
std::vector< double > benjamini_hochberg_correction(const std::vector< double > &p_values)
Benjamini-Hochberg correction (FDR control)
test_result t_test_welch(Iterator1 first1, Iterator1 last1, Iterator2 first2, Iterator2 last2, alternative_hypothesis alt=alternative_hypothesis::two_sided)
Two-sample t-test (Welch's method)
test_result chisq_test_gof_uniform(Iterator observed_first, Iterator observed_last)
Chi-square goodness of fit test (uniform expected frequencies)
test_result t_test_paired(Iterator1 first1, Iterator1 last1, Iterator2 first2, Iterator2 last2, alternative_hypothesis alt=alternative_hypothesis::two_sided)
Paired t-test.
test_result f_test(Iterator1 first1, Iterator1 last1, Iterator2 first2, Iterator2 last2, alternative_hypothesis alt=alternative_hypothesis::two_sided)
F-test (variance comparison)
test_result chisq_test_gof(Iterator1 observed_first, Iterator1 observed_last, Iterator2 expected_first, Iterator2 expected_last)
Chi-square goodness of fit test.
std::size_t count(Iterator first, Iterator last)
Data count.
test_result t_test_two_sample(Iterator1 first1, Iterator1 last1, Iterator2 first2, Iterator2 last2, alternative_hypothesis alt=alternative_hypothesis::two_sided)
Two-sample t-test (independent samples, pooled variance)
test_result z_test_proportion_two_sample(std::size_t successes1, std::size_t trials1, std::size_t successes2, std::size_t trials2, alternative_hypothesis alt=alternative_hypothesis::two_sided)
Two-sample proportion z-test.
test_result z_test_proportion(std::size_t successes, std::size_t trials, double p0, alternative_hypothesis alt=alternative_hypothesis::two_sided)
One-sample proportion z-test.
double f_cdf(double x, double df1, double df2)
F-distribution cumulative distribution function (CDF)
std::vector< double > bonferroni_correction(const std::vector< double > &p_values)
Bonferroni correction.
Structure to store statistical test results.
double df
Degrees of freedom.
double df2
Second degrees of freedom (used by F-test)
alternative_hypothesis alternative
Type of alternative hypothesis.
double statistic
Test statistic.