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.