From R to Overleaf: publication-ready output

library(colleyRstats)

colleyRstats reporters produce LaTeX so your analysis can flow straight into a manuscript. This vignette shows how to get that output into a LaTeX/Overleaf project with the least friction, and – just as importantly – how to make sure the generated .tex always compiles.

The two macro modes

By default the reporters emit compact macros such as \F{}{}{}, \p{}, and \m{} instead of raw math. This keeps the sentences short and gives you one central place to control how every statistic is typeset. The trade-off is that these macros must be defined in your document’s preamble, otherwise LaTeX stops with an “Undefined control sequence” error.

latex_preamble() prints (and optionally writes) the definitions so you can paste them into a preamble:

latex_preamble()
#> % colleyRstats: LaTeX commands required by the report functions
#> \newcommand{\F}[3]{$F({#1},{#2})={#3}$}
#> \newcommand{\p}{\textit{p=}}
#> \newcommand{\pminor}{\textit{p$<$}}
#> \newcommand{\padj}{\textit{p$_{adj}$=}}
#> \newcommand{\padjminor}{\textit{p$_{adj}<$}}
#> \newcommand{\m}{\textit{M=}}
#> \newcommand{\sd}{\textit{SD=}}
#> \newcommand{\df}{\textit{df=}}
#> \newcommand{\chisq}{$\chi^2$}
#> \newcommand{\rankbiserial}[1]{$r_{rb} = #1$}
#> \newcommand{\effectsize}{\textit{r=}}

For an Overleaf project it is cleaner to ship the definitions as a package. use_colleyrstats_sty(dir) writes colleyRstats.sty; upload that file and add a single \usepackage{colleyRstats} to your document. We write into tempdir() here so the vignette leaves nothing behind.

sty_path <- use_colleyrstats_sty(tempdir(), overwrite = TRUE)
#> Wrote '/tmp/RtmpQyjwDm/colleyRstats.sty'. Add \usepackage{colleyRstats} to your document.
sty_path
#> [1] "/tmp/RtmpQyjwDm/colleyRstats.sty"

If you would rather not carry a preamble at all, set options(colleyRstats.macros = FALSE). The sinks and the emit_overleaf() bundle then expand every macro to plain math via expand_latex_macros(), so the output is self-contained. You can call the expander directly to see what the compact form turns into:

cat(expand_latex_macros("A significant effect (\\F{2}{57}{4.50}, \\p{0.012})."))
#> A significant effect ($F(2, 57) = 4.50$, $p = 0.012$).

Whichever mode you use, any text you place into LaTeX – a variable name, a factor level – must be escaped so characters like _ and % do not break the build. latex_escape() handles that:

latex_escape("tlx_mental (%)")
#> [1] "tlx\\_mental (\\%)"

Tables

reportDunnTestTable() and reportArtConTable() render a full post-hoc comparison table as LaTeX. Both accept style = c("hline", "booktabs"): use "booktabs" for the cleaner rules most journals prefer. Rendering a table needs the xtable package, and computing a Dunn test from raw data additionally needs FSA, so the demo below is guarded and only runs when both are installed.

set.seed(1)
tbl_df <- data.frame(
  group = factor(rep(c("A", "B", "C"), each = 12)),
  score = c(rnorm(12, 50), rnorm(12, 54), rnorm(12, 58))
)

