Formatting Results and Sample Sizes

Compute counts, intervals, and tests before preparing display strings. freq_format() appends presentation text; get_group_n() creates sample-size labels from observation-level data.

Formatting Input and Recipes

.data is a data frame, usually a result from freq_tbl(), freq_table(), or freq_test(). recipe is one quoted string mixing column names with punctuation or other literal text.

estimates <- freq_table(mtcars, am, percent = TRUE)
estimates |> freq_format("n (percent%)")
#> # A tibble: 2 × 6
#>      am     n percent   lcl   ucl formatted_stats
#>   <dbl> <int>   <dbl> <dbl> <dbl> <chr>          
#> 1     0    19    59.4  40.9  75.5 19 (59.375%)   
#> 2     1    13    40.6  24.5  59.1 13 (40.625%)
estimates |> freq_format("percent (lcl - ucl)")
#> # A tibble: 2 × 6
#>      am     n percent   lcl   ucl formatted_stats                            
#>   <dbl> <int>   <dbl> <dbl> <dbl> <chr>                                      
#> 1     0    19    59.4  40.9  75.5 59.375 (40.9422545981586 - 75.497646229202)
#> 2     1    13    40.6  24.5  59.1 40.625 (24.502353770798 - 59.0577454018414)
mtcars |> group_by(cyl) |> freq_table(am) |>
  freq_format("n: prop_group (lcl_group - ucl_group)")
#> # A tibble: 6 × 8
#>     cyl    am     n n_group prop_group lcl_group ucl_group formatted_stats      
#>   <dbl> <dbl> <int>   <int>      <dbl>     <dbl>     <dbl> <chr>                
#> 1     4     0     3      11      0.273    0.0716     0.646 3: 0.272727272727273…
#> 2     4     1     8      11      0.727    0.354      0.928 8: 0.727272727272727…
#> 3     6     0     4       7      0.571    0.150      0.909 4: 0.571428571428571…
#> 4     6     1     3       7      0.429    0.0906     0.850 3: 0.428571428571429…
#> 5     8     0    12      14      0.857    0.520      0.971 12: 0.85714285714285…
#> 6     8     1     2      14      0.143    0.0292     0.480 2: 0.142857142857143…

The recipe must use column names that exist in the input. Unmatched words are retained as literal text, so a misspelled name will print unchanged. Tokens are separated at word boundaries. Rename columns containing spaces or punctuation before putting them in recipes. Proportion and percentage column names are intentionally different.

Column Name and Decimal Places

name = NA uses the default formatted_stats. Supply a nonempty string to name the new column. If that column exists, it is replaced.

digits = NA uses character conversion without explicit rounding. A nonnegative integer requests that many decimal places for numeric double columns, including trailing zeros and thousands separators. Integer count columns retain their integer representation. Character categories are left as text.

estimates |> freq_format("percent (lcl - ucl)", name = NA, digits = NA)
#> # A tibble: 2 × 6
#>      am     n percent   lcl   ucl formatted_stats                            
#>   <dbl> <int>   <dbl> <dbl> <dbl> <chr>                                      
#> 1     0    19    59.4  40.9  75.5 59.375 (40.9422545981586 - 75.497646229202)
#> 2     1    13    40.6  24.5  59.1 40.625 (24.502353770798 - 59.0577454018414)
estimates |> freq_format("percent (lcl - ucl)", name = "percent_ci", digits = 2)
#> # A tibble: 2 × 6
#>      am     n percent   lcl   ucl percent_ci           
#>   <dbl> <int>   <dbl> <dbl> <dbl> <chr>                
#> 1     0    19    59.4  40.9  75.5 59.38 (40.94 - 75.50)
#> 2     1    13    40.6  24.5  59.1 40.62 (24.50 - 59.06)
estimates |> freq_format("n (percent%)", digits = 0)
#> # A tibble: 2 × 6
#>      am     n percent   lcl   ucl formatted_stats
#>   <dbl> <int>   <dbl> <dbl> <dbl> <chr>          
#> 1     0    19    59.4  40.9  75.5 19 (59%)       
#> 2     1    13    40.6  24.5  59.1 13 (41%)
freq_table(mtcars, am) |> freq_format("prop (lcl - ucl)", digits = 3)
#> # A tibble: 2 × 6
#>      am     n  prop   lcl   ucl formatted_stats      
#>   <dbl> <int> <dbl> <dbl> <dbl> <chr>                
#> 1     0    19 0.594 0.409 0.755 0.594 (0.409 - 0.755)
#> 2     1    13 0.406 0.245 0.591 0.406 (0.245 - 0.591)
estimates |> freq_format("n", name = "label") |>
  freq_format("n (percent%)", name = "label", digits = 1)
