Using pubformat

Overview

pubformat provides tools for converting numeric statistical results into consistent, publication-ready character output.

The package is designed around a simple principle:

Statistical analysis and statistical formatting should remain separate.

R or another statistical procedure produces the numeric result. pubformat handles the reporting step without changing the underlying statistical result, inferential decision, or substantive interpretation.

library(pubformat)

The current development version includes formatting for:

Formatting p-values

format_p() converts numeric p-values into publication-ready text.

format_p(0.048)
#> [1] "p = .048"
format_p(0.0002)
#> [1] "p < .001"

The reporting threshold is strict. A value equal to .001 is retained, while a value below .001 is reported using less-than notation.

format_p(0.001)
#> [1] "p = .001"
format_p(0.0009)
#> [1] "p < .001"

Optional significance codes can be added when useful for tables or other compact displays.

format_p(0.048, sig = TRUE)
#> [1] "p = .048 *"
format_p(0.006, sig = TRUE)
#> [1] "p = .006 **"
format_p(0.0002, sig = TRUE)
#> [1] "p < .001 ***"

Significance codes are determined from the original numeric p-value rather than its rounded display value.

format_p(0.0499, sig = TRUE)
#> [1] "p = .050 *"

Formatting confidence intervals

format_ci() combines numeric lower and upper confidence limits into publication-ready interval notation.

format_ci(1.08, 1.87)
#> [1] "95% CI [1.08, 1.87]"

The confidence level can be changed.

format_ci(1.08, 1.87, level = 0.99)
#> [1] "99% CI [1.08, 1.87]"

Leading zeros are retained by default because the appropriate convention depends on the statistic being reported.

format_ci(0.21, 0.48)
#> [1] "95% CI [0.21, 0.48]"

For statistics that cannot exceed 1 in absolute value, leading zeros can be omitted.

format_ci(0.21, 0.48, leading_zero = FALSE)
#> [1] "95% CI [.21, .48]"

Formatting correlations

For Pearson correlations, format_r() provides a concise formatter.

format_r(0.42)
#> [1] "r = .42"
format_r(-0.31)
#> [1] "r = -.31"

For Pearson, Spearman, or Kendall correlations, format_cor() selects the appropriate reporting symbol.

format_cor(0.42, method = "pearson")
#> [1] "r = .42"
format_cor(0.42, method = "spearman")
#> [1] "ρ = .42"
format_cor(0.42, method = "kendall")
#> [1] "τ = .42"

format_cor() formats an existing coefficient. It does not calculate the correlation itself.

For example:

rho <- cor(
  mtcars$mpg,
  mtcars$wt,
  method = "spearman"
)

format_cor(rho, method = "spearman")
#> [1] "ρ = -.89"

Formatting percentages

format_percent() interprets numeric values as proportions by default.

format_percent(0.423)
#> [1] "42.3%"

Values that are already expressed as percentages can be identified explicitly.

format_percent(42.3, input = "percent")
#> [1] "42.3%"

This distinction prevents accidental rescaling.

format_percent(0.42)
#> [1] "42.0%"
format_percent(42, input = "percent")
#> [1] "42.0%"

The function is vectorized.

format_percent(c(0.25, 0.50, 0.75))
#> [1] "25.0%" "50.0%" "75.0%"

Values supplied with input = "percent" may also be negative or greater than 100, which can be useful for quantities such as percent change.

format_percent(-12.5, input = "percent")
#> [1] "-12.5%"
format_percent(150, input = "percent")
#> [1] "150.0%"

Formatting test statistics

format_t() and format_f() convert already-computed test statistics and their degrees of freedom into publication-ready text.

These functions do not perform statistical tests. They format numeric results produced by R or another statistical procedure.

t statistics

format_t() formats a t statistic together with its degrees of freedom.

format_t(0.86, df = 20)
#> [1] "t(20) = 0.86"

Negative statistics are preserved.

format_t(-2.14, df = 48)
#> [1] "t(48) = -2.14"

Fractional degrees of freedom are also supported.

format_t(3.27, df = 105.5)
#> [1] "t(105.5) = 3.27"

A p-value can be supplied directly.

format_t(
  0.86,
  df = 20,
  p = 0.041
)
#> [1] "t(20) = 0.86, p = .041"

The p-value is formatted internally using format_p().

format_t(
  statistic = c(3.10, 3.20),
  df = c(40, 40),
  p = c(0.001, 0.0009)
)
#> [1] "t(40) = 3.10, p = .001" "t(40) = 3.20, p < .001"

This preserves the distinction between p = .001 and p < .001.

F statistics

format_f() formats an F statistic together with numerator and denominator degrees of freedom.

format_f(
  2.50,
  df1 = 2,
  df2 = 30
)
#> [1] "F(2, 30) = 2.50"

Fractional degrees of freedom are retained when present.

format_f(
  4.4344,
  df1 = 1,
  df2 = 3262.9
)
#> [1] "F(1, 3262.9) = 4.43"

A p-value can be included in the same result.

