freq_test() is an S3
generic with methods for one-way and two-way freq_table()
results. The method is selected from the result class: no grouping
variable gives a goodness-of-fit test against equal category
probabilities; one grouping variable gives a test of independence.
.data must be a complete result with its category/count
columns and analysis metadata intact. This applies to the generic and
both supported methods. Filter the original observations to define an
analysis population, then calculate the table and test; filtering table
rows can invalidate a test.
one <- freq_table(mtcars, am)
one |> freq_test()
#> # A tibble: 2 × 10
#> am n prop lcl ucl n_expected chi2_contrib chi2_pearson df
#> <dbl> <int> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <int>
#> 1 0 19 0.594 0.409 0.755 16 0.562 1.12 1
#> 2 1 13 0.406 0.245 0.591 16 0.562 1.12 1
#> # ℹ 1 more variable: p_chi2_pearson <dbl>
one |> freq_test() |> select(am, n, prop, n_expected, p_chi2_pearson)
#> # A tibble: 2 × 5
#> am n prop n_expected p_chi2_pearson
#> <dbl> <int> <dbl> <dbl> <dbl>
#> 1 0 19 0.594 16 0.289
#> 2 1 13 0.406 16 0.289The two counts are 19 and 13. Under equal proportions, each expected count is 16. The Pearson statistic is 1.125 with 1 degree of freedom, giving p approximately 0.289. The same overall statistic and p-value are repeated on each row to make them available for formatting.
This tests an equal-probability null; it does not accept arbitrary
expected probabilities. The generic forwards ... to the
selected method, but both methods currently require it to be empty. No
method argument or other extra option is supported.
two <- mtcars |> group_by(am) |> freq_table(cyl)
tested <- two |> freq_test()
#> One or more expected cell counts are <= 5. Fisher's exact p-value is also returned.
tested |> select(am, cyl, n, n_expected, p_chi2_pearson, p_fisher)
#> # A tibble: 6 × 6
#> am cyl n n_expected p_chi2_pearson p_fisher
#> <dbl> <dbl> <int> <dbl> <dbl> <dbl>
#> 1 0 4 3 6.53 0.0126 0.00910
#> 2 0 6 4 4.16 0.0126 0.00910
#> 3 0 8 12 8.31 0.0126 0.00910
#> 4 1 4 8 4.47 0.0126 0.00910
#> 5 1 6 3 2.84 0.0126 0.00910
#> 6 1 8 2 5.69 0.0126 0.00910This tests independence between am and cyl.
Output includes n_col (outcome marginal counts), expected
cell counts, chi2_contrib, the summed statistic, numbers of
rows/columns (r, c), degrees of freedom, and
p_chi2_pearson. The Pearson test is uncorrected, including
for a 2 by 2 table.
When any expected cell count is <= 5, the function emits a message
and adds p_fisher alongside the Pearson result. Fisher’s
calculation uses the full rectangular contingency table. It is not
restricted to two group categories. As with R’s
fisher.test(), a large exact calculation may fail; that
error is propagated.
mtcars |> group_by(cyl) |> freq_table(am) |> freq_test()
#> One or more expected cell counts are <= 5. Fisher's exact p-value is also returned.
#> # A tibble: 6 × 16
#> cyl am n n_group prop_group lcl_group ucl_group n_col n_expected
#> <dbl> <dbl> <int> <int> <dbl> <dbl> <dbl> <dbl> <dbl>
#> 1 4 0 3 11 0.273 0.0716 0.646 19 6.53
#> 2 4 1 8 11 0.727 0.354 0.928 13 4.47
#> 3 6 0 4 7 0.571 0.150 0.909 19 4.16
#> 4 6 1 3 7 0.429 0.0906 0.850 13 2.84
#> 5 8 0 12 14 0.857 0.520 0.971 19 8.31
#> 6 8 1 2 14 0.143 0.0292 0.480 13 5.69
#> # ℹ 7 more variables: chi2_contrib <dbl>, chi2_pearson <dbl>, r <int>, c <int>,
#> # df <int>, p_chi2_pearson <dbl>, p_fisher <dbl>
# Table-row order does not determine contingency-table structure.
two[c(6, 1, 4, 2, 5, 3), ] |> freq_test()
#> One or more expected cell counts are <= 5. Fisher's exact p-value is also returned.
#> # A tibble: 6 × 16
#> am cyl n n_group prop_group lcl_group ucl_group n_col n_expected
#> <dbl> <dbl> <int> <int> <dbl> <dbl> <dbl> <dbl> <dbl>
#> 1 1 8 2 13 0.154 0.0308 0.510 14 5.69
#> 2 0 4 3 19 0.158 0.0460 0.422 11 6.53
#> 3 1 4 8 13 0.615 0.305 0.854 11 4.47
#> 4 0 6 4 19 0.211 0.0733 0.473 7 4.16
#> 5 1 6 3 13 0.231 0.0632 0.572 7 2.84
#> 6 0 8 12 19 0.632 0.380 0.827 14 8.31
#> # ℹ 7 more variables: chi2_contrib <dbl>, chi2_pearson <dbl>, r <int>, c <int>,
#> # df <int>, p_chi2_pearson <dbl>, p_fisher <dbl>
reference <- table(mtcars$am, mtcars$cyl)
stopifnot(isTRUE(all.equal(tested$p_fisher[1], fisher.test(reference)$p.value)))When all expected counts exceed 5, only the Pearson p-value is appended:
larger <- mtcars[rep(seq_len(nrow(mtcars)), 10), ]
larger |> group_by(am) |> freq_table(cyl) |> freq_test()
#> # A tibble: 6 × 15
#> am cyl n n_group prop_group lcl_group ucl_group n_col n_expected
#> <dbl> <dbl> <int> <int> <dbl> <dbl> <dbl> <dbl> <dbl>
#> 1 0 4 30 190 0.158 0.112 0.217 110 65.3
#> 2 0 6 40 190 0.211 0.158 0.275 70 41.6
#> 3 0 8 120 190 0.632 0.560 0.698 140 83.1
#> 4 1 4 80 130 0.615 0.528 0.696 110 44.7
#> 5 1 6 30 130 0.231 0.166 0.312 70 28.4
#> 6 1 8 20 130 0.154 0.101 0.228 140 56.9
#> # ℹ 6 more variables: chi2_contrib <dbl>, chi2_pearson <dbl>, r <int>, c <int>,
#> # df <int>, p_chi2_pearson <dbl>This repetition is solely a demonstration of function behavior, not a way to create independent observations for an actual analysis.
Tests depend on counts and category identities, so generic headers and percentage display give the same p-values. Calculate tests before stacking results from different outcomes.
mtcars |> group_by(am) |>
freq_table(cyl, generic_col_names = TRUE, percent = TRUE) |>
freq_test() |>
freq_format("percent_group (lcl_group - ucl_group)", name = "estimate", digits = 2) |>
select(group_01_cat, cat, estimate, p_chi2_pearson, p_fisher)
#> One or more expected cell counts are <= 5. Fisher's exact p-value is also returned.
#> # A tibble: 6 × 5
#> group_01_cat cat estimate p_chi2_pearson p_fisher
#> <chr> <chr> <chr> <dbl> <dbl>
#> 1 0 4 15.79 (4.60 - 42.17) 0.0126 0.00910
#> 2 0 6 21.05 (7.33 - 47.32) 0.0126 0.00910
#> 3 0 8 63.16 (38.05 - 82.72) 0.0126 0.00910
#> 4 1 4 61.54 (30.52 - 85.36) 0.0126 0.00910
#> 5 1 6 23.08 (6.32 - 57.17) 0.0126 0.00910
#> 6 1 8 15.38 (3.08 - 50.96) 0.0126 0.00910Missing outcomes or group values are categories unless excluded
before freq_table(). An unobserved cell is
filled with zero for the test even if omitted from the returned table. A
completely empty marginal category makes a two-way test undefined:
remove unused levels with drop = TRUE. One-way tests can
retain a zero-count category as part of their equal-probability
null.
d <- data.frame(g = c("a", "a", "b", "b", NA),
y = c("no", "yes", "yes", NA, "no"))
d |> filter(!is.na(g), !is.na(y)) |>
group_by(g) |> freq_table(y, drop = TRUE) |> freq_test()
#> One or more expected cell counts are <= 5. Fisher's exact p-value is also returned.
#> # A tibble: 3 × 16
#> g y n n_group prop_group lcl_group ucl_group n_col n_expected
#> <chr> <chr> <int> <int> <dbl> <dbl> <dbl> <dbl> <dbl>
#> 1 a no 1 2 0.5 9.19e-12 1.000 1 0.667
#> 2 a yes 1 2 0.5 9.19e-12 1.000 2 1.33
#> 3 b yes 1 1 1 NA NA 2 0.667
#> # ℹ 7 more variables: chi2_contrib <dbl>, chi2_pearson <dbl>, r <int>, c <int>,
#> # df <int>, p_chi2_pearson <dbl>, p_fisher <dbl>There is no automatic test for multiple grouping variables, because a joint comparison and a test within each stratum answer different questions. The default method explains this limitation:
mtcars |> group_by(cyl, vs) |> freq_table(am) |> freq_test()
#> Error:
#> ! freq_test() supports freq_table() results with zero or one grouping variable. For multiple groups, select an explicitly defined analysis.one |> filter(am == 0) |> freq_test()
#> Error:
#> ! freq_test() requires a complete table with intact category/count columns; recreate it from the desired input observations.For statistical details, see R’s Pearson chi-square and Fisher exact test documentation. Both rely on an appropriate sampling design; counting repeated observations does not make them independent.