Helper functions used throughout

documentation on the functions is interspersed through code comments

set some options

dont show messages when loading libraries

# library = function(...) suppressMessages(base::library(...))

never set strings as factors automatically (google for reason why)

options(stringsAsFactors = FALSE)

show four significant digits tops

options(digits = 4)

tend not to show scientific notation, because we’re just psychologists

options(scipen = 7)

make output a bit wider

options(width = 110)

set a seed to make analyses depending on random number generation reproducible

set.seed(1710) # if you use your significant other's birthday make sure you stay together for the sake of reproducibility

Load packages

generate the site

library(rmarkdown)

set options for chunks

library(knitr)

my formr utility package to generate e.g. the bibliography

library(formr)

pretty-printed output

library(pander)

tidyverse date times

library(lubridate)

tidyverse strings

library(stringr)

extractor functions for models

library(broom)

grammar of graphics plots

library(ggplot2)

tidyverse: transform data wide to long

library(tidyr)

tidyverse-style data wrangling. has a lot of naming conflicts, so always load last

library(dplyr)

some packages may be needed without being loaded

fool_packrat = function() {
    # needed to install formr package
    library(devtools)
    # needed to actually run rmarkdown in RStudio, but for some reason not in its dependencies
    library(formatR)
}

Spin R files

R scripts can be documented in markdown using Roxygen comments, as demonstrated here This function turns all R files (that don’t have an Rmd file of the same name and that don’t start with an underscore _) into HTML pages

