Templating with Quarto


Bruno Rodrigues


October 28, 2022

Set up

The goal is to have a frequency table for each question in a survey. But we do not want to have to do it by hand, so we define a function to create a table, and then, using the templating capabilities of Quarto, write some code to generate valid qmarkdown code.

Start by loading the data and defining some needed variables:

survey_data <- read.csv(

Let’s take a look at the data:


The column names are actually questions, so we save those in a variable:

questions <- colnames(survey_data)

[1] "Random.question."                        
[2] "Copy.of.Random.question."                
[3] "Copy.of.Copy.of.Random.question."        
[4] "Copy.of.Copy.of.Copy.of.Random.question."

Now we define question codes:

codes <- paste0("var_", seq(1, 4))

[1] "var_1" "var_2" "var_3" "var_4"

We create a lookup table that links questions to their codes:

lookup <- bind_cols("codes" = codes, "questions" = questions)


Finally, we replace the question names in the dataset by the code:

colnames(survey_data) <- codes


Now, we define a function that creates a frequency table. This function has two arguments: dataset and var. It uses the dplyr::count() function to count each instance of the levels of var in dataset. Then it uses the knitr::kable() function. This functions takes a data frame as an argument and returns a table formatted in markdown code:

create_table <- function(dataset, var){
  dataset %>%
    count(!!var) %>%

The next function is the one that does the magic: it takes only one argument as an input, and generates valid markdown code using the knitr::knit_expand() function. Any variable between {{}} gets replaced by its value (so {{question}} gets replaced by the question that gets fetched from the lookup table defined above). Using this function, we can now loop over question codes, and what we get in return is valid markdown code that defines a section with the question as the title, and our table.

return_section <- function(var){
  a <- knitr::knit_expand(text = c("## {{question}}",   create_table(survey_data, var)),
                          question = lookup$questions[grepl(quo_name(var), lookup$codes)])
  cat(a, sep = "\n")

Our codes are strings, so to be able to use them inside of dplyr::count() we need to define them as bare string, or symbols. This can be done using the rlang::sym() function. If this is confusing, try running count(mtcars, "am") and you will see that it will not return what you want (compare to count(mtcars, am)). This is also why we needed rlang::quo_name() in the function above, to convert the symbol back to a string, which is what grepl() requires:

sym_codes <- map(codes, sym)

Finally, we can create the sections. The line below uses purrr::walk(), which is equivalent to purrr::map(), the difference being that we use purrr::walk() when we are interested in the side effects of a function:

walk(sym_codes, return_section)


var_1 n
no 40
yes 44


var_2 n
no 52
yes 32


var_3 n
no 46
yes 47


var_4 n
no 48
yes 42