reportDunnTestTable(
  data = tbl_df,
  iv = "group",
  dv = "score",
  style = "booktabs"
)
#>   Kruskal-Wallis rank sum test
#> 
#> data: x and g
#> Kruskal-Wallis chi-squared = 31.1351, df = 2, p-value = 0
#> 
#> 
#>                       Dunn's Pairwise Comparison of x by g                      
#>                                      (Holm)                                     
#> 
#> Col Mean-│
#> Row Mean │          A          B
#> ─────────┼──────────────────────
#>        B │  -2.789943
#>          │     0.0105*
#>          │
#>        C │  -5.579886  -2.789943
#>          │     0.0000*    0.0053*
#> 
#> FWER = 0.05
#> Reject Ho if adjusted p ≤ FWER with stopping rule, where (unadjusted) p = Pr(|Z| ≥ |z|)
#> % latex table generated in R 4.6.1 by xtable 1.8-8 package
#> % Fri Jul  3 15:11:15 2026
#> \begin{table}[ht]
#> \centering
#> \caption{Post-hoc comparisons for independent variable \group and dependent variable \score. Positive Z-values mean that the first-named level is sig. higher than the second-named. For negative Z-values, the opposite is true. Effect size reported as rank-biserial correlation (r).} 
#> \label{tab:posthoc-group-score}
#> \begingroup\small
#> \begin{tabular}{lrll}
#>   \toprule
#> Comparison & Z & p-adjusted & r \\ 
#>   \midrule
#> A - B & -2.7899 & 0.0105 & 1.00 \\ 
#>   A - C & -5.5799 & $<$0.001 & 1.00 \\ 
#>   B - C & -2.7899 & 0.0053 & 1.00 \\ 
#>    \bottomrule
#> \end{tabular}
#> \endgroup
#> \end{table}

If FSA/xtable are not available the chunk above is skipped so the vignette still builds.

Reproducible values

The most robust way to keep numbers in your prose correct is to never type them by hand. define_result_macro() emits a \newcommand you reference in the text; re-running the R code updates the number everywhere it appears.

define_result_macro("tlx_mental_omnibus", "F(2, 57) = 4.50, p = .02")
#> \newcommand{\tlxMentalOmnibus}{F(2, 57) = 4.50, p = .02}

The label is sanitised to a valid (letters-only) command name – here \tlxMentalOmnibus – which you then use in prose as ... omnibus effect (\tlxMentalOmnibus).

Variable and factor-level names deserve the same treatment. When the reporters emit a name like \Video, that command must exist too. emit_name_macros() generates the matching \newcommand stubs so those names are never undefined:

emit_name_macros(c("Video", "DriverPosition"))
#> \newcommand{\Video}{Video}
#> \newcommand{\DriverPosition}{DriverPosition}

The one-call bundle

emit_overleaf() is the end of the pipeline: it turns a report_all() / analyze_and_report() result – or, as here, a plain named list of sentence vectors – into a folder you can drag straight into Overleaf. It writes main.tex, results.tex, one sections/*.tex per result, references.bib, colleyRstats.sty, and (when the text contains name macros) names.tex.

study <- list(results = list(
  workload = list(
    sentences = "A significant main effect of \\Video on workload (\\F{2}{57}{4.50}, \\p{0.012}).",
    plot = NULL
  ),
  trust = list(
    sentences = "No significant effect on trust (\\p{0.45}).",
    plot = NULL
  )
))

out <- emit_overleaf(study, dir = file.path(tempdir(), "paper"), overwrite = TRUE)
#> Wrote an Overleaf-ready project to '/tmp/RtmpQyjwDm/paper' (2 sections; \usepackage{colleyRstats}).
list.files(out$dir, recursive = TRUE)
#> [1] "colleyRstats.sty"      "main.tex"              "names.tex"            
#> [4] "references.bib"        "results.tex"           "sections/trust.tex"   
#> [7] "sections/workload.tex"

The generated main.tex already \usepackages the shipped colleyRstats.sty, so this project compiles as-is. To drop the preamble dependency entirely, switch to plain mode: the same call then expands the macros inline and omits the .sty.

old <- options(colleyRstats.macros = FALSE)
plain <- emit_overleaf(study, dir = file.path(tempdir(), "paper-plain"), overwrite = TRUE)
#> Wrote an Overleaf-ready project to '/tmp/RtmpQyjwDm/paper-plain' (2 sections; macros expanded inline).
options(old)

list.files(plain$dir, recursive = TRUE)
#> [1] "main.tex"              "names.tex"             "references.bib"       
#> [4] "results.tex"           "sections/trust.tex"    "sections/workload.tex"

Notice there is no colleyRstats.sty in the plain output – every statistic has been expanded to standalone math, so the folder compiles without a preamble. Either way, the result is a LaTeX project that goes from R to a compiled PDF with no manual editing of numbers.