library(dplyr, warn.conflicts = FALSE)
library(lme4)

knitr::opts_chunk$set(
  collapse = TRUE,
  comment = "#>",
  fig.path = "fig/README-"
)

printy

Over the years, I've written a lot of one-off functions for formatting numbers in RMarkdown documents. This packages collects them in a single location.

Installation ๐Ÿ“š

You can install printy from github with:

# install.packages("remotes")
remotes::install_github("tjmahr/printy")

Formatters โœ

fmt_fix_digits() prints a number with n digits of precision. R numbers lose precision when converted to strings. This function converts the numbers to strings and keeps precision. (It's a wrapper for sprintf().)

library(dplyr)
library(printy)
test_cor <- cor(mtcars[, 1:4]) 

# Typical loss of trailing zeroes
test_cor[1:4, 3] |> round(2) |> as.character()

test_cor[1:4, 3] |> fmt_fix_digits(2)

fmt_leading_zero() removes a leading zero on numbers that are bounded between โˆ’1 and 1, such as correlations or p-values.

fmt_leading_zero(c(-0.3, 0.4, 1))

fmt_minus_sign() formats negative numbers with a minus sign.

fmt_minus_sign(c(1, 2, -3, -0.4, -pi))

Putting it all together: Print a correlation matrix with 2 digits, no leading zero and with minus signs.

fmt_correlation <- function(xs, digits = 2) {
  xs |> fmt_fix_digits(digits) |> fmt_leading_zero() |> fmt_minus_sign()
}

test_cor |> 
  as.data.frame() |> 
  tibble::rownames_to_column(".rowname") |> 
  tibble::as_tibble() |> 
  mutate(
    across(-.rowname, fmt_correlation)
  ) |> 
  rename(` ` = .rowname) |> 
  knitr::kable(align = "lrrrr")

p-values ๐ŸŽฃ

fmt_p_value() formats p-values with n digits of precision, with no leading zero, and with very small values being printed with a < sign.

p <- c(1, 0.1, 0.01, 0.001, 0.0001)
fmt_p_value(p, digits = 2)
fmt_p_value(p, digits = 3)

fmt_p_value_md() formats p-values in markdown with nice defaults.

p <- c(1, 0.1, 0.06, 0.059, 0.051, 0.01, 0.001, 0.0001)
fmt_p_value_md(p)

These render as: r paste0(fmt_p_value_md(p), collapse = ", ").

Experimental formatters ๐Ÿงช

fmt_effect_md() is an experimental function for getting model effects formatted in markdown. You give the function a model, an effect and a string listing the quantities you want.

model <- lm(breaks ~ wool * tension, warpbreaks) 
summary(model)
# default to: b (beta), e (error), s (statistic), p (p value)
fmt_effect_md(model, "woolB", "besp")

r fmt_effect_md(model, "woolB", "besp")

# Just a subset of them
fmt_effect_md(model, "woolB", terms = "bp")

r fmt_effect_md(model, "woolB", terms = "bp")

# B for labeled b
fmt_effect_md(model, "woolB", terms = "Bp", b_lab = "Wool B")

r fmt_effect_md(model, "woolB", terms = "Bp", b_lab = "Wool B")

# i for interval
fmt_effect_md(model, "woolB", terms = "bi")

r fmt_effect_md(model, "woolB", terms = "bi")

# S for statistic with df
fmt_effect_md(model, "woolB", terms = "bSp")

r fmt_effect_md(model, "woolB", terms = "bSp")

# extra digits (except for p-values; those go through `fmt_p_value_md()`)
fmt_effect_md(model, "woolB", terms = "bep", digits = 6)

r fmt_effect_md(model, "woolB", terms = "bep", digits = 6)

These are the currently supported models:

For lme4 models, Wald confidence intervals are provided. For p-values, the Kenwood--Roger approximation for the degrees of freedom is used by default. We can also choose a method supported by the parameters package.

library(lme4)
data(Machines, package = "nlme")

m <- lmer(score ~ 1 + Machine + (Machine | Worker), data = Machines)

# Default is Kenward
fmt_effect_md(m, "MachineB", terms = "beSp")
fmt_effect_md(m, "MachineB", terms = "beSp", p_value_method = "kenward")

# Note residual degrees of freedom for Wald
fmt_effect_md(m, "MachineB", terms = "beSp", p_value_method = "wald")

# This example doesn't find differences between Satterthwaite and Kenward
fmt_effect_md(m, "MachineB", terms = "beSp", p_value_method = "satterthwaite")

We can also format effects from glmer() models. "S" is not supported because the model summary uses z statistics, not t statistics.

gm1 <- glmer(
  cbind(incidence, size - incidence) ~ period + (1 | herd),
  data = cbpp, 
  family = binomial
)

round(coef(summary(gm1)), 3)

fmt_effect_md(gm1, "period2", terms = "bespi")

# Don't use S here
fmt_effect_md(gm1, "period2", terms = "beSp")

Skeletons ๐Ÿฆด

I use fmt_ for formatting functions. The other convention in the package is skel_ to plug values into a formatting skeleton.

skel_conf_interval_pair() creates a confidence interval from two numbers.

skel_conf_interval_pair(c(1, 2))

skel_conf_interval() is the vectorized version. It is suitable for working on columns of numbers.

model <- lm(breaks ~ wool * tension, warpbreaks) 

ci_starts <- confint(model)[, 1] |> 
  fmt_fix_digits(2) |> 
  fmt_minus_sign()

ci_ends <- confint(model)[, 2] |> 
  fmt_fix_digits(2) |> 
  fmt_minus_sign()

skel_conf_interval(ci_starts, ci_ends)

skel_stat_n_value_pair() creates t-test-like or correlation-like statistic from a vector of two numbers.

skel_stat_n_value_pair(c("20", "2.0"))
skel_stat_n_value_pair(c("39", ".98"), stat = "*r*")

skel_se() and skel_ci() are shorthand functions to help with inline reporting.

skel_se(c(10, 4))

skel_ci(c("[1, 2]"))

skel_ci(c("[1, 2]"), ci_width = 90)

Formatting tables from lme4 models ๐Ÿ–‡

One thing I've had to do a lot is summarize mixed effects models fit with lme4. This package provides pretty_lme4_ranefs() which creates a dataframe random effect variances and covariances like those printed by summary().

For example, we can fit the model.

library(lme4)
model <- lmer(Reaction ~ Days + (Days | Subject), sleepstudy)
summary(model)

pretty_lme4_ranefs() creates the following dataframe.

pretty_lme4_ranefs(model)

Which in markdown renders as

knitr::kable(
  pretty_lme4_ranefs(model), 
  align = c("l", "l", "r", "r", "r")
)

Here's a dumb model with a lot going on in the random effects.

model <- lmer(mpg ~ wt * hp + (drat | gear) + (hp * cyl | am), mtcars)
model

knitr::kable(
  pretty_lme4_ranefs(model), 
  align = c("l", "l", "r", "r", "r", "r", "r", "r", "r")
)


tjmahr/printy documentation built on March 4, 2024, 1:25 a.m.