Controlling long outputs with knitr hooks
Introduction
This blog is written in a combination of Markdown and R Markdown (using the R package blogdown).
As such, some R Markdown posts contain code chunks who generate very lengthy and verbose output.
An example is this one, which generates a sequence of strings separated by newlines:
n <- 10
m <- 10
matrix(rnorm(m*n), m, n)
## [,1] [,2] [,3] [,4] [,5]
## [1,] -0.56047565 1.2240818 -1.0678237 0.42646422 -0.69470698
## [2,] -0.23017749 0.3598138 -0.2179749 -0.29507148 -0.20791728
## [3,] 1.55870831 0.4007715 -1.0260044 0.89512566 -1.26539635
## [4,] 0.07050839 0.1106827 -0.7288912 0.87813349 2.16895597
## [5,] 0.12928774 -0.5558411 -0.6250393 0.82158108 1.20796200
## [6,] 1.71506499 1.7869131 -1.6866933 0.68864025 -1.12310858
## [7,] 0.46091621 0.4978505 0.8377870 0.55391765 -0.40288484
## [8,] -1.26506123 -1.9666172 0.1533731 -0.06191171 -0.46665535
## [9,] -0.68685285 0.7013559 -1.1381369 -0.30596266 0.77996512
## [10,] -0.44566197 -0.4727914 1.2538149 -0.38047100 -0.08336907
## [,6] [,7] [,8] [,9] [,10]
## [1,] 0.25331851 0.37963948 -0.4910312 0.005764186 0.9935039
## [2,] -0.02854676 -0.50232345 -2.3091689 0.385280401 0.5483970
## [3,] -0.04287046 -0.33320738 1.0057385 -0.370660032 0.2387317
## [4,] 1.36860228 -1.01857538 -0.7092008 0.644376549 -0.6279061
## [5,] -0.22577099 -1.07179123 -0.6880086 -0.220486562 1.3606524
## [6,] 1.51647060 0.30352864 1.0255714 0.331781964 -0.6002596
## [7,] -1.54875280 0.44820978 -0.2847730 1.096839013 2.1873330
## [8,] 0.58461375 0.05300423 -1.2207177 0.435181491 1.5326106
## [9,] 0.12385424 0.92226747 0.1813035 -0.325931586 -0.2357004
## [10,] 0.21594157 2.05008469 -0.1388914 1.148807618 -1.0264209
and this one, which generates a single long string:
long_string <- paste(rep(letters, 30), collapse = '')
long_string
## [1] "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz"
Enter: knitr hooks
As detailed by the knitr documentation, knitr
provides a set of functions to transparently modify a chunk, without changing the inner contents, but specifying chunk options. Such functions are called hooks.
Knitr hooks can be of two types: chunk hooks, output hooks and option hooks.
To our purpose, we are interested in output hooks.
These are functions which, in general, take two parameters: the chunk output, and the chunk options (e.g. fig.width
).
From the documentation, an output hook can deal with 8 different types of outputs:
- source: the source code
- output: ordinary R output (i.e., what would have been printed in an R terminal) except warnings, messages and errors
- warning: warnings from warning()
- message: messages from message()
- error: errors from stop() (applies to errors in both code chunks and inline R code)
- plot: graphics output
- inline: output of inline R code
- chunk: all the output of a chunk (i.e., those produced by the previous hooks)
- document: the output of the whole document (is base::identity by default)
Again, we are interested in output hooks for the output, as we want to preserve source code, errors, warnings and messages.
Line truncation
This is directly taken from knitr examples: 052-suppress-output.Rmd
First, e.g. in the setup chunk, we retrieve the default output hook:
# the default output hook
hook_output_default <- knitr::knit_hooks$get('output')
We define a function which truncates the output up to n
lines. x
is the chunk output, as a character vector. If n
is missing, the output passes through, unaffected.
truncate_to_lines <- function(x, n) {
if (!is.null(n)) {
x = unlist(stringr::str_split(x, '\n'))
if (length(x) > n) {
# truncate the output
x = c(head(x, n), '...\n')
}
x = paste(x, collapse = '\n') # paste first n lines together
}
x
}
Finally, we can overwrite the default knitr hook, and truncate if option max.lines
is specified:
knitr::knit_hooks$set(output = function(x, options) {
max.lines <- options$max.lines
x <- truncate_to_lines(x, max.lines)
hook_output_default(x, options)
})
Let’s see the first chunk, now truncated to 3 lines. We set max.lines=3
in the chunk options:
```{r, max.lines = 3}
n <- 10
m <- 10
matrix(rnorm(m*n), m, n)
```
and here is the output:
n <- 10
m <- 10
matrix(rnorm(m*n), m, n)
## [,1] [,2] [,3] [,4] [,5]
## [1,] -0.71040656 -0.57534696 0.11764660 1.44455086 0.7017843
## [2,] 0.25688371 0.60796432 -0.94747461 0.45150405 -0.2621975
...
Character truncation
This time we need to define a function which truncates up to a specified amount of characters, if n_chars
is specified:
truncate_to_chars <- function(x, n_chars) {
if (!is.null(n_chars)) {
if (min(nchar(x), n_chars) < nchar(x)) {
x <- substr(x, 1, min(nchar(x), n_chars))
x <- paste(x, ' ...\n')
}
}
x
}
Then, we can add the truncation function to the output hook:
knitr::knit_hooks$set(output = function(x, options) {
max.lines <- options$max.lines
x <- truncate_to_lines(x, max.lines)
# Here is our new code
max.chars <- options$max.chars
x <- truncate_to_chars(x, max.chars)
hook_output_default(x, options)
})
And here is the second chunk, truncated to 20 chars (setting max.chars = 20
in the chunk options):
long_string <- paste(rep(letters, 30), collapse = '')
long_string
## [1] "abcdefghijkl ...
Notice that the code is not part of the character count (it is an output hook on the output!), but the prompt and the whitespaces are (up to "..."
).
And, of course, both truncations can be combined.
E.g., let’s see the first chunk to 3 lines and 160 characters:
n <- 10
m <- 10
matrix(rnorm(m*n), m, n)
## [,1] [,2] [,3] [,4] [,5]
## [1,] 2.19881035 0.1192452 -0.57397348 1.95529397 -0.7886220
## [2,] 1.31241298 0.24 ...
Thanks for reading!