spin_R_files_to_site_html = function() {
    library(knitr)
    all_Rs = c(list.files(pattern = "^[^_].+\\.R$"), ".Rprofile")
    component_Rmds = list.files(pattern = "^_.+\\.Rmd$")
    temporary_Rmds = c()
    for (i in seq_along(all_Rs)) {
        if(all_Rs[i] == ".Rprofile") {
            Rmd_file = ".Rprofile.Rmd"
        } else {
            Rmd_file = paste0(all_Rs[i], "md")
        }
        if (!file.exists(Rmd_file)) {
            next_document = length(temporary_Rmds) + 1
            temporary_Rmds[next_document] = spin(all_Rs[i], knit = FALSE, envir = new.env(), format = "Rmd")
            prepended_yaml = paste0(c("---
output:
  html_document:
    code_folding: 'show'
---

", readLines(temporary_Rmds[next_document])), collapse = "\n")
            cat(prepended_yaml, file = temporary_Rmds[next_document])
        }
    }
    components_and_scripts = c(temporary_Rmds, component_Rmds)
    for (i in seq_along(components_and_scripts)) {
        opts_chunk$set(eval = FALSE, cache = FALSE)
        # if we call render_site on the .R file directly it adds a header I don't like
        rmarkdown::render_site(components_and_scripts[i], quiet = TRUE)
    }
    opts_chunk$set(eval = TRUE, cache = TRUE)
    unlink(temporary_Rmds)
}

Output options

use pander to pretty-print objects (if possible)

opts_chunk$set(
    render = pander_handler
    )

don’t split tables, scroll horizontally

panderOptions("table.split.table", Inf)

Knitr components

summarise regression using a “knitr component”

regression_summary = function(model, indent = "##") {
    model_name = deparse(substitute(model_name))
    old_opt = options('knitr.duplicate.label')$knitr.duplicate.label
    options(knitr.duplicate.label = 'allow')
    on.exit(options(knitr.duplicate.label = old_opt))
    options = list(
        fig.path = paste0(knitr::opts_chunk$get("fig.path"), model_name, "_"),
        cache.path = paste0(knitr::opts_chunk$get("cache.path"), model_name, "_")
    )
    formr::asis_knit_child("_regression_summary.Rmd", options = options)
}
LS0tCm91dHB1dDoKICBodG1sX2RvY3VtZW50OgogICAgY29kZV9mb2xkaW5nOiAnc2hvdycKLS0tCgoKIyBIZWxwZXIgZnVuY3Rpb25zIHVzZWQgdGhyb3VnaG91dCB7LnRhYnNldCAudGFic2V0LXN0aWNreX0KZG9jdW1lbnRhdGlvbiBvbiB0aGUgZnVuY3Rpb25zIGlzIGludGVyc3BlcnNlZCB0aHJvdWdoIGNvZGUgY29tbWVudHMKCiMjIHNldCBzb21lIG9wdGlvbnMKZG9udCBzaG93IG1lc3NhZ2VzIHdoZW4gbG9hZGluZyBsaWJyYXJpZXMKCmBgYHtyIH0KIyBsaWJyYXJ5ID0gZnVuY3Rpb24oLi4uKSBzdXBwcmVzc01lc3NhZ2VzKGJhc2U6OmxpYnJhcnkoLi4uKSkKYGBgCgpuZXZlciBzZXQgc3RyaW5ncyBhcyBmYWN0b3JzIGF1dG9tYXRpY2FsbHkgKGdvb2dsZSBmb3IgcmVhc29uIHdoeSkKCmBgYHtyIH0Kb3B0aW9ucyhzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UpCmBgYAoKc2hvdyBmb3VyIHNpZ25pZmljYW50IGRpZ2l0cyB0b3BzCgpgYGB7ciB9Cm9wdGlvbnMoZGlnaXRzID0gNCkKYGBgCgp0ZW5kIG5vdCB0byBzaG93IHNjaWVudGlmaWMgbm90YXRpb24sIGJlY2F1c2Ugd2UncmUganVzdCBwc3ljaG9sb2dpc3RzCgpgYGB7ciB9Cm9wdGlvbnMoc2NpcGVuID0gNykKYGBgCgptYWtlIG91dHB1dCBhIGJpdCB3aWRlcgoKYGBge3IgfQpvcHRpb25zKHdpZHRoID0gMTEwKQpgYGAKCnNldCBhIHNlZWQgdG8gbWFrZSBhbmFseXNlcyBkZXBlbmRpbmcgb24gcmFuZG9tIG51bWJlciBnZW5lcmF0aW9uIHJlcHJvZHVjaWJsZQoKYGBge3IgfQpzZXQuc2VlZCgxNzEwKSAjIGlmIHlvdSB1c2UgeW91ciBzaWduaWZpY2FudCBvdGhlcidzIGJpcnRoZGF5IG1ha2Ugc3VyZSB5b3Ugc3RheSB0b2dldGhlciBmb3IgdGhlIHNha2Ugb2YgcmVwcm9kdWNpYmlsaXR5CmBgYAoKIyMgTG9hZCBwYWNrYWdlcwpnZW5lcmF0ZSB0aGUgc2l0ZQoKYGBge3IgfQpsaWJyYXJ5KHJtYXJrZG93bikKYGBgCgpzZXQgb3B0aW9ucyBmb3IgY2h1bmtzCgpgYGB7ciB9CmxpYnJhcnkoa25pdHIpCmBgYAoKbXkgZm9ybXIgdXRpbGl0eSBwYWNrYWdlIHRvIGdlbmVyYXRlIGUuZy4gdGhlIGJpYmxpb2dyYXBoeQoKYGBge3IgfQpsaWJyYXJ5KGZvcm1yKQpgYGAKCnByZXR0eS1wcmludGVkIG91dHB1dAoKYGBge3IgfQpsaWJyYXJ5KHBhbmRlcikKYGBgCgp0aWR5dmVyc2UgZGF0ZSB0aW1lcwoKYGBge3IgfQpsaWJyYXJ5KGx1YnJpZGF0ZSkKYGBgCgp0aWR5dmVyc2Ugc3RyaW5ncwoKYGBge3IgfQpsaWJyYXJ5KHN0cmluZ3IpCmBgYAoKZXh0cmFjdG9yIGZ1bmN0aW9ucyBmb3IgbW9kZWxzCgpgYGB7ciB9CmxpYnJhcnkoYnJvb20pCmBgYAoKZ3JhbW1hciBvZiBncmFwaGljcyBwbG90cwoKYGBge3IgfQpsaWJyYXJ5KGdncGxvdDIpCmBgYAoKdGlkeXZlcnNlOiB0cmFuc2Zvcm0gZGF0YSB3aWRlIHRvIGxvbmcKCmBgYHtyIH0KbGlicmFyeSh0aWR5cikKYGBgCgp0aWR5dmVyc2Utc3R5bGUgZGF0YSB3cmFuZ2xpbmcuIGhhcyBhIGxvdCBvZiBuYW1pbmcgY29uZmxpY3RzLCBzbyBhbHdheXMgbG9hZCBsYXN0CgpgYGB7ciB9CmxpYnJhcnkoZHBseXIpCmBgYAoKc29tZSBwYWNrYWdlcyBtYXkgYmUgbmVlZGVkIHdpdGhvdXQgYmVpbmcgbG9hZGVkCgpgYGB7ciB9CmZvb2xfcGFja3JhdCA9IGZ1bmN0aW9uKCkgewoJIyBuZWVkZWQgdG8gaW5zdGFsbCBmb3JtciBwYWNrYWdlCglsaWJyYXJ5KGRldnRvb2xzKQoJIyBuZWVkZWQgdG8gYWN0dWFsbHkgcnVuIHJtYXJrZG93biBpbiBSU3R1ZGlvLCBidXQgZm9yIHNvbWUgcmVhc29uIG5vdCBpbiBpdHMgZGVwZW5kZW5jaWVzCglsaWJyYXJ5KGZvcm1hdFIpCn0KYGBgCgojIyBTcGluIFIgZmlsZXMKUiBzY3JpcHRzIGNhbiBiZSBkb2N1bWVudGVkIGluIG1hcmtkb3duIHVzaW5nIFJveHlnZW4gY29tbWVudHMsIGFzIGRlbW9uc3RyYXRlZCBoZXJlClRoaXMgZnVuY3Rpb24gdHVybnMgYWxsIFIgZmlsZXMgKHRoYXQgZG9uJ3QgaGF2ZSBhbiBSbWQgZmlsZSBvZiB0aGUgc2FtZSBuYW1lIGFuZCB0aGF0IGRvbid0IHN0YXJ0IHdpdGggYW4gdW5kZXJzY29yZSBfKSBpbnRvIEhUTUwgcGFnZXMKCmBgYHtyIH0Kc3Bpbl9SX2ZpbGVzX3RvX3NpdGVfaHRtbCA9IGZ1bmN0aW9uKCkgewoJbGlicmFyeShrbml0cikKCWFsbF9ScyA9IGMobGlzdC5maWxlcyhwYXR0ZXJuID0gIl5bXl9dLitcXC5SJCIpLCAiLlJwcm9maWxlIikKCWNvbXBvbmVudF9SbWRzID0gbGlzdC5maWxlcyhwYXR0ZXJuID0gIl5fLitcXC5SbWQkIikKCXRlbXBvcmFyeV9SbWRzID0gYygpCglmb3IgKGkgaW4gc2VxX2Fsb25nKGFsbF9ScykpIHsKCQlpZihhbGxfUnNbaV0gPT0gIi5ScHJvZmlsZSIpIHsKCQkJUm1kX2ZpbGUgPSAiLlJwcm9maWxlLlJtZCIKCQl9IGVsc2UgewoJCQlSbWRfZmlsZSA9IHBhc3RlMChhbGxfUnNbaV0sICJtZCIpCgkJfQoJCWlmICghZmlsZS5leGlzdHMoUm1kX2ZpbGUpKSB7CgkJCW5leHRfZG9jdW1lbnQgPSBsZW5ndGgodGVtcG9yYXJ5X1JtZHMpICsgMQoJCQl0ZW1wb3JhcnlfUm1kc1tuZXh0X2RvY3VtZW50XSA9IHNwaW4oYWxsX1JzW2ldLCBrbml0ID0gRkFMU0UsIGVudmlyID0gbmV3LmVudigpLCBmb3JtYXQgPSAiUm1kIikKCQkJcHJlcGVuZGVkX3lhbWwgPSBwYXN0ZTAoYygiLS0tCm91dHB1dDoKICBodG1sX2RvY3VtZW50OgogICAgY29kZV9mb2xkaW5nOiAnc2hvdycKLS0tCgoiLCByZWFkTGluZXModGVtcG9yYXJ5X1JtZHNbbmV4dF9kb2N1bWVudF0pKSwgY29sbGFwc2UgPSAiXG4iKQoJCQljYXQocHJlcGVuZGVkX3lhbWwsIGZpbGUgPSB0ZW1wb3JhcnlfUm1kc1tuZXh0X2RvY3VtZW50XSkKCQl9Cgl9Cgljb21wb25lbnRzX2FuZF9zY3JpcHRzID0gYyh0ZW1wb3JhcnlfUm1kcywgY29tcG9uZW50X1JtZHMpCglmb3IgKGkgaW4gc2VxX2Fsb25nKGNvbXBvbmVudHNfYW5kX3NjcmlwdHMpKSB7CgkJb3B0c19jaHVuayRzZXQoZXZhbCA9IEZBTFNFLCBjYWNoZSA9IEZBTFNFKQoJCSMgaWYgd2UgY2FsbCByZW5kZXJfc2l0ZSBvbiB0aGUgLlIgZmlsZSBkaXJlY3RseSBpdCBhZGRzIGEgaGVhZGVyIEkgZG9uJ3QgbGlrZQoJCXJtYXJrZG93bjo6cmVuZGVyX3NpdGUoY29tcG9uZW50c19hbmRfc2NyaXB0c1tpXSwgcXVpZXQgPSBUUlVFKQoJfQoJb3B0c19jaHVuayRzZXQoZXZhbCA9IFRSVUUsIGNhY2hlID0gVFJVRSkKCXVubGluayh0ZW1wb3JhcnlfUm1kcykKfQpgYGAKCiMjIE91dHB1dCBvcHRpb25zCnVzZSBwYW5kZXIgdG8gcHJldHR5LXByaW50IG9iamVjdHMgKGlmIHBvc3NpYmxlKQoKYGBge3IgfQpvcHRzX2NodW5rJHNldCgKCXJlbmRlciA9IHBhbmRlcl9oYW5kbGVyCgkpCmBgYAoKZG9uJ3Qgc3BsaXQgdGFibGVzLCBzY3JvbGwgaG9yaXpvbnRhbGx5CgpgYGB7ciB9CnBhbmRlck9wdGlvbnMoInRhYmxlLnNwbGl0LnRhYmxlIiwgSW5mKQpgYGAKCiMjIEtuaXRyIGNvbXBvbmVudHMKCnN1bW1hcmlzZSByZWdyZXNzaW9uIHVzaW5nIGEgImtuaXRyIGNvbXBvbmVudCIKCmBgYHtyIH0KcmVncmVzc2lvbl9zdW1tYXJ5ID0gZnVuY3Rpb24obW9kZWwsIGluZGVudCA9ICIjIyIpIHsKCW1vZGVsX25hbWUgPSBkZXBhcnNlKHN1YnN0aXR1dGUobW9kZWxfbmFtZSkpCglvbGRfb3B0ID0gb3B0aW9ucygna25pdHIuZHVwbGljYXRlLmxhYmVsJykka25pdHIuZHVwbGljYXRlLmxhYmVsCglvcHRpb25zKGtuaXRyLmR1cGxpY2F0ZS5sYWJlbCA9ICdhbGxvdycpCglvbi5leGl0KG9wdGlvbnMoa25pdHIuZHVwbGljYXRlLmxhYmVsID0gb2xkX29wdCkpCglvcHRpb25zID0gbGlzdCgKCQlmaWcucGF0aCA9IHBhc3RlMChrbml0cjo6b3B0c19jaHVuayRnZXQoImZpZy5wYXRoIiksIG1vZGVsX25hbWUsICJfIiksCgkJY2FjaGUucGF0aCA9IHBhc3RlMChrbml0cjo6b3B0c19jaHVuayRnZXQoImNhY2hlLnBhdGgiKSwgbW9kZWxfbmFtZSwgIl8iKQoJKQoJZm9ybXI6OmFzaXNfa25pdF9jaGlsZCgiX3JlZ3Jlc3Npb25fc3VtbWFyeS5SbWQiLCBvcHRpb25zID0gb3B0aW9ucykKfQpgYGAK