Use hooks to format table in output

2019-06-16 04:00发布

问题:

Using knitr and R Markdown, I can produce a tabularised output from a matrix using the following command:

```{r results='asis'}
kable(head(x))
```

However, I’m searching for a way to make the kable code implicit since I don’t want to clutter the echoed code with it. Essentially, I want this:

```{r table=TRUE}
head(x)
```

… to yield a formatted tabular (rather than the normal output='markdown') output.

I actually thought this must be pretty straightforward since it’s a pretty obvious requirement, but I cannot find any way to achieve this, either via the documentation or on the web.

My approach to create an output hook fails because once the data arrives at the hook, it’s already formatted and no longer the raw data. Even when specifying results='asis', the hook obtains the output as a character string and not as a matrix. Here’s what I’ve tried:

default_output_hook <- knit_hooks$get('output')
knit_hooks$set(output = function (x, options)
    if (! is.null(options$table))
        kable(x)
    else
        default_output_hook(x, options)
)

But like I said, this fails since x is not the original matrix but rather a character string, and it doesn’t matter which value for the results option I specify.

回答1:

Lacking a better solution I’m currently re-parsing the character string representation that I receive in the hook. I’m posting it here since it kind of works. However, parsing a data frame’s string representation is never perfect. I haven’t tried the following with anything but my own data and I fully expect it to break on some common use-cases.

reparse <- function (data, comment, ...) {
    # Remove leading comments
    data <- gsub(sprintf('(^|\n)%s ', comment), '\\1', data)
    # Read into data frame
    read.table(text = data, header = TRUE, ...)
}

default_output_hook <- knit_hooks$get('output')

knit_hooks$set(output = function (x, options)
    if (is.null(options$table))
        default_output_hook(x, options)
    else {
        extra_opts <- if (is.list(options$table)) options$table else list()
        paste(kable(do.call(reparse, c(x, options$comment, extra_opts))),
              collapse = '\n')
    }
)

This will break if the R markdown comment option is set to a character sequence containing a regular expression special char (e.g. *), because R doesn’t seem to have an obvious means of escaping a regular expression.

Here’s a usage example:

```{r table=TRUE}
data.frame(A=1:3, B=4:6)
```

You can pass extra arguments to the deparse function. This is necessary e.g. when the table contains NA values because read.table by default interprets them as strings:

```{r table=list(colClasses=c('numeric', 'numeric'))}
data.frame(A=c(1, 2, NA, 3), B=c(4:6, NA))
```

Far from perfect, but at least it works (for many cases).



回答2:

Not exactly what you are looking for, but I am posting an answer here (that could not fit in a comment) as your described workflow is really similar to what my initial goal and use-case was when I started to work on my pander package. Although I really like the bunch of chunk options that are available in knitr, I wanted to have an engine that makes creating documents really easy, automatic and without any needed tweaks. I am aware of the fact that knitr hooks are really powerful, but I just wanted to set a few things in my Rprofile and let the literate programming tool its job without further trouble, that ended to be Pandoc.brew for me.

The main idea is to specify a few options (what markdown flavour are you using, what's your decimal mark, favorite colors for your charts etc), then simply write your report in a brew syntax without any chunk options, and the results of your code would be automatically transformed to markdown. Then convert that to pdf/docx/odt etc. with Pandoc.



标签: r markdown knitr