# Rendering markdown with pander

#### Roman Tsegelskyi, Gergely Daróczi

#### 2022-03-18

`Pander` is designed to provide a minimal and easy tool for rendering `R` objects into Pandoc’s markdown. This vignette aims to introduce the `pander` package and its core pieces of functionality. It is intended to be a general overview with pointers to places with detailed information. This vignette will talk about:

- core functionality for rendering objects in `Pandoc`’s markdown with generic S3 pander method.
- functionality for capturing various information when evaluating R expressions with evals.
- report generation with Pandoc.brew.
- globally adjustable options through panderOptions/evalsOptions.

## Rendering R objects

The core functionality of `pander` is centered around rendering `R` objects into `Pandoc`’s markdown. Let’s dive in to a demo of the most common usage of `pander`:

    pander(head(iris))
    #>
    #> -------------------------------------------------------------------
    #>  Sepal.Length   Sepal.Width   Petal.Length   Petal.Width   Species
    #> -------------- ------------- -------------- ------------- ---------
    #>      5.1            3.5           1.4            0.2       setosa
    #>
    #>      4.9             3            1.4            0.2       setosa
    #>
    #>      4.7            3.2           1.3            0.2       setosa
    #>
    #>      4.6            3.1           1.5            0.2       setosa
    #>
    #>       5             3.6           1.4            0.2       setosa
    #>
    #>      5.4            3.9           1.7            0.4       setosa
    #> -------------------------------------------------------------------
    pander(head(mtcars[1:5]))
    #>
    #> --------------------------------------------------------
    #>                          mpg    cyl   disp   hp    drat
    #> ----------------------- ------ ----- ------ ----- ------
    #>        Mazda RX4          21     6    160    110   3.9
    #>
    #>      Mazda RX4 Wag        21     6    160    110   3.9
    #>
    #>       Datsun 710         22.8    4    108    93    3.85
    #>
    #>     Hornet 4 Drive       21.4    6    258    110   3.08
    #>
    #>    Hornet Sportabout     18.7    8    360    175   3.15
    #>
    #>         Valiant          18.1    6    225    105   2.76
    #> --------------------------------------------------------
    pander(tabular( (Species + 1) ~ (n=1) + Format(digits=2)*
             (Sepal.Length + Sepal.Width)*(mean + sd), data=iris ))
    #>
    #> -----------------------------------------------------------------
    #>                     Sepal.Length            Sepal.Width
    #>   Species      n         mean         sd        mean         sd
    #> ------------ ----- ---------------- ------ --------------- ------
    #>    setosa     50         5.01        0.35       3.43        0.38
    #>
    #>  versicolor   50         5.94        0.52       2.77        0.31
    #>
    #>  virginica    50         6.59        0.64       2.97        0.32
    #>
    #>     All       150        5.84        0.83       3.06        0.44
    #> -----------------------------------------------------------------

As you have probably guessed, this is achieved via the generic `pander` `S3` method. Out of the box, `pander` supports a variety of classes:

    methods(pander)
    #>  [1] pander.Arima*           pander.CrossTable*      pander.Date*
    #>  [4] pander.Glm*             pander.NULL*            pander.POSIXct*
    #>  [7] pander.POSIXlt*         pander.anova*           pander.aov*
    #> [10] pander.aovlist*         pander.call*            pander.cast_df*
    #> [13] pander.character*       pander.clogit*          pander.coxph*
    #> [16] pander.cph*             pander.data.frame*      pander.data.table*
    #> [19] pander.default*         pander.density*         pander.describe*
    #> [22] pander.ets*             pander.evals*           pander.factor*
    #> [25] pander.formula*         pander.ftable*          pander.function*
    #> [28] pander.glm*             pander.gtable*          pander.htest*
    #> [31] pander.image*           pander.irts*            pander.list*
    #> [34] pander.lm*              pander.lme*             pander.logical*
    #> [37] pander.lrm*             pander.manova*          pander.matrix*
    #> [40] pander.microbenchmark*  pander.name*            pander.nls*
    #> [43] pander.numeric*         pander.ols*             pander.orm*
    #> [46] pander.polr*            pander.prcomp*          pander.randomForest*
    #> [49] pander.rapport*         pander.rlm*             pander.sessionInfo*
    #> [52] pander.smooth.spline*   pander.stat.table*      pander.summary.aov*
    #> [55] pander.summary.aovlist* pander.summary.glm*     pander.summary.lm*
    #> [58] pander.summary.lme*     pander.summary.manova*  pander.summary.nls*
    #> [61] pander.summary.polr*    pander.summary.prcomp*  pander.summary.rms*
    #> [64] pander.summary.survreg* pander.summary.table*   pander.survdiff*
    #> [67] pander.survfit*         pander.survreg*         pander.table*
    #> [70] pander.tabular*         pander.ts*              pander.zoo*
    #> see '?methods' for accessing help and source code

