While the report generation functionality of `pander` and `knitr` do overlap, we feel that the most powerful way to use `R/knitr/pander` for report generation is to utilize them together. This short vignette aims to explain how to embed `pander` output in reports generated by `knitr`. If you are not aware of `knitr`, be sure to check out the project's homepage for extensive documentation and examples.

One of `knitr`'s most useful features is the ability to convert tables to output format on the fly. For example:

    head(iris)
    #>   Sepal.Length Sepal.Width Petal.Length Petal.Width Species
    #> 1          5.1         3.5          1.4         0.2  setosa
    #> 2          4.9         3.0          1.4         0.2  setosa
    #> 3          4.7         3.2          1.3         0.2  setosa
    #> 4          4.6         3.1          1.5         0.2  setosa
    #> 5          5.0         3.6          1.4         0.2  setosa
    #> 6          5.4         3.9          1.7         0.4  setosa
    knitr::kable(head(iris))

Sepal.Length | Sepal.Width | Petal.Length | Petal.Width | Species
--- | --- | --- | --- | ---
5.1 | 3.5 | 1.4 | 0.2 | setosa
4.9 | 3.0 | 1.4 | 0.2 | setosa
4.7 | 3.2 | 1.3 | 0.2 | setosa
4.6 | 3.1 | 1.5 | 0.2 | setosa
5.0 | 3.6 | 1.4 | 0.2 | setosa
5.4 | 3.9 | 1.7 | 0.4 | setosa

However, `kable` table generator is simple by design, and does not capture all the variety of classes that `R` has to offer. For example, `CrossTable` and `tabular` are not supported:

    library(descr, quietly = TRUE)
    ct <- CrossTable(mtcars$gear, mtcars$cyl)
    #> Warning in chisq.test(tab, correct = FALSE, ...): Chi-squared approximation
    #> may be incorrect
    knitr::kable(ct)
    #> Error in as.data.frame.default(x): cannot coerce class ""CrossTable"" to a data.frame
    library(tables, quietly = TRUE)
    #>
    #> Attaching package: 'Hmisc'
    #> The following objects are masked from 'package:base':
    #>
    #>     format.pval, round.POSIXt, trunc.POSIXt, units
    tab <- tabular( (Species + 1) ~ (n=1) + Format(digits=2)*
             (Sepal.Length + Sepal.Width)*(mean + sd), data=iris )
    knitr::kable(tab)
    #> Error in dimnames(x) <- dn: length of 'dimnames' [2] not equal to array extent

This is where `pander` comes in handy, as `pander` supports rendering for many popular classes:

    methods(pander)
    #>  [1] pander.Arima*           pander.CrossTable*
    #>  [3] pander.Date*            pander.Glm*
    #>  [5] pander.NULL*            pander.POSIXct*
    #>  [7] pander.POSIXlt*         pander.anova*
    #>  [9] pander.aov*             pander.aovlist*
    #> [11] pander.call*            pander.cast_df*
    #> [13] pander.character*       pander.clogit*
    #> [15] pander.coxph*           pander.cph*
    #> [17] pander.data.frame*      pander.data.table*
    #> [19] pander.default*         pander.density*
    #> [21] pander.describe*        pander.ets*
    #> [23] pander.evals*           pander.factor*
    #> [25] pander.formula*         pander.ftable*
    #> [27] pander.function*        pander.glm*
    #> [29] pander.gtable*          pander.htest*
    #> [31] pander.image*           pander.irts*
    #> [33] pander.list*            pander.lm*
    #> [35] pander.lme*             pander.logical*
    #> [37] pander.lrm*             pander.manova*
    #> [39] pander.matrix*          pander.microbenchmark*
    #> [41] pander.name*            pander.nls*
    #> [43] pander.numeric*         pander.ols*
    #> [45] pander.orm*             pander.polr*
    #> [47] pander.prcomp*          pander.randomForest*
    #> [49] pander.rapport*         pander.rlm*
    #> [51] pander.sessionInfo*     pander.smooth.spline*
    #> [53] pander.stat.table*      pander.summary.aov*
    #> [55] pander.summary.aovlist* pander.summary.glm*
    #> [57] pander.summary.lm*      pander.summary.lme*
    #> [59] pander.summary.manova*  pander.summary.nls*
    #> [61] pander.summary.polr*    pander.summary.prcomp*
    #> [63] pander.summary.rms*     pander.summary.survreg*
    #> [65] pander.summary.table*   pander.survdiff*
    #> [67] pander.survfit*         pander.survreg*
    #> [69] pander.table*           pander.tabular*
    #> [71] pander.ts*              pander.zoo*
    #> see '?methods' for accessing help and source code

Also, `pander` is integrated with `knitr` by default. `pander` simply identifies if `knitr` is running in the background, and if so, it uses `capture.output` to return the resulting string as an `knit_asis` object, meaning that you do not need to specify the `results='asis'` option in your knitr chunk:

    library(descr, quietly = TRUE)
    pander(CrossTable(mtcars$gear, mtcars$cyl))
    #> Warning in chisq.test(tab, correct = FALSE, ...): Chi-squared approximation
    #> may be incorrect

\ mtcars$cyl\ \ \ \
mtcars$gear 4 6 8 Total