#> # A tibble: 2 × 6
#>      am     n percent   lcl   ucl label     
#>   <dbl> <int>   <dbl> <dbl> <dbl> <chr>     
#> 1     0    19    59.4  40.9  75.5 19 (59.4%)
#> 2     1    13    40.6  24.5  59.1 13 (40.6%)

The returned data frame retains all original statistics; only the new string is rounded. To show selected columns, follow with select().

estimates |> freq_format("percent (lcl - ucl)", name = "estimate", digits = 2) |>
  select(am, n, estimate)
#> # A tibble: 2 × 3
#>      am     n estimate             
#>   <dbl> <int> <chr>                
#> 1     0    19 59.38 (40.94 - 75.50)
#> 2     1    13 40.62 (24.50 - 59.06)

Zero-row input receives an empty character column. Missing numeric limits are displayed as missing-value text. Invalid recipes or negative decimal places produce informative errors.

estimates |> freq_format("n", digits = -1)
#> Error:
#> ! digits must be a nonnegative integer or NA.

Sample-Size Input and Filter Expressions

For get_group_n(), .data is the original observation-level data. ... accepts zero or more dplyr::filter() expressions. With no filters, all input observations are counted. Multiple expressions are combined with AND. Missing logical filter results are excluded, following filter(). The result is a character vector, not a table.

get_group_n(mtcars)
#> [1] "N = 32"
mtcars |> get_group_n(cyl == 4)
#> [1] "N = 11"
mtcars |> get_group_n(cyl == 4, am == 1)
#> [1] "N = 8"
mtcars |> get_group_n(cyl == 99)
#> [1] "N = 0"

These return “N = 32”, “N = 11”, “N = 8”, and “N = 0”. Passing a frequency table would count its category rows, not its observations.

For grouped data, one label is returned per retained group, in dplyr’s group order. Retain a companion group-label table if those labels will be used elsewhere.

mtcars |> group_by(cyl) |> get_group_n(am == 1)
#> [1] "N = 8" "N = 3" "N = 2"
data.frame(cyl = c(4, 6, 8),
           label = mtcars |> group_by(cyl) |> get_group_n(am == 1))
#>   cyl label
#> 1   4 N = 8
#> 2   6 N = 3
#> 3   8 N = 2

End-to-End Reporting

sample_label <- mtcars |> get_group_n(!is.na(am), !is.na(cyl))
sample_label
#> [1] "N = 32"
mtcars |> filter(!is.na(am), !is.na(cyl)) |>
  group_by(am) |>
  freq_table(cyl, percent = TRUE) |>
  freq_test() |>
  freq_format("n (percent_group%)", name = "count_percent", digits = 1) |>
  select(am, cyl, count_percent, p_chi2_pearson)
#> One or more expected cell counts are <= 5. Fisher's exact p-value is also returned.
#> # A tibble: 6 × 4
#>      am   cyl count_percent p_chi2_pearson
#>   <dbl> <dbl> <chr>                  <dbl>
#> 1     0     4 3 (15.8%)             0.0126
#> 2     0     6 4 (21.1%)             0.0126
#> 3     0     8 12 (63.2%)            0.0126
#> 4     1     4 8 (61.5%)             0.0126
#> 5     1     6 3 (23.1%)             0.0126
#> 6     1     8 2 (15.4%)             0.0126