statcpp
C++17 Header-Only Statistics Library
Loading...
Searching...
No Matches
anova.hpp
Go to the documentation of this file.
1
9#pragma once
10
12
13#include <algorithm>
14#include <cmath>
15#include <cstddef>
16#include <limits>
17#include <numeric>
18#include <stdexcept>
19#include <string>
20#include <utility>
21#include <vector>
22
23namespace statcpp {
24
25// ============================================================================
26// ANOVA Result Structures
27// ============================================================================
28
35struct anova_row {
36 std::string source;
37 double ss;
38 double df;
39 double ms;
40 double f_statistic;
41 double p_value;
42};
43
52 double ss_total;
53 double df_total;
54 std::size_t n_groups;
55 std::size_t n_total;
56 double grand_mean;
57 std::vector<double> group_means;
58 std::vector<std::size_t> group_sizes;
59};
60
78
85 std::size_t group1;
86 std::size_t group2;
87 double mean_diff;
88 double se;
89 double statistic;
90 double p_value;
91 double lower;
92 double upper;
94};
95
102 std::string method;
103 std::vector<posthoc_comparison> comparisons;
104 double alpha;
105 double mse;
106 double df_error;
107};
108
109// ============================================================================
110// One-Way ANOVA
111// ============================================================================
112
125inline one_way_anova_result one_way_anova(const std::vector<std::vector<double>>& groups)
126{
127 std::size_t k = groups.size(); // Number of groups
128 if (k < 2) {
129 throw std::invalid_argument("statcpp::one_way_anova: need at least 2 groups");
130 }
131
132 // Calculate size and mean for each group
133 std::vector<std::size_t> group_sizes(k);
134 std::vector<double> group_means(k);
135 std::size_t n_total = 0;
136 double grand_sum = 0.0;
137
138 for (std::size_t i = 0; i < k; ++i) {
139 if (groups[i].empty()) {
140 throw std::invalid_argument("statcpp::one_way_anova: empty group detected");
141 }
142 group_sizes[i] = groups[i].size();
143 n_total += group_sizes[i];
144 double group_sum = std::accumulate(groups[i].begin(), groups[i].end(), 0.0);
145 group_means[i] = group_sum / static_cast<double>(group_sizes[i]);
146 grand_sum += group_sum;
147 }
148
149 if (n_total <= k) {
150 throw std::invalid_argument("statcpp::one_way_anova: need more observations than groups");
151 }
152
153 double grand_mean = grand_sum / static_cast<double>(n_total);
154
155 // Calculate sum of squares
156 double ss_between = 0.0; // Between-group sum of squares
157 double ss_within = 0.0; // Within-group sum of squares
158
159 for (std::size_t i = 0; i < k; ++i) {
160 double diff = group_means[i] - grand_mean;
161 ss_between += static_cast<double>(group_sizes[i]) * diff * diff;
162
163 for (double x : groups[i]) {
164 double diff_within = x - group_means[i];
165 ss_within += diff_within * diff_within;
166 }
167 }
168
169 double ss_total = ss_between + ss_within;
170
171 // Degrees of freedom
172 double df_between = static_cast<double>(k - 1);
173 double df_within = static_cast<double>(n_total - k);
174 double df_total = static_cast<double>(n_total - 1);
175
176 // Mean squares
177 double ms_between = ss_between / df_between;
178 double ms_within = ss_within / df_within;
179
180 // F statistic and p-value (handle degenerate case ms_within == 0)
181 double f_statistic;
182 double p_value;
183 if (ms_within == 0.0) {
184 f_statistic = (ms_between == 0.0) ? 0.0 : std::numeric_limits<double>::infinity();
185 p_value = (ms_between == 0.0) ? 1.0 : 0.0;
186 } else {
187 f_statistic = ms_between / ms_within;
188 p_value = 1.0 - f_cdf(f_statistic, df_between, df_within);
189 }
190
191 anova_row between{"Between Groups", ss_between, df_between, ms_between, f_statistic, p_value};
192 anova_row within{"Within Groups", ss_within, df_within, ms_within, 0.0, 0.0};
193
194 return {between, within, ss_total, df_total, k, n_total, grand_mean, group_means, group_sizes};
195}
196
197// ============================================================================
198// Two-Way ANOVA
199// ============================================================================
200
216 const std::vector<std::vector<std::vector<double>>>& data)
217{
218 std::size_t a = data.size(); // Number of levels for factor A
219 if (a < 2) {
220 throw std::invalid_argument("statcpp::two_way_anova: need at least 2 levels for factor A");
221 }
222
223 std::size_t b = data[0].size(); // Number of levels for factor B
224 if (b < 2) {
225 throw std::invalid_argument("statcpp::two_way_anova: need at least 2 levels for factor B");
226 }
227
228 // Check if all levels have the same number of replications
229 std::size_t n_rep = data[0][0].size(); // Number of replications
230 if (n_rep < 2) {
231 throw std::invalid_argument("statcpp::two_way_anova: at least 2 replications per cell are required (n_rep >= 2)");
232 }
233 std::size_t n_total = 0;
234 double grand_sum = 0.0;
235
236 for (std::size_t i = 0; i < a; ++i) {
237 if (data[i].size() != b) {
238 throw std::invalid_argument("statcpp::two_way_anova: inconsistent number of levels for factor B");
239 }
240 for (std::size_t j = 0; j < b; ++j) {
241 if (data[i][j].size() != n_rep) {
242 throw std::invalid_argument("statcpp::two_way_anova: unequal cell sizes not supported");
243 }
244 n_total += n_rep;
245 for (double x : data[i][j]) {
246 grand_sum += x;
247 }
248 }
249 }
250
251 double grand_mean = grand_sum / static_cast<double>(n_total);
252 double n_rep_d = static_cast<double>(n_rep);
253 double a_d = static_cast<double>(a);
254 double b_d = static_cast<double>(b);
255
256 // Calculate means for each level
257 std::vector<double> mean_a(a, 0.0); // Mean for each level of factor A
258 std::vector<double> mean_b(b, 0.0); // Mean for each level of factor B
259 std::vector<std::vector<double>> mean_ab(a, std::vector<double>(b, 0.0)); // Cell means
260
261 for (std::size_t i = 0; i < a; ++i) {
262 for (std::size_t j = 0; j < b; ++j) {
263 double cell_sum = std::accumulate(data[i][j].begin(), data[i][j].end(), 0.0);
264 mean_ab[i][j] = cell_sum / n_rep_d;
265 mean_a[i] += cell_sum;
266 mean_b[j] += cell_sum;
267 }
268 mean_a[i] /= (b_d * n_rep_d);
269 }
270 for (std::size_t j = 0; j < b; ++j) {
271 mean_b[j] /= (a_d * n_rep_d);
272 }
273
274 // Calculate sum of squares
275 double ss_a = 0.0; // Sum of squares for factor A
276 double ss_b = 0.0; // Sum of squares for factor B
277 double ss_ab = 0.0; // Sum of squares for interaction
278 double ss_error = 0.0; // Error sum of squares
279
280 for (std::size_t i = 0; i < a; ++i) {
281 double diff_a = mean_a[i] - grand_mean;
282 ss_a += diff_a * diff_a;
283 }
284 ss_a *= b_d * n_rep_d;
285
286 for (std::size_t j = 0; j < b; ++j) {
287 double diff_b = mean_b[j] - grand_mean;
288 ss_b += diff_b * diff_b;
289 }
290 ss_b *= a_d * n_rep_d;
291
292 for (std::size_t i = 0; i < a; ++i) {
293 for (std::size_t j = 0; j < b; ++j) {
294 double interaction = mean_ab[i][j] - mean_a[i] - mean_b[j] + grand_mean;
295 ss_ab += interaction * interaction;
296
297 for (double x : data[i][j]) {
298 double error = x - mean_ab[i][j];
299 ss_error += error * error;
300 }
301 }
302 }
303 ss_ab *= n_rep_d;
304
305 double ss_total = ss_a + ss_b + ss_ab + ss_error;
306
307 // Degrees of freedom
308 double df_a = a_d - 1.0;
309 double df_b = b_d - 1.0;
310 double df_ab = df_a * df_b;
311 double df_error = static_cast<double>(n_total) - a_d * b_d;
312 double df_total = static_cast<double>(n_total) - 1.0;
313
314 // Mean squares
315 double ms_a = ss_a / df_a;
316 double ms_b = ss_b / df_b;
317 double ms_ab = ss_ab / df_ab;
318 double ms_error = ss_error / df_error;
319
320 // F statistics and p-values (handle degenerate case ms_error == 0)
321 double f_a, f_b, f_ab;
322 double p_a, p_b, p_ab;
323 if (ms_error == 0.0) {
324 f_a = (ms_a == 0.0) ? 0.0 : std::numeric_limits<double>::infinity();
325 f_b = (ms_b == 0.0) ? 0.0 : std::numeric_limits<double>::infinity();
326 f_ab = (ms_ab == 0.0) ? 0.0 : std::numeric_limits<double>::infinity();
327 p_a = (ms_a == 0.0) ? 1.0 : 0.0;
328 p_b = (ms_b == 0.0) ? 1.0 : 0.0;
329 p_ab = (ms_ab == 0.0) ? 1.0 : 0.0;
330 } else {
331 f_a = ms_a / ms_error;
332 f_b = ms_b / ms_error;
333 f_ab = ms_ab / ms_error;
334 p_a = 1.0 - f_cdf(f_a, df_a, df_error);
335 p_b = 1.0 - f_cdf(f_b, df_b, df_error);
336 p_ab = 1.0 - f_cdf(f_ab, df_ab, df_error);
337 }
338
339 anova_row factor_a{"Factor A", ss_a, df_a, ms_a, f_a, p_a};
340 anova_row factor_b{"Factor B", ss_b, df_b, ms_b, f_b, p_b};
341 anova_row interaction{"A x B", ss_ab, df_ab, ms_ab, f_ab, p_ab};
342 anova_row error{"Error", ss_error, df_error, ms_error, 0.0, 0.0};
343
344 return {factor_a, factor_b, interaction, error,
345 ss_total, df_total, a, b, n_total, grand_mean};
346}
347
348// ============================================================================
349// Post-hoc Comparisons
350// ============================================================================
351
371 const std::vector<std::vector<double>>& groups,
372 double alpha = 0.05)
373{
374 (void)groups; // Currently unused; all statistics derived from anova_result
375
376 if (alpha <= 0.0 || alpha >= 1.0) {
377 throw std::invalid_argument("statcpp::tukey_hsd: alpha must be in (0, 1)");
378 }
379
380 std::size_t k = anova_result.n_groups;
381 double mse = anova_result.within.ms;
382 double df_error = anova_result.within.df;
383 double k_d = static_cast<double>(k);
384
385 double q_crit = studentized_range_quantile(1.0 - alpha, k_d, df_error);
386
387 std::vector<posthoc_comparison> comparisons;
388
389 // Compare all pairs
390 for (std::size_t i = 0; i < k; ++i) {
391 for (std::size_t j = i + 1; j < k; ++j) {
392 double mean_diff = anova_result.group_means[i] - anova_result.group_means[j];
393 double n_i = static_cast<double>(anova_result.group_sizes[i]);
394 double n_j = static_cast<double>(anova_result.group_sizes[j]);
395
396 // Tukey-Kramer standard error
397 double se = std::sqrt(mse * 0.5 * (1.0 / n_i + 1.0 / n_j));
398
399 double q_stat, p_value, lower, upper;
400 bool significant;
401
402 if (se == 0.0) {
403 // Degenerate case: within-group variance is zero
404 if (mean_diff == 0.0) {
405 q_stat = 0.0; p_value = 1.0;
406 lower = 0.0; upper = 0.0; significant = false;
407 } else {
408 q_stat = std::numeric_limits<double>::infinity();
409 p_value = 0.0;
410 lower = mean_diff; upper = mean_diff; significant = true;
411 }
412 } else {
413 // q statistic (studentized range statistic)
414 q_stat = std::abs(mean_diff) / se;
415
416 // p-value from studentized range distribution
417 p_value = 1.0 - studentized_range_cdf(q_stat, k_d, df_error);
418 p_value = std::max(0.0, std::min(1.0, p_value));
419
420 double margin = q_crit * se;
421 lower = mean_diff - margin;
422 upper = mean_diff + margin;
423 significant = (q_stat > q_crit);
424 }
425
426 comparisons.push_back({i, j, mean_diff, se, q_stat, p_value, lower, upper, significant});
427 }
428 }
429
430 return {"Tukey HSD", comparisons, alpha, mse, df_error};
431}
432
445 double alpha = 0.05)
446{
447 if (alpha <= 0.0 || alpha >= 1.0) {
448 throw std::invalid_argument("statcpp::bonferroni_posthoc: alpha must be in (0, 1)");
449 }
450
451 std::size_t k = anova_result.n_groups;
452 double mse = anova_result.within.ms;
453 double df_error = anova_result.within.df;
454
455 double n_comparisons = static_cast<double>(k * (k - 1) / 2);
456 double alpha_adj = alpha / n_comparisons;
457 double t_crit = t_quantile(1.0 - alpha_adj / 2.0, df_error);
458
459 std::vector<posthoc_comparison> comparisons;
460
461 for (std::size_t i = 0; i < k; ++i) {
462 for (std::size_t j = i + 1; j < k; ++j) {
463 double mean_diff = anova_result.group_means[i] - anova_result.group_means[j];
464 double n_i = static_cast<double>(anova_result.group_sizes[i]);
465 double n_j = static_cast<double>(anova_result.group_sizes[j]);
466
467 double se = std::sqrt(mse * (1.0 / n_i + 1.0 / n_j));
468
469 double t_stat, p_value, lower, upper;
470 bool significant;
471
472 if (se == 0.0) {
473 // Degenerate case: within-group variance is zero
474 if (mean_diff == 0.0) {
475 t_stat = 0.0; p_value = 1.0;
476 lower = 0.0; upper = 0.0; significant = false;
477 } else {
478 t_stat = std::copysign(std::numeric_limits<double>::infinity(), mean_diff);
479 p_value = 0.0;
480 lower = mean_diff; upper = mean_diff; significant = true;
481 }
482 } else {
483 t_stat = mean_diff / se;
484
485 p_value = 2.0 * (1.0 - t_cdf(std::abs(t_stat), df_error));
486 p_value = std::min(1.0, p_value * n_comparisons);
487
488 double margin = t_crit * se;
489 lower = mean_diff - margin;
490 upper = mean_diff + margin;
491 significant = (std::abs(t_stat) > t_crit);
492 }
493
494 comparisons.push_back({i, j, mean_diff, se, t_stat, p_value, lower, upper, significant});
495 }
496 }
497
498 return {"Bonferroni", comparisons, alpha, mse, df_error};
499}
500
515 std::size_t control_group = 0,
516 double alpha = 0.05)
517{
518 if (alpha <= 0.0 || alpha >= 1.0) {
519 throw std::invalid_argument("statcpp::dunnett_posthoc: alpha must be in (0, 1)");
520 }
521 if (control_group >= anova_result.n_groups) {
522 throw std::invalid_argument("statcpp::dunnett_posthoc: invalid control group index");
523 }
524
525 std::size_t k = anova_result.n_groups;
526 double mse = anova_result.within.ms;
527 double df_error = anova_result.within.df;
528
529 double n_comparisons = static_cast<double>(k - 1);
530 double alpha_adj = alpha / n_comparisons; // Bonferroni approximation
531 double t_crit = t_quantile(1.0 - alpha_adj / 2.0, df_error);
532
533 std::vector<posthoc_comparison> comparisons;
534
535 for (std::size_t i = 0; i < k; ++i) {
536 if (i == control_group) continue;
537
538 double mean_diff = anova_result.group_means[i] - anova_result.group_means[control_group];
539 double n_i = static_cast<double>(anova_result.group_sizes[i]);
540 double n_c = static_cast<double>(anova_result.group_sizes[control_group]);
541
542 double se = std::sqrt(mse * (1.0 / n_i + 1.0 / n_c));
543
544 double t_stat, p_value, lower, upper;
545 bool significant;
546
547 if (se == 0.0) {
548 // Degenerate case: within-group variance is zero
549 if (mean_diff == 0.0) {
550 t_stat = 0.0; p_value = 1.0;
551 lower = 0.0; upper = 0.0; significant = false;
552 } else {
553 t_stat = std::copysign(std::numeric_limits<double>::infinity(), mean_diff);
554 p_value = 0.0;
555 lower = mean_diff; upper = mean_diff; significant = true;
556 }
557 } else {
558 t_stat = mean_diff / se;
559
560 p_value = 2.0 * (1.0 - t_cdf(std::abs(t_stat), df_error));
561 p_value = std::min(1.0, p_value * n_comparisons);
562
563 double margin = t_crit * se;
564 lower = mean_diff - margin;
565 upper = mean_diff + margin;
566 significant = (std::abs(t_stat) > t_crit);
567 }
568
569 comparisons.push_back({i, control_group, mean_diff, se, t_stat, p_value, lower, upper, significant});
570 }
571
572 return {"Dunnett (Bonferroni approximation)", comparisons, alpha, mse, df_error};
573}
574
592 double alpha = 0.05)
593{
594 if (alpha <= 0.0 || alpha >= 1.0) {
595 throw std::invalid_argument("statcpp::scheffe_posthoc: alpha must be in (0, 1)");
596 }
597
598 std::size_t k = anova_result.n_groups;
599 double mse = anova_result.within.ms;
600 double df_between = anova_result.between.df;
601 double df_error = anova_result.within.df;
602
603 // Scheffe critical value
604 double f_crit = f_quantile(1.0 - alpha, df_between, df_error);
605 double scheffe_crit = std::sqrt(df_between * f_crit);
606
607 std::vector<posthoc_comparison> comparisons;
608
609 for (std::size_t i = 0; i < k; ++i) {
610 for (std::size_t j = i + 1; j < k; ++j) {
611 double mean_diff = anova_result.group_means[i] - anova_result.group_means[j];
612 double n_i = static_cast<double>(anova_result.group_sizes[i]);
613 double n_j = static_cast<double>(anova_result.group_sizes[j]);
614
615 double se = std::sqrt(mse * (1.0 / n_i + 1.0 / n_j));
616
617 double t_stat, p_value, lower, upper;
618 bool significant;
619
620 if (se == 0.0) {
621 // Degenerate case: within-group variance is zero
622 if (mean_diff == 0.0) {
623 t_stat = 0.0; p_value = 1.0;
624 lower = 0.0; upper = 0.0; significant = false;
625 } else {
626 t_stat = std::copysign(std::numeric_limits<double>::infinity(), mean_diff);
627 p_value = 0.0;
628 lower = mean_diff; upper = mean_diff; significant = true;
629 }
630 } else {
631 t_stat = mean_diff / se;
632
633 // Scheffe's F statistic
634 double f_stat = (t_stat * t_stat) / df_between;
635 p_value = 1.0 - f_cdf(f_stat, df_between, df_error);
636
637 double margin = scheffe_crit * se;
638 lower = mean_diff - margin;
639 upper = mean_diff + margin;
640 significant = (std::abs(t_stat) > scheffe_crit);
641 }
642
643 comparisons.push_back({i, j, mean_diff, se, t_stat, p_value, lower, upper, significant});
644 }
645 }
646
647 return {"Scheffe", comparisons, alpha, mse, df_error};
648}
649
650// ============================================================================
651// ANCOVA (Analysis of Covariance)
652// ============================================================================
653
662 double ss_error;
665 double df_error;
668 double ms_error;
669 double f_covariate;
670 double f_treatment;
671 double p_covariate;
672 double p_treatment;
673 std::vector<double> adjusted_means;
674};
675
689 const std::vector<std::vector<std::pair<double, double>>>& groups)
690{
691 std::size_t k = groups.size();
692 if (k < 2) {
693 throw std::invalid_argument("statcpp::one_way_ancova: need at least 2 groups");
694 }
695
696 // Organize data
697 std::size_t n_total = 0;
698 for (const auto& g : groups) {
699 if (g.empty()) {
700 throw std::invalid_argument("statcpp::one_way_ancova: empty group detected");
701 }
702 n_total += g.size();
703 }
704
705 if (n_total <= k + 1) {
706 throw std::invalid_argument("statcpp::one_way_ancova: insufficient observations");
707 }
708
709 // Calculate overall means
710 double sum_y = 0.0, sum_x = 0.0;
711 for (const auto& g : groups) {
712 for (const auto& pair : g) {
713 sum_y += pair.first;
714 sum_x += pair.second;
715 }
716 }
717 double grand_mean_y = sum_y / static_cast<double>(n_total);
718 double grand_mean_x = sum_x / static_cast<double>(n_total);
719
720 // Calculate means for each group
721 std::vector<double> group_mean_y(k);
722 std::vector<double> group_mean_x(k);
723 std::vector<std::size_t> group_sizes(k);
724
725 for (std::size_t i = 0; i < k; ++i) {
726 group_sizes[i] = groups[i].size();
727 double sy = 0.0, sx = 0.0;
728 for (const auto& pair : groups[i]) {
729 sy += pair.first;
730 sx += pair.second;
731 }
732 group_mean_y[i] = sy / static_cast<double>(group_sizes[i]);
733 group_mean_x[i] = sx / static_cast<double>(group_sizes[i]);
734 }
735
736 // Calculate total sums of squares and cross-products
737 double sst_y = 0.0; // Total sum of squares (y)
738 double sst_x = 0.0; // Total sum of squares (x)
739 double spt = 0.0; // Total sum of cross-products
740 double ssw_y = 0.0; // Within-group sum of squares (y)
741 double ssw_x = 0.0; // Within-group sum of squares (x)
742 double spw = 0.0; // Within-group sum of cross-products
743
744 for (std::size_t i = 0; i < k; ++i) {
745 for (const auto& pair : groups[i]) {
746 double dy_t = pair.first - grand_mean_y;
747 double dx_t = pair.second - grand_mean_x;
748 sst_y += dy_t * dy_t;
749 sst_x += dx_t * dx_t;
750 spt += dy_t * dx_t;
751
752 double dy_w = pair.first - group_mean_y[i];
753 double dx_w = pair.second - group_mean_x[i];
754 ssw_y += dy_w * dy_w;
755 ssw_x += dx_w * dx_w;
756 spw += dy_w * dx_w;
757 }
758 }
759
760 // Common regression coefficient (within groups)
761 double b_within = (ssw_x > 0.0) ? spw / ssw_x : 0.0;
762
763 // Calculate sum of squares
764 double ss_error = ssw_y - b_within * spw; // Adjusted within-group sum of squares
765 double ss_covariate = b_within * spw; // Variance explained by covariate
766
767 // Overall regression coefficient
768 double b_total = (sst_x > 0.0) ? spt / sst_x : 0.0;
769 double ss_total_adj = sst_y - b_total * spt;
770
771 // Sum of squares for treatment effect
772 double ss_treatment = ss_total_adj - ss_error;
773
774 // Degrees of freedom
775 double df_covariate = 1.0;
776 double df_treatment = static_cast<double>(k - 1);
777 double df_error = static_cast<double>(n_total - k - 1);
778
779 // Mean squares
780 double ms_covariate = ss_covariate / df_covariate;
781 double ms_treatment = ss_treatment / df_treatment;
782 double ms_error = ss_error / df_error;
783
784 // F statistics and p-values (handle degenerate case ms_error == 0)
785 double f_covariate, f_treatment;
786 double p_covariate, p_treatment;
787 if (ms_error == 0.0) {
788 f_covariate = (ms_covariate == 0.0) ? 0.0 : std::numeric_limits<double>::infinity();
789 f_treatment = (ms_treatment == 0.0) ? 0.0 : std::numeric_limits<double>::infinity();
790 p_covariate = (ms_covariate == 0.0) ? 1.0 : 0.0;
791 p_treatment = (ms_treatment == 0.0) ? 1.0 : 0.0;
792 } else {
793 f_covariate = ms_covariate / ms_error;
794 f_treatment = ms_treatment / ms_error;
795 p_covariate = 1.0 - f_cdf(f_covariate, df_covariate, df_error);
796 p_treatment = 1.0 - f_cdf(f_treatment, df_treatment, df_error);
797 }
798
799 // Adjusted means
800 std::vector<double> adjusted_means(k);
801 for (std::size_t i = 0; i < k; ++i) {
802 adjusted_means[i] = group_mean_y[i] - b_within * (group_mean_x[i] - grand_mean_x);
803 }
804
805 return {
806 ss_covariate, ss_treatment, ss_error,
807 df_covariate, df_treatment, df_error,
808 ms_covariate, ms_treatment, ms_error,
809 f_covariate, f_treatment,
810 p_covariate, p_treatment,
811 adjusted_means
812 };
813}
814
815// ============================================================================
816// Effect Size for ANOVA
817// ============================================================================
818
828inline double eta_squared(const one_way_anova_result& result)
829{
830 if (result.ss_total == 0.0) {
831 return 0.0;
832 }
833 return result.between.ss / result.ss_total;
834}
835
845inline double partial_eta_squared_a(const two_way_anova_result& result)
846{
847 double denom = result.factor_a.ss + result.error.ss;
848 if (denom == 0.0) {
849 return 0.0;
850 }
851 return result.factor_a.ss / denom;
852}
853
863inline double partial_eta_squared_b(const two_way_anova_result& result)
864{
865 double denom = result.factor_b.ss + result.error.ss;
866 if (denom == 0.0) {
867 return 0.0;
868 }
869 return result.factor_b.ss / denom;
870}
871
882{
883 double denom = result.interaction.ss + result.error.ss;
884 if (denom == 0.0) {
885 return 0.0;
886 }
887 return result.interaction.ss / denom;
888}
889
899inline double omega_squared(const one_way_anova_result& result)
900{
901 double ss_between = result.between.ss;
902 double df_between = result.between.df;
903 double ms_within = result.within.ms;
904 double ss_total = result.ss_total;
905
906 double denom = ss_total + ms_within;
907 if (denom == 0.0) {
908 return 0.0;
909 }
910 return (ss_between - df_between * ms_within) / denom;
911}
912
922inline double cohens_f(const one_way_anova_result& result)
923{
924 double eta_sq = eta_squared(result);
925 if (eta_sq >= 1.0) {
926 return std::numeric_limits<double>::infinity();
927 }
928 return std::sqrt(eta_sq / (1.0 - eta_sq));
929}
930
931} // namespace statcpp
Continuous distribution functions.
posthoc_result scheffe_posthoc(const one_way_anova_result &anova_result, double alpha=0.05)
Perform Scheffe's method for multiple comparisons.
Definition anova.hpp:591
double omega_squared(const one_way_anova_result &result)
Calculate Omega-squared for one-way ANOVA.
Definition anova.hpp:899
double partial_eta_squared_interaction(const two_way_anova_result &result)
Calculate Partial eta-squared for interaction in two-way ANOVA.
Definition anova.hpp:881
double cohens_f(const one_way_anova_result &result)
Calculate Cohen's f for one-way ANOVA.
Definition anova.hpp:922
double studentized_range_cdf(double q, double k, double df)
CDF of the studentized range distribution.
double f_quantile(double p, double df1, double df2)
F-distribution quantile function (Newton-Raphson method)
double t_cdf(double x, double df)
t-distribution cumulative distribution function (CDF)
ancova_result one_way_ancova(const std::vector< std::vector< std::pair< double, double > > > &groups)
Perform one-way analysis of covariance.
Definition anova.hpp:688
double partial_eta_squared_a(const two_way_anova_result &result)
Calculate Partial eta-squared for factor A in two-way ANOVA.
Definition anova.hpp:845
double eta_squared(const one_way_anova_result &result)
Calculate Eta-squared for one-way ANOVA.
Definition anova.hpp:828
two_way_anova_result two_way_anova(const std::vector< std::vector< std::vector< double > > > &data)
Perform two-way analysis of variance (with replication)
Definition anova.hpp:215
double t_quantile(double p, double df)
t-distribution quantile function (Newton-Raphson method)
double partial_eta_squared_b(const two_way_anova_result &result)
Calculate Partial eta-squared for factor B in two-way ANOVA.
Definition anova.hpp:863
std::vector< double > diff(Iterator first, Iterator last, std::size_t order=1)
Difference series (first-order or d-th order differencing)
posthoc_result dunnett_posthoc(const one_way_anova_result &anova_result, std::size_t control_group=0, double alpha=0.05)
Perform Dunnett's test for multiple comparisons against a control group.
Definition anova.hpp:514
posthoc_result tukey_hsd(const one_way_anova_result &anova_result, const std::vector< std::vector< double > > &groups, double alpha=0.05)
Perform Tukey's Honestly Significant Difference (HSD) test.
Definition anova.hpp:370
one_way_anova_result one_way_anova(const std::vector< std::vector< double > > &groups)
Perform one-way analysis of variance.
Definition anova.hpp:125
double mse(Iterator1 first1, Iterator1 last1, Iterator2 first2)
Mean Squared Error (MSE)
posthoc_result bonferroni_posthoc(const one_way_anova_result &anova_result, double alpha=0.05)
Perform Bonferroni method for multiple comparisons.
Definition anova.hpp:444
aggregation_result< K > group_sum(const std::vector< K > &keys, const std::vector< double > &values)
Sum per group.
double studentized_range_quantile(double p, double k, double df)
Quantile function of the studentized range distribution.
double f_cdf(double x, double df1, double df2)
F-distribution cumulative distribution function (CDF)
Structure storing ANCOVA (Analysis of Covariance) results.
Definition anova.hpp:659
double ss_covariate
Sum of squares for covariate.
Definition anova.hpp:660
double ms_treatment
Mean square for treatment.
Definition anova.hpp:667
double df_treatment
Degrees of freedom for treatment.
Definition anova.hpp:664
double ms_covariate
Mean square for covariate.
Definition anova.hpp:666
double ss_error
Error sum of squares.
Definition anova.hpp:662
double f_covariate
F statistic for covariate.
Definition anova.hpp:669
double p_treatment
p-value for treatment
Definition anova.hpp:672
double df_covariate
Degrees of freedom for covariate.
Definition anova.hpp:663
double f_treatment
F statistic for treatment.
Definition anova.hpp:670
std::vector< double > adjusted_means
Adjusted group means.
Definition anova.hpp:673
double ss_treatment
Sum of squares for treatment effect.
Definition anova.hpp:661
double ms_error
Mean square for error.
Definition anova.hpp:668
double p_covariate
p-value for covariate
Definition anova.hpp:671
double df_error
Degrees of freedom for error.
Definition anova.hpp:665
Structure representing a row in the ANOVA table.
Definition anova.hpp:35
double ms
Mean Square.
Definition anova.hpp:39
double ss
Sum of Squares.
Definition anova.hpp:37
double p_value
p-value
Definition anova.hpp:41
double f_statistic
F statistic.
Definition anova.hpp:40
double df
Degrees of freedom.
Definition anova.hpp:38
std::string source
Name of the source of variation.
Definition anova.hpp:36
Structure storing one-way ANOVA results.
Definition anova.hpp:49
std::vector< std::size_t > group_sizes
Size of each group.
Definition anova.hpp:58
anova_row within
Within-group variation (residual)
Definition anova.hpp:51
double df_total
Total degrees of freedom.
Definition anova.hpp:53
std::size_t n_groups
Number of groups.
Definition anova.hpp:54
double ss_total
Total sum of squares.
Definition anova.hpp:52
std::size_t n_total
Total number of observations.
Definition anova.hpp:55
std::vector< double > group_means
Mean of each group.
Definition anova.hpp:57
anova_row between
Between-group variation.
Definition anova.hpp:50
double grand_mean
Grand mean.
Definition anova.hpp:56
Structure storing individual pairwise comparison result in post-hoc tests.
Definition anova.hpp:84
double mean_diff
Difference in means.
Definition anova.hpp:87
double lower
Confidence interval lower bound.
Definition anova.hpp:91
std::size_t group2
Index of group 2.
Definition anova.hpp:86
double statistic
Test statistic.
Definition anova.hpp:89
double upper
Confidence interval upper bound.
Definition anova.hpp:92
bool significant
Whether significant.
Definition anova.hpp:93
double se
Standard error.
Definition anova.hpp:88
double p_value
p-value
Definition anova.hpp:90
std::size_t group1
Index of group 1.
Definition anova.hpp:85
Structure storing post-hoc comparison results.
Definition anova.hpp:101
std::string method
Method name.
Definition anova.hpp:102
double alpha
Significance level.
Definition anova.hpp:104
std::vector< posthoc_comparison > comparisons
All comparisons.
Definition anova.hpp:103
double df_error
Error degrees of freedom.
Definition anova.hpp:106
double mse
Mean square error.
Definition anova.hpp:105
Structure storing two-way ANOVA results.
Definition anova.hpp:66
double grand_mean
Grand mean.
Definition anova.hpp:76
anova_row factor_a
Effect of factor A.
Definition anova.hpp:67
anova_row interaction
Interaction effect.
Definition anova.hpp:69
anova_row error
Error (residual)
Definition anova.hpp:70
std::size_t levels_a
Number of levels for factor A.
Definition anova.hpp:73
std::size_t levels_b
Number of levels for factor B.
Definition anova.hpp:74
double df_total
Total degrees of freedom.
Definition anova.hpp:72
std::size_t n_total
Total number of observations.
Definition anova.hpp:75
double ss_total
Total sum of squares.
Definition anova.hpp:71
anova_row factor_b
Effect of factor B.
Definition anova.hpp:68