3\ \ \ \ \
N\ 1\ 2\ 12\ 15\
Chi-square\ 3.3502\ 0.5003\ 4.5054\ \
Row(%)\ 6.6667%\ 13.3333%\ 80.0000%\ 46.8750%\
Column(%)\ 9.0909%\ 28.5714%\ 85.7143%\ \
Total(%) 3.125% 6.250% 37.500%

4\ \ \ \ \
N\ 8\ 4\ 0\ 12\
Chi-square\ 3.6402\ 0.7202\ 5.2500\ \
Row(%)\ 66.6667%\ 33.3333%\ 0.0000%\ 37.5000%\
Column(%)\ 72.7273%\ 57.1429%\ 0.0000%\ \
Total(%) 25.000% 12.500% 0.000%

5\ \ \ \ \
N\ 2\ 1\ 2\ 5\
Chi-square\ 0.0460\ 0.0080\ 0.0161\ \
Row(%)\ 40.0000%\ 20.0000%\ 40.0000%\ 15.6250%\
Column(%)\ 18.1818%\ 14.2857%\ 14.2857%\ \
Total(%) 6.250% 3.125% 6.250%

    Total\         11\            7\           14\            32\
                  34.375%       21.875%       43.75%

    library(tables, quietly = TRUE)
    tab <- tabular( (Species + 1) ~ (n=1) + Format(digits=2)*
             (Sepal.Length + Sepal.Width)*(mean + sd), data=iris )
    pander(tab)

      \         \     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

In a nutshell, this is achieved by modification that whenever you call `pander` inside of a `knitr` document, instead of returning the markdown text to the standard output (the default behavior), `pander` returns a `knit_asis` class object, which renders correctly in the resulting document — without the double comment chars, thus properly rendering the tables in HTML, PDF, or other document formats.

If you don't want the results of `pander` to be converted automatically, just set `knitr.auto.asis` to `FALSE` using `panderOptions`:

    panderOptions('knitr.auto.asis', FALSE)
    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('knitr.auto.asis', TRUE)

#### Rendering markdown inside loop/vectorized function

One frequenly asked question is how to use `pander` with `knitr` in a loop or vectorized function. For example, we have 3 tables that we want to render using `lapply`:

    dfs <- list(mtcars[1:3, 1:4], mtcars[4:6, 1:4], mtcars[7:9, 1:4])
    lapply(dfs, pander)
    #> [[1]]
    #> [1] "\n---------------------------------------------\n      &nbsp;         mpg    cyl   disp   hp  \n------------------- ------ ----- ------ -----\n   **Mazda RX4**      21     6    160    110 \n\n **Mazda RX4 Wag**    21     6    160    110 \n\n  **Datsun 710**     22.8    4    108    93  \n---------------------------------------------\n\n"
    #> attr(,"class")
    #> [1] "knit_asis"
    #> attr(,"knit_cacheable")
    #> [1] NA
    #>
    #> [[2]]
    #> [1] "\n-------------------------------------------------\n        &nbsp;           mpg    cyl   disp   hp  \n----------------------- ------ ----- ------ -----\n  **Hornet 4 Drive**     21.4    6    258    110 \n\n **Hornet Sportabout**   18.7    8    360    175 \n\n      **Valiant**        18.1    6    225    105 \n-------------------------------------------------\n\n"
    #> attr(,"class")
    #> [1] "knit_asis"
    #> attr(,"knit_cacheable")
    #> [1] NA
    #>
    #> [[3]]
    #> [1] "\n-------------------------------------------\n     &nbsp;       mpg    cyl   disp    hp  \n---------------- ------ ----- ------- -----\n **Duster 360**   14.3    8     360    245 \n\n **Merc 240D**    24.4    4    146.7   62  \n\n  **Merc 230**    22.8    4    140.8   95  \n-------------------------------------------\n\n"
    #> attr(,"class")
    #> [1] "knit_asis"
    #> attr(,"knit_cacheable")
    #> [1] NA

As you can see, this doesn't work correctly because `pander` tries to return a `knit_asis` class object when run inside `knitr`, but for loops/vectorized functions this results in incorrect output. The recommended way to solve this is to disable this behavior by setting `knitr.auto.asis` to `FALSE` using `panderOptions`. However, we also need to tell `knitr` to convert the table on the fly by specifying `results='asis'` in the chunk options:

    panderOptions('knitr.auto.asis', FALSE)
    dfs <- list(mtcars[1:3, 1:4], mtcars[4:6, 1:4], mtcars[7:9, 1:4])
    invisible(lapply(dfs, pander))

      &nbsp;         mpg    cyl   disp   hp

Mazda RX4 21 6 160 110

Mazda RX4 Wag 21 6 160 110

## Datsun 710 22.8 4 108 93

        &nbsp;           mpg    cyl   disp   hp

Hornet 4 Drive 21.4 6 258 110

Hornet Sportabout 18.7 8 360 175

      **Valiant**        18.1    6    225    105

     &nbsp;       mpg    cyl   disp    hp

Duster 360 14.3 8 360 245

Merc 240D 24.4 4 146.7 62

## Merc 230 22.8 4 140.8 95