Use freq_tbl() for
counts and proportions and freq_table() when you also want
confidence intervals. Both analyze one outcome; preceding
group_by() calls specify any grouping variables. All
examples use local data frames.
Here are 16 observations whose counts can be checked directly: group
A has 3 “no” and 7 “yes”; group B has 4 “no” and 2 “yes”. The overall
counts are 7 “no” and 9 “yes”. The .data argument is the
data frame (supplied by the pipe below), and .freq_var is
its outcome column.
study <- data.frame(
group = rep(c("A", "A", "B", "B"), c(3, 7, 4, 2)),
outcome = rep(c("no", "yes", "no", "yes"), c(3, 7, 4, 2))
)
freq_tbl(study, outcome)
#> # A tibble: 2 × 3
#> outcome n prop
#> <chr> <int> <dbl>
#> 1 no 7 0.438
#> 2 yes 9 0.562
study |> freq_table(outcome)
#> # A tibble: 2 × 5
#> outcome n prop lcl ucl
#> <chr> <int> <dbl> <dbl> <dbl>
#> 1 no 7 0.438 0.204 0.702
#> 2 yes 9 0.562 0.298 0.796The first result contains outcome, n, and
prop: 7/16 and 9/16. The second adds lcl and
ucl. Proportions and interval limits are numbers on the 0–1
scale. Rounding in printed tibbles does not round stored values.
Outcomes retain their original type, including factor levels.
The column may also be a literal string. To forward an unquoted
column through your own function, embrace it with
{{ }}:
freq_tbl(study, "outcome")
#> # A tibble: 2 × 3
#> outcome n prop
#> <chr> <int> <dbl>
#> 1 no 7 0.438
#> 2 yes 9 0.562
freq_table(study, "outcome")
#> # A tibble: 2 × 5
#> outcome n prop lcl ucl
#> <chr> <int> <dbl> <dbl> <dbl>
#> 1 no 7 0.438 0.204 0.702
#> 2 yes 9 0.562 0.298 0.796
count_outcome <- function(data, variable) freq_tbl(data, {{ variable }})
interval_outcome <- function(data, variable) freq_table(data, {{ variable }})
count_outcome(study, outcome)
#> # A tibble: 2 × 3
#> outcome n prop
#> <chr> <int> <dbl>
#> 1 no 7 0.438
#> 2 yes 9 0.562
interval_outcome(study, outcome)
#> # A tibble: 2 × 5
#> outcome n prop lcl ucl
#> <chr> <int> <dbl> <dbl> <dbl>
#> 1 no 7 0.438 0.204 0.702
#> 2 yes 9 0.562 0.298 0.796grouped <- study |> group_by(group)
grouped |> freq_tbl(outcome)
#> # A tibble: 4 × 5
#> group outcome n n_group prop_group
#> <chr> <chr> <int> <int> <dbl>
#> 1 A no 3 10 0.3
#> 2 A yes 7 10 0.7
#> 3 B no 4 6 0.667
#> 4 B yes 2 6 0.333
grouped |> freq_table(outcome)
#> # A tibble: 4 × 7
#> group outcome n n_group prop_group lcl_group ucl_group
#> <chr> <chr> <int> <int> <dbl> <dbl> <dbl>
#> 1 A no 3 10 0.3 0.0764 0.690
#> 2 A yes 7 10 0.7 0.310 0.924
#> 3 B no 4 6 0.667 0.149 0.958
#> 4 B yes 2 6 0.333 0.0418 0.851n is the cell count; n_group is 10 for A
and 6 for B. Thus the within-group proportions are 3/10, 7/10, 4/6, and
2/6. Each group’s proportions sum to 1. freq_table() adds
lcl_group and ucl_group.
The same rule applies to each combination of multiple groups:
study$band <- rep(c("young", "old"), 8)
study |> group_by(group, band) |> freq_tbl(outcome)
#> # A tibble: 8 × 6
#> group band outcome n n_group prop_group
#> <chr> <chr> <chr> <int> <int> <dbl>
#> 1 A old no 1 5 0.2
#> 2 A old yes 4 5 0.8
#> 3 A young no 2 5 0.4
#> 4 A young yes 3 5 0.6
#> 5 B old no 2 3 0.667
#> 6 B old yes 1 3 0.333
#> 7 B young no 2 3 0.667
#> 8 B young yes 1 3 0.333
study |> group_by(group, band) |> freq_table(outcome)
#> # A tibble: 8 × 8
#> group band outcome n n_group prop_group lcl_group ucl_group
#> <chr> <chr> <chr> <int> <int> <dbl> <dbl> <dbl>
#> 1 A old no 1 5 0.2 0.00771 0.889
#> 2 A old yes 4 5 0.8 0.111 0.992
#> 3 A young no 2 5 0.4 0.0377 0.919
#> 4 A young yes 3 5 0.6 0.0810 0.962
#> 5 B old no 2 3 0.667 0.00314 0.999
#> 6 B old yes 1 3 0.333 0.000787 0.997
#> 7 B young no 2 3 0.667 0.00314 0.999
#> 8 B young yes 1 3 0.333 0.000787 0.997Results are ungrouped tibbles. This avoids carrying implicit grouping
into later calculations. To summarize the results by group, call
group_by() again. A one-person group can have a defined
proportion but has missing intervals under the package’s t-based
convention.
percent = FALSE is the default in both functions. Set it
to TRUE to multiply proportions, interval bounds, and
requested standard errors by 100. Count columns and t critical values do
not change. Proportion column names switch from prop* to
percent*.
grouped |> freq_tbl(outcome, percent = TRUE)
#> # A tibble: 4 × 5
#> group outcome n n_group percent_group
#> <chr> <chr> <int> <int> <dbl>
#> 1 A no 3 10 30
#> 2 A yes 7 10 70
#> 3 B no 4 6 66.7
#> 4 B yes 2 6 33.3
grouped |> freq_table(outcome, percent = TRUE)
#> # A tibble: 4 × 7
#> group outcome n n_group percent_group lcl_group ucl_group
#> <chr> <chr> <int> <int> <dbl> <dbl> <dbl>
#> 1 A no 3 10 30 7.64 69.0
#> 2 A yes 7 10 70 31.0 92.4
#> 3 B no 4 6 66.7 14.9 95.8
#> 4 B yes 2 6 33.3 4.18 85.1
freq_tbl(study, outcome, percent = TRUE)
#> # A tibble: 2 × 3
#> outcome n percent
#> <chr> <int> <dbl>
#> 1 no 7 43.8
#> 2 yes 9 56.2
freq_table(study, outcome, percent = TRUE)
#> # A tibble: 2 × 5
#> outcome n percent lcl ucl
#> <chr> <int> <dbl> <dbl> <dbl>
#> 1 no 7 43.8 20.4 70.2
#> 2 yes 9 56.2 29.8 79.6overall = FALSE keeps grouped output focused on
within-group results. With TRUE, n_total is
the total input rows, and prop_total is the cell
count divided by that total. For A/“no”, this is 3/16, not the
marginal 7/16 for all “no” outcomes. freq_table() also adds
overall interval bounds. For ungrouped data, overall = TRUE
adds only n_total, because prop and its
intervals are already overall.
grouped |> freq_tbl(outcome, overall = TRUE)
#> # A tibble: 4 × 7
#> group outcome n n_group prop_group n_total prop_total
#> <chr> <chr> <int> <int> <dbl> <int> <dbl>
#> 1 A no 3 10 0.3 16 0.188
#> 2 A yes 7 10 0.7 16 0.438
#> 3 B no 4 6 0.667 16 0.25
#> 4 B yes 2 6 0.333 16 0.125
grouped |> freq_table(outcome, overall = TRUE)
#> # A tibble: 4 × 11
#> group outcome n n_group prop_group n_total prop_total lcl_group ucl_group
#> <chr> <chr> <int> <int> <dbl> <int> <dbl> <dbl> <dbl>
#> 1 A no 3 10 0.3 16 0.188 0.0764 0.690
#> 2 A yes 7 10 0.7 16 0.438 0.310 0.924
#> 3 B no 4 6 0.667 16 0.25 0.149 0.958
#> 4 B yes 2 6 0.333 16 0.125 0.0418 0.851
#> # ℹ 2 more variables: lcl_total <dbl>, ucl_total <dbl>
freq_tbl(study, outcome, overall = TRUE)
#> # A tibble: 2 × 4
#> outcome n prop n_total
#> <chr> <int> <dbl> <int>
#> 1 no 7 0.438 16
#> 2 yes 9 0.562 16
freq_table(study, outcome, overall = TRUE)
#> # A tibble: 2 × 6
#> outcome n prop n_total lcl ucl
#> <chr> <int> <dbl> <int> <dbl> <dbl>
#> 1 no 7 0.438 16 0.204 0.702
#> 2 yes 9 0.562 16 0.298 0.796
grouped |> freq_table(outcome, overall = TRUE, percent = TRUE)
#> # A tibble: 4 × 11
#> group outcome n n_group percent_group n_total percent_total lcl_group
#> <chr> <chr> <int> <int> <dbl> <int> <dbl> <dbl>
#> 1 A no 3 10 30 16 18.8 7.64
#> 2 A yes 7 10 70 16 43.8 31.0
#> 3 B no 4 6 66.7 16 25 14.9
#> 4 B yes 2 6 33.3 16 12.5 4.18
#> # ℹ 3 more variables: ucl_group <dbl>, lcl_total <dbl>, ucl_total <dbl>Missing outcomes and grouping values count as categories by default.
To exclude them, filter the original observations. Neither function has
an na.rm argument. The denominators always describe the
rows after filtering.
with_missing <- data.frame(
group = factor(c("A", "A", NA), levels = c("A", "B")),
outcome = factor(c("no", NA, "yes"), levels = c("no", "yes", "unused"))
)
with_missing |> group_by(group) |> freq_tbl(outcome)
#> # A tibble: 10 × 5
#> group outcome n n_group prop_group
#> <fct> <fct> <int> <int> <dbl>
#> 1 A no 1 2 0.5
#> 2 A yes 0 2 0
#> 3 A unused 0 2 0
#> 4 A <NA> 1 2 0.5
#> 5 B no 0 0 NA
#> 6 B yes 0 0 NA
#> 7 B unused 0 0 NA
#> 8 <NA> no 0 1 0
#> 9 <NA> yes 1 1 1
#> 10 <NA> unused 0 1 0
with_missing |> group_by(group) |> freq_table(outcome)
#> # A tibble: 10 × 7
#> group outcome n n_group prop_group lcl_group ucl_group
#> <fct> <fct> <int> <int> <dbl> <dbl> <dbl>
#> 1 A no 1 2 0.5 9.19e-12 1.000
#> 2 A yes 0 2 0 NA NA
#> 3 A unused 0 2 0 NA NA
#> 4 A <NA> 1 2 0.5 9.19e-12 1.000
#> 5 B no 0 0 NA NA NA
#> 6 B yes 0 0 NA NA NA
#> 7 B unused 0 0 NA NA NA
#> 8 <NA> no 0 1 0 NA NA
#> 9 <NA> yes 1 1 1 NA NA
#> 10 <NA> unused 0 1 0 NA NA
with_missing |>
filter(!is.na(group), !is.na(outcome)) |>
group_by(group) |> freq_tbl(outcome)
#> # A tibble: 6 × 5
#> group outcome n n_group prop_group
#> <fct> <fct> <int> <int> <dbl>
#> 1 A no 1 1 1
#> 2 A yes 0 1 0
#> 3 A unused 0 1 0
#> 4 B no 0 0 NA
#> 5 B yes 0 0 NA
#> 6 B unused 0 0 NAdrop = FALSE retains unobserved factor levels and empty
factor-group combinations with n = 0, following dplyr’s count
semantics. A completely empty group has n_group = 0 and
missing proportions and intervals. This option is independent of
missing-value removal.
freq_tbl(with_missing, outcome, drop = FALSE)
#> # A tibble: 4 × 3
#> outcome n prop
#> <fct> <int> <dbl>
#> 1 no 1 0.333
#> 2 yes 1 0.333
#> 3 unused 0 0
#> 4 <NA> 1 0.333
freq_tbl(with_missing, outcome, drop = TRUE)
#> # A tibble: 3 × 3
#> outcome n prop
#> <fct> <int> <dbl>
#> 1 no 1 0.333
#> 2 yes 1 0.333
#> 3 <NA> 1 0.333
freq_table(with_missing, outcome, drop = FALSE)
#> # A tibble: 4 × 5
#> outcome n prop lcl ucl
#> <fct> <int> <dbl> <dbl> <dbl>
#> 1 no 1 0.333 0.000787 0.997
#> 2 yes 1 0.333 0.000787 0.997
#> 3 unused 0 0 NA NA
#> 4 <NA> 1 0.333 0.000787 0.997
freq_table(with_missing, outcome, drop = TRUE)
#> # A tibble: 3 × 5
#> outcome n prop lcl ucl
#> <fct> <int> <dbl> <dbl> <dbl>
#> 1 no 1 0.333 0.000787 0.997
#> 2 yes 1 0.333 0.000787 0.997
#> 3 <NA> 1 0.333 0.000787 0.997
with_missing |> group_by(group, .drop = FALSE) |>
freq_table(outcome, drop = TRUE)
#> # A tibble: 3 × 7
#> group outcome n n_group prop_group lcl_group ucl_group
#> <fct> <fct> <int> <int> <dbl> <dbl> <dbl>
#> 1 A no 1 2 0.5 9.19e-12 1.000
#> 2 A <NA> 1 2 0.5 9.19e-12 1.000
#> 3 <NA> yes 1 1 1 NA NAAn empty data frame with a factor outcome can therefore produce explicit zero rows; an empty character outcome produces a zero-row result.
generic_col_names = FALSE uses variable names as headers
and preserves their types. With TRUE, the outcome becomes
col/cat, and groups become
group_01_col/group_01_cat,
group_02_col/ group_02_cat, and so on in
group_by() order. All category columns become character,
allowing numeric and character outcomes to be stacked.
grouped |> freq_tbl(outcome, generic_col_names = TRUE)
#> # A tibble: 4 × 7
#> group_01_col group_01_cat col cat n n_group prop_group
#> <chr> <chr> <chr> <chr> <int> <int> <dbl>
#> 1 group A outcome no 3 10 0.3
#> 2 group A outcome yes 7 10 0.7
#> 3 group B outcome no 4 6 0.667
#> 4 group B outcome yes 2 6 0.333
grouped |> freq_table(outcome, generic_col_names = TRUE)
#> # A tibble: 4 × 9
#> group_01_col group_01_cat col cat n n_group prop_group lcl_group
#> <chr> <chr> <chr> <chr> <int> <int> <dbl> <dbl>
#> 1 group A outcome no 3 10 0.3 0.0764
#> 2 group A outcome yes 7 10 0.7 0.310
#> 3 group B outcome no 4 6 0.667 0.149
#> 4 group B outcome yes 2 6 0.333 0.0418
#> # ℹ 1 more variable: ucl_group <dbl>
study |> group_by(group, band) |>
freq_table(outcome, generic_col_names = TRUE)
#> # A tibble: 8 × 11
#> group_01_col group_01_cat group_02_col group_02_cat col cat n n_group
#> <chr> <chr> <chr> <chr> <chr> <chr> <int> <int>
#> 1 group A band old outco… no 1 5
#> 2 group A band old outco… yes 4 5
#> 3 group A band young outco… no 2 5
#> 4 group A band young outco… yes 3 5
#> 5 group B band old outco… no 2 3
#> 6 group B band old outco… yes 1 3
#> 7 group B band young outco… no 2 3
#> 8 group B band young outco… yes 1 3
#> # ℹ 3 more variables: prop_group <dbl>, lcl_group <dbl>, ucl_group <dbl>
bind_rows(
freq_tbl(study, outcome, generic_col_names = TRUE),
freq_tbl(mtcars, am, generic_col_names = TRUE)
)
#> # A tibble: 4 × 4
#> col cat n prop
#> <chr> <chr> <int> <dbl>
#> 1 outcome no 7 0.438
#> 2 outcome yes 9 0.562
#> 3 am 0 19 0.594
#> 4 am 1 13 0.406Calculate intervals and tests before stacking separate analyses. Metadata belongs to one analysis and is not a registry for a stack of tables.
Only freq_table() has interval controls.
percent_ci = 95 means 95% confidence, regardless of
percent. Any numeric level strictly between 0 and 100 is
accepted. ci_type is either "logit" (default)
or "wald"; both work with any number of groups.
grouped |> freq_table(outcome, percent_ci = 90, ci_type = "logit")
#> # A tibble: 4 × 7
#> group outcome n n_group prop_group lcl_group ucl_group
#> <chr> <chr> <int> <int> <dbl> <dbl> <dbl>
#> 1 A no 3 10 0.3 0.101 0.619
#> 2 A yes 7 10 0.7 0.381 0.899
#> 3 B no 4 6 0.667 0.228 0.931
#> 4 B yes 2 6 0.333 0.0688 0.772
grouped |> freq_table(outcome, percent_ci = 99, ci_type = "wald")
#> # A tibble: 4 × 7
#> group outcome n n_group prop_group lcl_group ucl_group
#> <chr> <chr> <int> <int> <dbl> <dbl> <dbl>
#> 1 A no 3 10 0.3 -0.196 0.796
#> 2 A yes 7 10 0.7 0.204 1.20
#> 3 B no 4 6 0.667 -0.183 1.52
#> 4 B yes 2 6 0.333 -0.517 1.18se = FALSE and critical_value = FALSE omit
intermediate statistics. Enable them independently to include standard
errors and Student-t critical values. These are also available for
overall results:
grouped |> freq_table(outcome, se = TRUE)
#> # A tibble: 4 × 8
#> group outcome n n_group prop_group se_group lcl_group ucl_group
#> <chr> <chr> <int> <int> <dbl> <dbl> <dbl> <dbl>
#> 1 A no 3 10 0.3 0.153 0.0764 0.690
#> 2 A yes 7 10 0.7 0.153 0.310 0.924
#> 3 B no 4 6 0.667 0.211 0.149 0.958
#> 4 B yes 2 6 0.333 0.211 0.0418 0.851
grouped |> freq_table(outcome, critical_value = TRUE)
#> # A tibble: 4 × 8
#> group outcome n n_group prop_group t_crit_group lcl_group ucl_group
#> <chr> <chr> <int> <int> <dbl> <dbl> <dbl> <dbl>
#> 1 A no 3 10 0.3 2.26 0.0764 0.690
#> 2 A yes 7 10 0.7 2.26 0.310 0.924
#> 3 B no 4 6 0.667 2.57 0.149 0.958
#> 4 B yes 2 6 0.333 2.57 0.0418 0.851
grouped |> freq_table(outcome, se = TRUE, critical_value = TRUE,
overall = TRUE, percent = TRUE)
#> # A tibble: 4 × 15
#> group outcome n n_group percent_group n_total percent_total se_group
#> <chr> <chr> <int> <int> <dbl> <int> <dbl> <dbl>
#> 1 A no 3 10 30 16 18.8 15.3
#> 2 A yes 7 10 70 16 43.8 15.3
#> 3 B no 4 6 66.7 16 25 21.1
#> 4 B yes 2 6 33.3 16 12.5 21.1
#> # ℹ 7 more variables: t_crit_group <dbl>, lcl_group <dbl>, ucl_group <dbl>,
#> # se_total <dbl>, t_crit_total <dbl>, lcl_total <dbl>, ucl_total <dbl>For formulas and boundary behavior, read Confidence Intervals.
... must be empty: both functions accept one outcome,
with all other options supplied by name. An old two-variable call gives
a migration error. Incorrectly named options are errors too.
freq_tbl(study, group, outcome)
#> Error:
#> ! freq_tbl() accepts one outcome and named options only. For grouped analysis use mtcars |> dplyr::group_by(cyl) |> freq_tbl(am), instead of freq_tbl(mtcars, cyl, am). Unused options: outcome.freq_table(study, group, outcome)
#> Error:
#> ! freq_table() accepts one outcome and named options only. For grouped analysis use mtcars |> dplyr::group_by(cyl) |> freq_table(am), instead of freq_table(mtcars, cyl, am). Unused options: outcome.freq_table(study, outcome, na.rm = TRUE)
#> Error:
#> ! freq_table() accepts one outcome and named options only. For grouped analysis use mtcars |> dplyr::group_by(cyl) |> freq_table(am), instead of freq_table(mtcars, cyl, am). Unused options: TRUE.Create derived variables with mutate() first. Do not use
the outcome as its own grouping variable. Rowwise input requires
ungroup(). Analysis names that conflict with generated
columns (for example n or prop) must be
renamed first, so result columns never silently overwrite
categories.