format_f(
  4.4344,
  df1 = 1,
  df2 = 3262.9,
  p = 0.03530
)
#> [1] "F(1, 3262.9) = 4.43, p = .035"

Very small p-values retain the behavior of format_p().

format_f(
  107.8924,
  df1 = 1,
  df2 = 86.7,
  p = 2e-16
)
#> [1] "F(1, 86.7) = 107.89, p < .001"

This is useful for converting ANOVA or mixed-model output into manuscript-ready text.

Styled test-statistic symbols

Plain character output is the default because the R console does not render italics.

For documents that support markup, format_t() and format_f() can return Markdown, HTML, or LaTeX formatting.

format_f(
  4.4344,
  df1 = 1,
  df2 = 3262.9,
  p = 0.03530,
  style = "markdown"
)
#> [1] "*F*(1, 3262.9) = 4.43, *p* = .035"

Formatting effect sizes

The effect-size functions accept already-computed estimates and optional confidence intervals.

They do not calculate the effect size and do not classify the result as small, medium, or large.

Cohen’s d

format_d() formats Cohen’s d.

format_d(0.52)
#> [1] "d = 0.52"

Confidence limits can be included.

format_d(
  0.52,
  lower = 0.18,
  upper = 0.86
)
#> [1] "d = 0.52, 95% CI [0.18, 0.86]"

Negative estimates and confidence limits are preserved.

format_d(
  -0.31,
  lower = -0.62,
  upper = 0.00
)
#> [1] "d = -0.31, 95% CI [-0.62, 0.00]"

For output formats that support markup, the Latin statistical symbol can also be styled.

format_d(
  0.52,
  lower = 0.18,
  upper = 0.86,
  style = "markdown"
)
#> [1] "*d* = 0.52, 95% CI [0.18, 0.86]"

Hedges’ g

format_g() follows the same structure for Hedges’ g.

format_g(0.49)
#> [1] "g = 0.49"
format_g(
  0.49,
  lower = 0.16,
  upper = 0.82
)
#> [1] "g = 0.49, 95% CI [0.16, 0.82]"

Styled output is also available.

format_g(
  0.49,
  lower = 0.16,
  upper = 0.82,
  style = "markdown"
)
#> [1] "*g* = 0.49, 95% CI [0.16, 0.82]"

Eta squared

format_eta2() automatically provides the eta-squared symbol.

format_eta2(0.14)
#> [1] "η² = .14"

A confidence interval can be supplied.

format_eta2(
  0.14,
  lower = 0.08,
  upper = 0.21
)
#> [1] "η² = .14, 95% CI [.08, .21]"

Eta squared is bounded between 0 and 1, so leading zeros are omitted.

format_eta2(
  eta2 = c(0.14, 0.00, 0.67),
  lower = c(0.08, 0.00, 0.55),
  upper = c(0.21, 0.04, 0.78)
)
#> [1] "η² = .14, 95% CI [.08, .21]" "η² = .00, 95% CI [.00, .04]"
#> [3] "η² = .67, 95% CI [.55, .78]"

Partial eta squared

format_partial_eta2() automatically supplies the partial eta-squared notation.

format_partial_eta2(0.14)
#> [1] "ηₚ² = .14"

The returned character output contains the Greek eta, subscript p, and superscript 2 directly.

format_partial_eta2(
  0.14,
  lower = 0.08,
  upper = 0.21
)
#> [1] "ηₚ² = .14, 95% CI [.08, .21]"

This avoids the need to manually insert the notation in a word processor.

Omega squared

format_omega2() automatically supplies the omega-squared symbol.

format_omega2(0.08)
#> [1] "ω² = .08"

Confidence limits can be supplied.

format_omega2(
  0.08,
  lower = 0.02,
  upper = 0.15
)
#> [1] "ω² = .08, 95% CI [.02, .15]"

Negative omega-squared estimates are preserved rather than automatically changed to zero.

format_omega2(-0.02)
#> [1] "ω² = -.02"

Likewise, negative confidence-limit estimates remain unchanged.

format_omega2(
  0.01,
  lower = -0.03,
  upper = 0.05
)
#> [1] "ω² = .01, 95% CI [-.03, .05]"

This reflects the package’s role as a formatter rather than a statistical decision-making tool.

A typical analysis-to-publication workflow

Consider a simple linear model using the built-in mtcars data.

model <- lm(mpg ~ wt, data = mtcars)

summary(model)
#> 
#> Call:
#> lm(formula = mpg ~ wt, data = mtcars)
#> 
#> Residuals:
#>     Min      1Q  Median      3Q     Max 
#> -4.5432 -2.3647 -0.1252  1.4096  6.8727 
#> 
#> Coefficients:
#>             Estimate Std. Error t value Pr(>|t|)    
#> (Intercept)  37.2851     1.8776  19.858  < 2e-16 ***
#> wt           -5.3445     0.5591  -9.559 1.29e-10 ***
#> ---
#> Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
#> 
#> Residual standard error: 3.046 on 30 degrees of freedom
#> Multiple R-squared:  0.7528, Adjusted R-squared:  0.7446 
#> F-statistic: 91.38 on 1 and 30 DF,  p-value: 1.294e-10

