Knitr & Jekyll: A Stats Blog Pipeline
Combining Knitr and Jekyll took some effort. This post describes how to get it all working as part of a statistical blogging pipeline.
Introduction
If you’re reading this, you know that I write a blog about statistics for Inferentialist Consulting. You may also be aware of the knitr package, useful for reproducible research. knitr is a dramatic improvement over its predecessor, Sweave, and the easiest way I’ve found to interweave text and R output. However, until now, I’ve only used knitr in a latex workflow. Now that I’m authoring a blog—I use jekyll—I’d love a similarly easy way to transform R Markdown to jekyll Markdown.
The good news is that most of the plumbing already exists. RStudio has some functionality that exposes R Markdown in a jekyll format. However, the context requires running jekyll as a temporary background process. The missing piece is the ability to export transformed R Markdown into jekyll Markdown.
Luckily, it doesn’t take too much R magic to extend knitr in exactly the way we want. Here’s the code to do the job:
input_file = "sample.Rmd"
# Remove the file extension
base_name = gsub("(^.*)\\.(.*$)", "\\1", input_file, fixed=FALSE)
# Define the location of the blog; here, a soft link
blog_dir = "./blog"
posts_dir = paste(blog_dir, "_posts", sep="/")
# Define the output file,
output_basename = paste(base_name, "md", sep=".")
output_file = paste(posts_dir, output_basename, sep="/")
# ...and the output path for the figures
fig.path = paste(blog_dir, "assets", base_name, "", sep="/")
# Set up the hooks for a RStudio jekyll
knitr::render_jekyll()
# Maybe you want some nice defaults for the figures / images
knitr::opts_chunk$set(
fig.width=6,
fig.height=4.5,
fig.path=fig.path,
warning=FALSE,
message=FALSE
)
# Tweak the upload.fun hook so that the blog_dir is removed
# when the Markdown link is written out
knitr::opts_knit$set(
upload.fun = function(x){
gsub(blog_dir, "", x)
## _OR_ gsub(blog_dir, "http://blog.inferentialist.com", x)
}
)
# The default table attributes confuses jekyll, but this works
options(xtable.html.table.attributes = "class='foobar'")
# Finally, let knitr do its thing.
knit(input_file, output = output_file)
Extras
Verbatim Rmarkdown
The following extension adds a verbatim flag which is useful for showcasing the actual Rmd code.
I borrowed this idea from Ramnath Vaidyanathan with slight modifications.
source_orig = knitr::knit_hooks$get()$source
knitr::knit_hooks$set(
source = function(x, options){
if (!is.null(options$verbatim) && options$verbatim){
opts = gsub(",\\s*verbatim\\s*=\\s*TRUE\\s*", "", options$params.src)
opts = gsub(",\\s*eval\\s*=\\s*FALSE\\s*", "", opts)
bef = sprintf('\n\n ```{r %s}\n', opts, "\n")
stringr::str_c(
bef,
knitr:::indent_block(paste(x, collapse = '\n'), " "),
"\n ```\n\n"
)
} else {
source_orig(x, options)
}
})
Debugging Knitr
Finally, it can be useful to add debug wrappers around the hooks. For example, this lets you know which hooks are actually called and with what parameters.
fdbg = list()
for(item in names(knitr::knit_hooks$get())){
fdbg[[item]] = knit_hooks$get(item)
## Create a new environment so that the function is invoked
## with the correct value of item
e = new.env()
e$item = item
fn = function(...) {
cat("----------", item, "\n")
flush.console()
browser()
fdbg[[item]](...)
}
environment(fn) = e
## The setter doesn't quite work the way you'd expect, so
## use reflection.
cmd = paste("knit_hooks$set(",item," = fn)", sep="")
eval(parse(text=cmd))
}