Mixing purrr, flextable, and RMarkdown for table outputs

I'm working on generating a document for which I need to provide a single table broken up into many smaller tables for a word document. I figured this might be a good place to use flextable and purrr. However, the output is bedeviling me, and I just don't get tables. Let's assume I'm setup for an rmarkdown document. I have the document setup for word output, and then attempt the following:

YAML:

---
title: "PURRR and tables for word"
output: word_document
---

Here's the setup code block:

#libraries
library(knitr)
library(purrr)
library(flextable)

#the table
my_tab <- tibble::tribble(
  ~`Biogenic.Habitat/Species`, ~Limiting.Driver, ~`Max,.Min,.or.Mean`, ~Units, ~Value,
                    "Clams",  "Flow Velocity",              "Min",  "m/s",   0.04,
               "Mussel Bed",          "Depth",              "Max",    "m",     10,
               "Mussel Bed",  "Flow Velocity",              "Max",  "m/s",    1.8,
               "Mussel Bed",    "Temperature",              "Max",    "C",     32,
                  "Oysters",  "Flow Velocity",              "Max",  "m/s",    2.6,
                  "Oysters",  "Flow Velocity",              "Min",  "m/s",   1.56)

#split it to a list
my_tab_split <- my_tab %>% split(.$`Biogenic.Habitat/Species`)

So far, so good.

Now, the following works in my markdown document and outputs a nice table.

regulartable(my_tab)

But, the following code just outputs either nothing, lists, or codes

#generates nothing
walk(my_tab_split, regulartable)

#generates nothing
walk(my_tab_split, ~regulartable(.x) %>% knit_print())

#generates the original list
map(my_tab_split, ~regulartable(.x))

#generates word code
map(my_tab_split, ~regulartable(.x) %>% knit_print())

Nor does the following work.

for(i in 1:length(my_tab_split)){
  regulartable(my_tab_split[[i]]) %>% knit_print()
}

Any thoughts on how to make purrr and flextable make beautiful music together in a word doc?

1 Like

I could not find a good solution, but this might get you part of the way there:

map(my_tab_split, regulartable) %>% map(htmltools_value)

I have a solution that involves advanced knitr feature.

flextable implements a knit_print.flextable method as explained in knitr vignette. You'll learn in this documentation about that render chunk option that can allow to customise rendering in rmarkdown, and that default to knitr::knit_print. In fact, it could be any function that return a character output to be printed.

If you look at flextable:::knit_print.flextable, you'll see that for html output, it uses htmltools_value and for docx output (and pandoc > 2.0), it creates a character string with pandoc docx flags. This knit_print method is applied only if the chunk returns a flextable object.

When you map the regulartable() function, the chunk return a list. So the knit_print method is not correct. This is why if you apply knit_print inside the map you are close to the result. However the result of the chunk is still a list of element resulting for individual knit_print.flextable call.

At the end, the result of the chunk is rendered to pandoc markdown so you need the correct render function in adequation with your result.

Thus, the solution : you could use a custom rendering function that knows how to deal with a list of regulartable() output and create a character string corresponding to pandoc markdown - a concatenation of the knit_print.flextable output.

For example, a function like that:

render_custom <- function(x, ...) {
  # I use imap here because I create a title with name of the list
  # I create a character string with the result of `knit_print.flextable`
  imap_chr(x, ~ paste0("**Table: ",.y,"**\n", knit_print(.x))) %>% 
    # collapse all the results
    paste(collapse = "\n") %>% 
    # knir must use this result `asis`
    asis_output()
}

This function could then be use in the render chunk option.

Full example that renders correctly on my computer:

---
title: "PURRR and tables for word"
output: word_document
---

```{r setup, include=FALSE}
knitr::opts_chunk$set(
	echo = TRUE,
	warning = FALSE,
	warnings = FALSE
)
```

```{r}
#libraries
library(knitr)
library(purrr)
library(flextable)

#the table
my_tab <- tibble::tribble(
  ~`Biogenic.Habitat/Species`, ~Limiting.Driver, ~`Max,.Min,.or.Mean`, ~Units, ~Value,
                    "Clams",  "Flow Velocity",              "Min",  "m/s",   0.04,
               "Mussel Bed",          "Depth",              "Max",    "m",     10,
               "Mussel Bed",  "Flow Velocity",              "Max",  "m/s",    1.8,
               "Mussel Bed",    "Temperature",              "Max",    "C",     32,
                  "Oysters",  "Flow Velocity",              "Max",  "m/s",    2.6,
                  "Oysters",  "Flow Velocity",              "Min",  "m/s",   1.56)

#split it to a list
my_tab_split <- my_tab %>% split(.$`Biogenic.Habitat/Species`)
```

```{r, include = FALSE}
render_custom <- function(x, ...) {
  # I use imap here because I create a title with name of the list
  # I create a character string with the result of `knit_print.flextable`
  imap_chr(x, ~ paste0("**Table: ",.y,"**\n", knit_print(.x))) %>% 
    # collapse all the results
    paste(collapse = "\n") %>% 
    # knir must use this result `asis`
    asis_output()
}
```


```{r, render=render_custom}
map(my_tab_split, ~regulartable(.x))
```

Let me know if it is clear and if it works. You can adapt render_custom to your desire.

5 Likes

As it was the occasion to test another knitr advanced feature, knit_child, here is another solution.

Intead of creating a custom rendering function, it is possible to write a child Rmd file based on the code you want - basically, it is an already existing document, a template file that you could fill or a template string that could be fill in the document.

So, there are several solution to do that

Only knitr tools: knit_expand and knit_child

  • it uses knit_expand to fill a template ( see vignette)
  • it uses knit_child in inline code to render the templated string created.
---
title: "PURRR and tables for word"
output: word_document
---

```{r setup, include=FALSE}
knitr::opts_chunk$set(
	echo = TRUE,
	warning = FALSE,
	warnings = FALSE
)
```

```{r}
#libraries
library(knitr)
library(purrr)
library(flextable)

#the table
my_tab <- tibble::tribble(
  ~`Biogenic.Habitat/Species`, ~Limiting.Driver, ~`Max,.Min,.or.Mean`, ~Units, ~Value,
                    "Clams",  "Flow Velocity",              "Min",  "m/s",   0.04,
               "Mussel Bed",          "Depth",              "Max",    "m",     10,
               "Mussel Bed",  "Flow Velocity",              "Max",  "m/s",    1.8,
               "Mussel Bed",    "Temperature",              "Max",    "C",     32,
                  "Oysters",  "Flow Velocity",              "Max",  "m/s",    2.6,
                  "Oysters",  "Flow Velocity",              "Min",  "m/s",   1.56)

#split it to a list
my_tab_split <- my_tab %>% split(.$`Biogenic.Habitat/Species`)
```

```{r, include = FALSE}
src <- map_chr(seq_along(my_tab_split), ~ {
  tab_name <- names(my_tab_split)[.x]
  knit_expand(text = c("## Table {{tab_name}}", 
                       "```{r tab-{{.x}}, echo = FALSE}", 
                       "regulartable(my_tab_split[[{{.x}}]])",
                       "```"))
})
```

`r knit_child(text = src)`

Some variants

  • knit_expand can be used with a template file instead of text directly.
  • the child rmd could be a child document (by writing the result of knit_expand to a file. That way, the child chunk option could be used to include in the main Rmd.
  • Other templating tool could also be used like glue :package: , brew or whisker instead of knit_expand

By the way, thank you for the question, it helps to find example for these tools ! :smile:

7 Likes