If you think that pander lacks support for any other R class(es), please feel free to open a ticket suggesting a new feature or submit pull request and we will be happy to extend the package.

Under the hood, `pander` S3 methods rely on different `pandoc.*` methods, where most of functionality is implemented in `pandoc.table` which is used for rendering tables. `pandoc.table` provides functionality similar to `knitr::kable` in rendering markdown, but also adds a truly rich functionality with a variety of rendering options inherited from `pander`. For more usage/implementation details and examples, please refer to the `pandoc.table` vignette, which can be accessed by `vignette('pandoc_table')` (and is also available online).

## Evals

The `pander` package was originally developed in conjunction with rapport package, when a need arose for a function that could evaluate `R` expressions while also capturing errors and warnings. So `evals` emerged and soon some further feature requests arose, like identifying if an R expression results in a plot, etc.

But probably it’s easier to explain what `evals` can do with a simple example:

    evals('1:10')
    #> INFO [2022-03-18 09:57:20] Command run: 1:10
    #> TRACE [2022-03-18 09:57:20] Cached result
    #> DEBUG [2022-03-18 09:57:20] Returned object: class = integer, length = 10, dim = , size = 96 bytes
    #> [[1]]
    #> $src
    #> [1] "1:10"
    #>
    #> $result
    #>  [1]  1  2  3  4  5  6  7  8  9 10
    #>
    #> $output
    #> [1] " [1]  1  2  3  4  5  6  7  8  9 10"
    #>
    #> $type
    #> [1] "integer"
    #>
    #> $msg
    #> $msg$messages
    #> NULL
    #>
    #> $msg$warnings
    #> NULL
    #>
    #> $msg$errors
    #> NULL
    #>
    #>
    #> $stdout
    #> NULL
    #>
    #> attr(,"class")
    #> [1] "evals"

`evals` is aimed at collecting as much information as possible while evaluating R code. It can evaluate a character vector of R expressions, and it returns a list of information captured while running them:

- `src` holds the R expression,
- `result` contains the raw R object as-is,
- `output` represents how the R object is printed to the standard output,
- `type` is the class of the returned R object,
- `msg` is a list of possible messages captured while evaluating the R expression. Among other messages, warnings/errors will appear here.
- `stdout` contains what, if anything, was written to the standard output.

For more usage/implementation details and examples, please refer to the `evals` vignette, which can be accessed by `vignette('evals')` (also available online).

## Brew to Pandoc

The brew package, a templating framework for report generation, has not been updated since 2011, but it’s still some of R projects based on its simple design and useful literate programming features. For a quick overview, please see the following documents if you are not familiar with brew:

- slides on “Building a reporting sytem with BREW”
- learnr blogpost on brew

A brew document is a simple text file with some special tags. `Pandoc.brew` uses only two of them (as building on a personalized version of Jeff’s really great brew function):

- `<% ... %>` stands for running inline R commands as usual,
- `<%= ... %>` does pretty much the same but applies pander to the returning R object (instead of `cat` like the original brew function does). So inserting any R object into the tag would return it in Pandoc markdown format, with all possible error/warning messages, etc.

The latter tries to be smart in some ways:

- A code chunk block (`R` commands between the tags) can return any number of values at any part of the block.
- Plots and images are grabbed in the document, rendered to a png file and pander method would result in a Pandoc markdown formatted image link. This means that the image would be rendered/shown/included in the exported document.
- All warnings/messages and errors are recorded in the blocks and returned in the document as footnotes or inline messages.
- All heavy `R` commands (e.g. those taking more then 0.1 sec to evaluate) are cached so rebrewing a report would not result in a coffee break.