The analysis produces numeric statistical results. Those values can then be extracted and formatted separately.

P-value

p_value <- summary(model)$coefficients["wt", "Pr(>|t|)"]

p_value
#> [1] 1.293959e-10
format_p(p_value)
#> [1] "p < .001"

Confidence interval

ci <- confint(model, "wt")

ci
#>        2.5 %    97.5 %
#> wt -6.486308 -4.202635
format_ci(ci[1], ci[2])
#> [1] "95% CI [-6.49, -4.20]"

Correlation

r_value <- cor(mtcars$mpg, mtcars$wt)

r_value
#> [1] -0.8676594
format_r(r_value)
#> [1] "r = -.87"

t statistic with p-value

coef_table <- summary(model)$coefficients

t_value <- coef_table["wt", "t value"]
p_value <- coef_table["wt", "Pr(>|t|)"]
df_value <- df.residual(model)

format_t(
  statistic = t_value,
  df = df_value,
  p = p_value
)
#> [1] "t(30) = -9.56, p < .001"

Effect sizes follow the same pattern: calculate the effect size using the appropriate statistical method and then pass the resulting estimate and optional confidence limits to a pubformat formatter.

For example, if an analysis has already produced:

d_value <- 0.52
d_lower <- 0.18
d_upper <- 0.86

the reporting step is:

format_d(
  d_value,
  lower = d_lower,
  upper = d_upper
)
#> [1] "d = 0.52, 95% CI [0.18, 0.86]"

The workflow is therefore:

statistical analysis
        |
        v
numeric statistical results
        |
        v
pubformat
        |
        v
publication-ready output

pubformat does not replace statistical analysis functions. Its purpose is to make the final reporting step more consistent, reproducible, and convenient.

Vectorized workflows

The formatting functions are designed to accept vectors, which makes them useful when preparing tables or formatting several results at once.

P-values

p_values <- c(0.032, 0.006, 0.0004, 0.213)

format_p(p_values)
#> [1] "p = .032" "p = .006" "p < .001" "p = .213"

Correlations

correlations <- c(0.42, -0.31, 0.18)

format_r(correlations)
#> [1] "r = .42"  "r = -.31" "r = .18"

Percentages

proportions <- c(0.24, 0.51, 0.83)

format_percent(proportions)
#> [1] "24.0%" "51.0%" "83.0%"

Test statistics

format_t(
  statistic = c(0.86, -2.14, 3.27),
  df = c(20, 48, 105.5),
  p = c(0.041, 0.038, 0.0012)
)
#> [1] "t(20) = 0.86, p = .041"    "t(48) = -2.14, p = .038"  
#> [3] "t(105.5) = 3.27, p = .001"
format_f(
  statistic = c(2.50, 4.4344),
  df1 = c(2, 1),
  df2 = c(30, 3262.9),
  p = c(0.091, 0.03530)
)
#> [1] "F(2, 30) = 2.50, p = .091"     "F(1, 3262.9) = 4.43, p = .035"

Effect sizes

format_d(
  d = c(0.52, -0.31, 1.14),
  lower = c(0.18, -0.62, 0.80),
  upper = c(0.86, 0.00, 1.48)
)
#> [1] "d = 0.52, 95% CI [0.18, 0.86]"   "d = -0.31, 95% CI [-0.62, 0.00]"
#> [3] "d = 1.14, 95% CI [0.80, 1.48]"
format_omega2(
  omega2 = c(0.08, -0.02, 0.31),
  lower = c(0.02, -0.06, 0.20),
  upper = c(0.15, 0.02, 0.42)
)
#> [1] "ω² = .08, 95% CI [.02, .15]"   "ω² = -.02, 95% CI [-.06, .02]"
#> [3] "ω² = .31, 95% CI [.20, .42]"

Missing values are preserved rather than silently converted into text.

format_eta2(
  eta2 = c(0.14, NA, 0.67),
  lower = c(0.08, 0.01, 0.55),
  upper = c(0.21, 0.10, 0.78)
)
#> [1] "η² = .14, 95% CI [.08, .21]" NA                           
#> [3] "η² = .67, 95% CI [.55, .78]"

Design philosophy

The package separates three concepts that can otherwise become conflated:

  1. Statistical calculation — performed by functions such as lm(), cor(), confint(), or functions from other statistical packages.
  2. Statistical decisions and interpretation — made using the original numeric results, the research design, and the researcher’s chosen inferential criteria.
  3. Publication formatting — performed by pubformat after the analysis is complete.

This separation allows results to be formatted consistently without changing the values on which statistical conclusions are based.

The same principle applies to effect sizes. pubformat does not automatically classify an effect as small, medium, or large. Instead, it preserves the supplied estimate and, when available, its confidence interval.

This keeps reporting reproducible while leaving substantive interpretation with the researcher.