Blogging with Jekyll and R Markdown using knitr

use knitr

One way to blog using R and Jekyll is to copy and paste every code chunk, output and plot into a plain vanilla markdown file by hand. This is cumbersome, especially for plots which need to be saved as images and embedded. I tried this for my first few posts. It was clunky.

A more seamless solution is to use knitr to convert R Markdown files to Jekyll friendly markdown files. Knitr allows you to write your entire post in R Markdown utilizing all the features of traditional markdown while conveniently running your code, printing your output and displaying your charts. And it looks fresh.

Rmd ==> Jekyll markdown

Knitr is good at transforming R Markdown to HTML, PDF or even Word files. Jekyll is good at turning traditional/Jekyll markdown to HTML (your website). There might be an elegant way to shove the HTML generated by knitr into a jekyll site, but I haven’t found one. I found it much easier to convert R Markdown to its close cousin of markdown that is compatible with Jekyll. This conversion really just boils down to handling the R chunks: saving the plots as SVGs, formatting the code chunks with R syntax highlighting and printing the output for selected chunks.

A convenient feature provided in knitr is the ability to suppress code, messages, warnings, errors and output printed to the console for specific code chunks using the echo, message, warning, error and eval parameters respectively.

fuzzy middle

I tried a few approaches, but this one from chepec both worked the best and made the most sense to me. I had to make a couple adjustments to make it work for my setup, but they were small and included below. As I started generating more posts from R Markdown, I found a need to overwrite specific posts (rather than all or none). So I added a parameter overwriteOne which takes a string input and will overwrite any post that partially matches it (a simple grep ignoring case). However, my additions are trivial – all the credit goes to chepec.

how to use KnitPost

  1. Configure the directories at the top of KnitPost to match the file system of your blog. I could have included these as parameters, but I figured I wouldn’t re-architect my blog very often… so I left them hard-coded.
  2. Create an R Markdown post and save it as a .Rmd file in rmd.path. Be sure to include the proper YAML front matter for Jekyll. This tripped me up initially. I forgot to change the date from the knitr style date (“Month, day YYYY”) that auto-generates when you create a new .Rmd to a Jekyll style date (‘YYYY-MM-DD’).
  3. Run KnitPost to publish your R Markdown file.
KnitPost <- function(site.path='/pathToYourBlog/', overwriteAll=F, overwriteOne=NULL) {
  if(!'package:knitr' %in% search()) library('knitr')
  
  ## Blog-specific directories.  This will depend on how you organize your blog.
  site.path <- site.path # directory of jekyll blog (including trailing slash)
  rmd.path <- paste0(site.path, "_Rmd") # directory where your Rmd-files reside (relative to base)
  fig.dir <- "assets/Rfig/" # directory to save figures
  posts.path <- paste0(site.path, "_posts/articles/") # directory for converted markdown files
  cache.path <- paste0(site.path, "_cache") # necessary for plots
  
  render_jekyll(highlight = "pygments")
  opts_knit$set(base.url = '/', base.dir = site.path)
  opts_chunk$set(fig.path=fig.dir, fig.width=8.5, fig.height=5.25, dev='svg', cache=F, 
                 warning=F, message=F, cache.path=cache.path, tidy=F)   
  

  setwd(rmd.path) # setwd to base
  
  # some logic to help us avoid overwriting already existing md files
  files.rmd <- data.frame(rmd = list.files(path = rmd.path,
                                full.names = T,
                                pattern = "\\.Rmd$",
                                ignore.case = T,
                                recursive = F), stringsAsFactors=F)
  files.rmd$corresponding.md.file <- paste0(posts.path, "/", basename(gsub(pattern = "\\.Rmd$", replacement = ".md", x = files.rmd$rmd)))
  files.rmd$corresponding.md.exists <- file.exists(files.rmd$corresponding.md.file)
  
  ## determining which posts to overwrite from parameters overwriteOne & overwriteAll
  files.rmd$md.overwriteAll <- overwriteAll
  if(is.null(overwriteOne)==F) files.rmd$md.overwriteAll[grep(overwriteOne, files.rmd[,'rmd'], ignore.case=T)] <- T
  files.rmd$md.render <- F
  for (i in 1:dim(files.rmd)[1]) {
    if (files.rmd$corresponding.md.exists[i] == F) {
      files.rmd$md.render[i] <- T
    }
    if ((files.rmd$corresponding.md.exists[i] == T) && (files.rmd$md.overwriteAll[i] == T)) {
      files.rmd$md.render[i] <- T
    }
  }
  
  # For each Rmd file, render markdown (contingent on the flags set above)
  for (i in 1:dim(files.rmd)[1]) {
    if (files.rmd$md.render[i] == T) {
      out.file <- knit(as.character(files.rmd$rmd[i]), 
                      output = as.character(files.rmd$corresponding.md.file[i]),
                      envir = parent.frame(), 
                      quiet = T)
      message(paste0("KnitPost(): ", basename(files.rmd$rmd[i])))
    }     
  }
  
}