Besides this, the custom brew function can do more and also less compared to the original brew package. First of all, the internal caching mechanism of brew has been rewritten for benefits besides improved caching. Quick example:

    str(Pandoc.brew(text ='Pi equals to `<%= pi %>`. And here are some random data: `<%= runif(10)%>`'))
    #> Pi equals to `INFO [2022-03-18 09:57:20] Command run: pi
    #> TRACE [2022-03-18 09:57:20] Cached result
    #> DEBUG [2022-03-18 09:57:20] Returned object: class = numeric, length = 1, dim = , size = 56 bytes
    #> _3.142_`. And here are some random data: `INFO [2022-03-18 09:57:20] Command run: runif(10)
    #> TRACE [2022-03-18 09:57:20] Cached result
    #> DEBUG [2022-03-18 09:57:20] Returned object: class = numeric, length = 10, dim = , size = 176 bytes
    #> _0.39_, _0.7773_, _0.9606_, _0.4347_, _0.7125_, _0.4_, _0.3254_, _0.7571_, _0.2027_ and _0.7111_`
    #> List of 1
    #>  $ :List of 4
    #>   ..$ type  : chr "text"
    #>   ..$ text  :List of 2
    #>   .. ..$ raw : chr "Pi equals to `<%=pi%>`. And here are some random data: `<%=runif(10)%>`\n"
    #>   .. ..$ eval: chr "Pi equals to `_3.142_`. And here are some random data: `_0.39_, _0.7773_, _0.9606_, _0.4347_, _0.7125_, _0.4_, "| __truncated__
    #>   ..$ chunks:List of 2
    #>   .. ..$ raw : chr [1:2] "<%=pi%>" "<%=runif(10)%>"
    #>   .. ..$ eval: chr [1:2] "_3.142_" "_0.39_, _0.7773_, _0.9606_, _0.4347_, _0.7125_, _0.4_, _0.3254_, _0.7571_, _0.2027_ and _0.7111_"
    #>   ..$ msg   :List of 3
    #>   .. ..$ messages: NULL
    #>   .. ..$ warnings: NULL
    #>   .. ..$ errors  : NULL

The package bundles some examples for `Pandoc.brew` to let you quickly check its features. To brew these examples on your machine, run the following commands:

    Pandoc.brew(system.file('examples/minimal.brew', package='pander'))
    Pandoc.brew(system.file('examples/minimal.brew', package='pander'),
                output = tempfile(), convert = 'html')

    Pandoc.brew(system.file('examples/short-code-long-report.brew', package='pander'))
    Pandoc.brew(system.file('examples/short-code-long-report.brew', package='pander'),
                            output = tempfile(), convert = 'html')

    Pandoc.brew(system.file('examples/graphs.brew', package='pander'))
    Pandoc.brew(system.file('examples/graphs.brew', package='pander'),
                            output = tempfile(), convert = 'html')

## General Options

The package comes with a variety of globally adjustable options that have an effect on the result of your reports. A full list of options can be viewed by calling `?panderOptions` or in the online readme.

You can query and update these options with the `panderOptions` function:

    pots <- panderOptions("table.style")
    panderOptions("table.style", "simple")
    pander(mtcars[1:3, 1:4])
    #>
    #>
    #>                      mpg    cyl   disp   hp
    #> ------------------- ------ ----- ------ -----
    #>      Mazda RX4        21     6    160    110
    #>    Mazda RX4 Wag      21     6    160    110
    #>     Datsun 710       22.8    4    108    93
    pander(head(iris))
    #>
    #>
    #>  Sepal.Length   Sepal.Width   Petal.Length   Petal.Width   Species
    #> -------------- ------------- -------------- ------------- ---------
    #>      5.1            3.5           1.4            0.2       setosa
    #>      4.9             3            1.4            0.2       setosa
    #>      4.7            3.2           1.3            0.2       setosa
    #>      4.6            3.1           1.5            0.2       setosa
    #>       5             3.6           1.4            0.2       setosa
    #>      5.4            3.9           1.7            0.4       setosa
    panderOptions("table.style", "grid")
    pander(head(iris))
    #>
    #>
    #> +--------------+-------------+--------------+-------------+---------+
    #> | Sepal.Length | Sepal.Width | Petal.Length | Petal.Width | Species |
    #> +==============+=============+==============+=============+=========+
    #> |     5.1      |     3.5     |     1.4      |     0.2     | setosa  |
    #> +--------------+-------------+--------------+-------------+---------+
    #> |     4.9      |      3      |     1.4      |     0.2     | setosa  |
    #> +--------------+-------------+--------------+-------------+---------+
    #> |     4.7      |     3.2     |     1.3      |     0.2     | setosa  |
    #> +--------------+-------------+--------------+-------------+---------+
    #> |     4.6      |     3.1     |     1.5      |     0.2     | setosa  |
    #> +--------------+-------------+--------------+-------------+---------+
    #> |      5       |     3.6     |     1.4      |     0.2     | setosa  |
    #> +--------------+-------------+--------------+-------------+---------+
    #> |     5.4      |     3.9     |     1.7      |     0.4     | setosa  |
    #> +--------------+-------------+--------------+-------------+---------+
    panderOptions("table.style", pots)