Passing and R6 Class to Quarto

Quarto has many improvements over RMarkdown, plus a couple of limitations, one being that you cannot pass R objects as parameters to a Quarto document in the way the RMarkdown would accept them. Quarto receives parameters serialized as yaml, so only text, numeric, boolean (received as TRUE="yes" and FALSE="no") and lists can be passed. There are instances when you may wish to pass a complex object, for example an R6 class instance, as a paramater for Quarto. One way to do this is to first serialize the object and then pass it as a serialized JSON object.

Preparing data

The code below simply creates a data frame and a list. The data frame contains 1000 rows and 1000 columns and the list contains 1000 members.

1n_row <- 1000
2n_col <- 1000
3
4d <- as.data.frame(matrix(rnorm(n_row * n_col), n_row, n_col))
5l <- as.list(rnorm(n_row))

In order to explore passing data vs. passing an R6 class we'll create a simple R6 class:

 1R <- R6::R6Class("R",
 2  public = list(
 3    data = NULL,
 4    list = NULL,
 5    initialize = function(data = NULL, list = NULL) {
 6      self$data = data
 7      self$list = list
 8    }
 9  )
10)

This class simply holds the data frame and list and returns them. We can create an instance of the class with the data defined above as follows:

1r <- R$new(data = d, list = l)

Quarto Doc 1 - sending data frame and list as parameters

The first Quarto document, quarto_01.qmd accepts two parameters - a data frame and a list.

 1---
 2title: "data test 01"
 3format: html
 4engine: knitr
 5params:
 6  mydata: NULL
 7  mylist: NULL
 8---
 9
10Imported two items: tibble and list.  
11Number of rows in tibble: `r nrow(params$mydata)`  
12Number of items in list: `r length(params$mylist)`

Rendering the Quarto document produces the following output. It should be noted that when rendered, the data frame is passed as a list since Quarto does not have a concept of data frames and therefore the term nrow(params$mydata) fails to report the number of rows.

1quarto::quarto_render("quarto_01.qmd", execute_params = list(mydata = d, mylist = l))

In order to pass the data frame we'll need to serialize it to a JSON representation and unserialize it once passed. We can do this using {jsonlite} as follows:

 1---
 2title: "data test 01A"
 3format: html
 4engine: knitr
 5params:
 6  mydata: NULL
 7  mylist: NULL
 8---
 9
10```{r}
11#| echo: false
12df <- jsonlite::fromJSON(params$mydata)
13```
14
15Imported two items: data frame and list.  
16Number of rows in data frame: `r nrow(df)`  
17Number of items in list: `r length(params$mylist)`

Now when rendered, we get the following output:

1quarto::quarto_render("quarto_01A.qmd", execute_params = list(mydata = jsonlite::toJSON(d), mylist = l))

Quarto Doc 2 - sending the R6 class instance as a parameter

The final Quarto document, quarto_02.qmd accepts just a single parameter for the R6 class instance. In this case we serialize the R6 class and then pass it using jsonlite::JSONserialize(). When received by the Quarto document we simply reverse the procedure. The class itself has to be serialized first as using jsonlite::JSONserialize() on the class instance, r results in passing the class structure as a parameter and not the class instance.

 1---
 2title: "data test 02"
 3format: html
 4engine: knitr
 5params:
 6  my_serialized_class: NULL
 7---
 8
 9```{r}
10#| echo: false
11r_class <- unserialize(jsonlite::unserializeJSON(params$my_serialized_class))
12```
13
14Imported one items: R6 class.  
15Number of rows in data frame: `r nrow(r_class$data)`  
16Number of items in list: `r length(r_class$list)`

Rendering the document leads to the expected output.

1param_r6 <- jsonlite::serializeJSON(serialize(r, connection = NULL))
2quarto::quarto_render("quarto_02.qmd", execute_params = list(my_serialized_class = param_r6))

Performance

Times to render (including serializing and deserializing) are shown below. Surprisingly passing the R6 class was faster than than the data frame.

description time (secs)
data frame and list, no serialization 9.0
data frame and list, data frame serialized 13.9
R6 class, serialized 5.4

The effect is even more pronounced as the quantity of data increases. When n_row is increased 10-fold to 10000, the times become:

description time (secs)
data frame and list, no serialization 43.5
data frame and list, data frame serialized 80.3
R6 class, serialized 14.3

Conclusion

Complex objects, such as R6 classes, can be used in parameterized Quarto documents if properly serialized. Passing a serialized R6 class is more efficient than passing an equivalent data frame / list combination.