Skip to contents

periglue is a fork of table.glue with new features, bug fixes, and a cleaner API. This vignette covers what changed and how to update code that used table.glue.

Renamed functions

All table_* functions are now peri_*:

table.glue periglue
table_glue() peri_glue()
table_value() peri_value()
table_pvalue() peri_pvalue()
table_ester() peri_ester()
table_estin() peri_estin()

All other functions (round_spec(), round_using_*(), format_*(), bracket_*(), as_inline()) are unchanged.

New default rounding

table.glue used magnitude-based rounding by default — the number of decimal places depended on the size of the value:

  • values < 1 → 2 decimal places
  • values 1–10 → 1 decimal place
  • values > 10 → 0 decimal places

This meant a vector of values could be formatted inconsistently:

# Simulated mean ± SD pairs at different scales
peri_value(c(0.82, 4.7, 31.2), rspec = round_spec() |> round_using_magnitude())
#> [1] "0.82" "4.7"  "31"

periglue defaults to 1 decimal place for all values, which is more predictable and appropriate for most table cells:

peri_value(c(0.82, 4.7, 31.2))
#> [1] "0.8"  "4.7"  "31.2"

If you preferred the old magnitude-based behaviour, pass an explicit rspec:

rspec_mag <- round_spec() |>
  round_using_magnitude(digits = c(2, 1, 0), breaks = c(1, 10, Inf))

peri_value(c(0.82, 4.7, 31.2), rspec = rspec_mag)
#> [1] "0.82" "4.7"  "31"

New way to set a session default

table.glue required users to rename rspec fields manually before passing them to options():

# table.glue approach — no longer recommended
rspec <- round_spec() |> round_using_decimal(digits = 2)
names(rspec) <- paste("periglue", names(rspec), sep = ".")
options(rspec)

This worked but was easy to get wrong, and CRAN discourages packages from modifying user options.

periglue provides set_default_rounding_spec(), which stores the specification in the package’s own internal environment rather than in global options:

rspec <- round_spec() |> round_using_decimal(digits = 2)
set_default_rounding_spec(rspec)

peri_value(pi)
#> [1] "3.14"
peri_glue("mean (SD): {mean(mtcars$mpg)} ({sd(mtcars$mpg)})")
#> [1] "mean (SD): 20.09 (6.03)"

Pass NULL to clear the default and revert to factory settings:

set_default_rounding_spec(NULL)
peri_value(pi)
#> [1] "3.1"

set_default_rounding_spec() returns the previous default invisibly, making it easy to save and restore around a block of code:

rspec_narrow <- round_spec() |> round_using_decimal(digits = 3)

old <- set_default_rounding_spec(rspec_narrow)
peri_value(pi)
#> [1] "3.142"

set_default_rounding_spec(old)  # restore whatever was set before
peri_value(pi)
#> [1] "3.1"

New function: peri_n_pct()

The most common cell format in demographic tables — a count with its percentage — previously required peri_glue():

n   <- c(48, 32, 20)
pct <- c(0.48, 0.32, 0.20)

# old approach — relies on n and pct being in scope
peri_glue("{n} ({round(100 * pct, 1)}%)")
#> [1] "48.0 (48.0%)" "32.0 (32.0%)" "20.0 (20.0%)"

This works but rounds the percentage using the same specification as everything else in the template, and gives no control over formatting the count separately from the percentage. peri_n_pct() handles both in one call:

peri_n_pct(n = n, pct = pct)
#> [1] "48 (48.0%)" "32 (32.0%)" "20 (20.0%)"

Counts are always displayed as whole numbers regardless of the rspec used for the percentage, and the formatting settings (big mark, decimal mark, miss_replace) are inherited from a single specification:

peri_n_pct(n = 12480, pct = 0.621)
#> [1] "12,480 (62.1%)"

Missing percentages — common when a subgroup denominator is zero — are handled cleanly:

peri_n_pct(n = c(10, 0), pct = c(0.10, NaN))
#> [1] "10 (10.0%)" "0 (--)"

The output format is customisable via the form argument:

peri_n_pct(n = 48, pct = 0.48, form = "{n}/{100} [{pct}%]")
#> [1] "48/100 [48.0%]"

Bug fix: peri_glue() no longer modifies the calling environment

In table.glue, calling table_glue() injected an internal helper function ..f into the calling environment as a side effect. This was invisible in most cases but could corrupt the caller’s scope, and would raise an error if the calling environment happened to be locked (as can occur in certain dplyr contexts).

peri_glue() now evaluates in a disposable child environment, so nothing leaks:

check_leak <- function() {
  x <- 1.23456
  before <- ls(all.names = TRUE)
  peri_glue("{x}")
  after  <- ls(all.names = TRUE)
  setdiff(after, before)  # should be empty
}

check_leak()
#> [1] "before"

Bug fix: format_decimal() validates mark width

table.glue silently accepted multi-character decimal marks, which worked in older R versions but was deprecated and is becoming an error in recent R. periglue catches this at the point of specification rather than at formatting time:

round_spec() |> format_decimal(mark = "decimal!")
#> Error:
#> ! `mark` must be a single character, but 'decimal!' has 8 characters.