Before knitr v1.6, printing objects in R code chunks basically emulates the R console. For example, a data frame is printed like this1:
head(mtcars)
mpg cyl disp hp drat wt qsec vs am gear carb
Mazda RX4 21.0 6 160 110 3.90 2.620 16.46 0 1 4 4
Mazda RX4 Wag 21.0 6 160 110 3.90 2.875 17.02 0 1 4 4
Datsun 710 22.8 4 108 93 3.85 2.320 18.61 1 1 4 1
Hornet 4 Drive 21.4 6 258 110 3.08 3.215 19.44 1 0 3 1
Hornet Sportabout 18.7 8 360 175 3.15 3.440 17.02 0 0 3 2
Valiant 18.1 6 225 105 2.76 3.460 20.22 1 0 3 1
The text representation of the data frame above may look very
familiar with most R users, but for reporting purposes, it may not be
satisfactory – often times we want to see a table representation
instead. That is the problem that the chunk option render
and the S3 generic function knit_print()
try to solve.
After we evaluate each R expression in a code chunk, there is an
object returned. For example, 1 + 1
returns 2
.
This object is passed to the chunk option render
, which is
a function with two arguments, x
and options
,
or x
and ...
. The default value for the
render
option is knit_print
, an S3 function in
knitr:
library(knitr)
# an S3 generic function knit_print
## function (x, ...)
## {
## if (need_screenshot(x, ...)) {
## html_screenshot(x)
## }
## else {
## UseMethod("knit_print")
## }
## }
## <bytecode: 0x7f901aea0d28>
## <environment: namespace:knitr>
methods(knit_print)
## [1] knit_print.css* knit_print.data.frame*
## [3] knit_print.default* knit_print.grouped_df*
## [5] knit_print.html* knit_print.knit_asis*
## [7] knit_print.knit_asis_url* knit_print.knitr_kable*
## [9] knit_print.rowwise_df* knit_print.sass*
## [11] knit_print.shiny.tag* knit_print.shiny.tag.list*
## [13] knit_print.tbl_sql*
## see '?methods' for accessing help and source code
getS3method('knit_print', 'default') # the default method
## function (x, ..., inline = FALSE)
## {
## if (inline)
## x
## else normal_print(x)
## }
## <bytecode: 0x7f901e8ebe70>
## <environment: namespace:knitr>
normal_print
## function (x, ...)
## if (isS4(x)) methods::show(x) else print(x)
## <bytecode: 0x7f901f275650>
## <environment: namespace:evaluate>
As we can see, knit_print()
has a default
method, which is basically print()
or show()
,
depending on whether the object is an S4 object. This means it does
nothing special when printing R objects:
knit_print(1:10)
## [1] 1 2 3 4 5 6 7 8 9 10
knit_print(head(mtcars))
## mpg cyl disp hp drat wt qsec vs am gear carb
## Mazda RX4 21.0 6 160 110 3.90 2.620 16.46 0 1 4 4
## Mazda RX4 Wag 21.0 6 160 110 3.90 2.875 17.02 0 1 4 4
## Datsun 710 22.8 4 108 93 3.85 2.320 18.61 1 1 4 1
## Hornet 4 Drive 21.4 6 258 110 3.08 3.215 19.44 1 0 3 1
## Hornet Sportabout 18.7 8 360 175 3.15 3.440 17.02 0 0 3 2
## Valiant 18.1 6 225 105 2.76 3.460 20.22 1 0 3 1
S3 generic functions are extensible in the sense that we can define
custom methods for them. A method knit_print.foo()
will be
applied to the object that has the class foo
. Here is quick
example of how we can print data frames as tables:
library(knitr)
# define a method for objects of the class data.frame
= function(x, ...) {
knit_print.data.frame = paste(c('', '', kable(x)), collapse = '\n')
res asis_output(res)
}# register the method
registerS3method("knit_print", "data.frame", knit_print.data.frame)
If you define a method in a code chunk in a knitr
document, the call to registerS3method()
will be necessary
for R >= 3.5.0, because the S3 dispatch mechanism has changed since R
3.5.0. If you are developing an R package, see the section For package authors below.
We expect the print method to return a character vector, or an object
that can be coerced into a character vector. In the example above, the
kable()
function returns a character vector, which we pass
to the asis_output()
function so that later
knitr knows that this result needs no special treatment
(just write it as is), otherwise it depends on the chunk option
results
(= 'asis'
/ 'markup'
/
'hide'
) how a normal character vector should be written.
The function asis_output()
has the same effect as
results = 'asis'
, but saves us the effort to provide this
chunk option explicitly. Now we check how the printing behavior is
changed. We print a number, a character vector, a list, a data frame,
and write a character value using cat()
in the chunk
below:
1 + 1
## [1] 2
head(letters)
## [1] "a" "b" "c" "d" "e" "f"
list(a = 1, b = 9:4)
## $a
## [1] 1
##
## $b
## [1] 9 8 7 6 5 4
head(mtcars)
mpg | cyl | disp | hp | drat | wt | qsec | vs | am | gear | carb | |
---|---|---|---|---|---|---|---|---|---|---|---|
Mazda RX4 | 21.0 | 6 | 160 | 110 | 3.90 | 2.620 | 16.46 | 0 | 1 | 4 | 4 |
Mazda RX4 Wag | 21.0 | 6 | 160 | 110 | 3.90 | 2.875 | 17.02 | 0 | 1 | 4 | 4 |
Datsun 710 | 22.8 | 4 | 108 | 93 | 3.85 | 2.320 | 18.61 | 1 | 1 | 4 | 1 |
Hornet 4 Drive | 21.4 | 6 | 258 | 110 | 3.08 | 3.215 | 19.44 | 1 | 0 | 3 | 1 |
Hornet Sportabout | 18.7 | 8 | 360 | 175 | 3.15 | 3.440 | 17.02 | 0 | 0 | 3 | 2 |
Valiant | 18.1 | 6 | 225 | 105 | 2.76 | 3.460 | 20.22 | 1 | 0 | 3 | 1 |
cat('This is cool.')
## This is cool.
We see all objects except the data frame were printed “normally”2. The data
frame was printed as a real table. Note you do not have to use
kable()
to create tables – there are many other options
such as xtable. Just make sure the print method returns
a character string.
The printr package is a companion to knitr containing printing methods for some common objects like matrices and data frames. Users only need to load this package to get attractive printed results. A major factor to consider (which has been considered in printr) when defining a printing method is the output format. For example, the table syntax can be entirely different when the output is LaTeX vs when it is Markdown.
It is strongly recommended that your S3 method has a ...
argument, so that your method can safely ignore arguments that are
passed to knit_print()
but not defined in your method. At
the moment, a knit_print()
method can have two optional
arguments:
options
argument takes a list of the current chunk
options;inline
argument indicates if the method is called
in code chunks or inline R code;Depending on your application, you may optionally use these arguments. Here are some examples:
= function(x, ...) {
knit_print.classA # ignore options and inline
}= function(x, options, ...) {
knit_print.classB # use the chunk option out.height
asis_output(paste0(
'<iframe src="https://yihui.org" height="', options$out.height, '"></iframe>',
))
}= function(x, inline = FALSE, ...) {
knit_print.classC # different output according to inline=TRUE/FALSE
if (inline) {
'inline output for classC'
else {
} 'chunk output for classC'
}
}= function(x, options, inline = FALSE, ...) {
knit_print.classD # use both options and inline
}
Note that when using your (or another)
knit_print()
method inline (if it supports that),
you must not call knit_print()
on the object, but just have
it return. For example, your inline code should read
`r c("foo")`
and not
`r knit_print(c("foo"))`
. The latter inline code would
yield the methods’ result for in-chunk (not inline), because,
as set up in the above, knit_print()
methods default to
inline = FALSE
. This default gets overwritten depending on
the context in which knit_print()
is called (inline or
in-chunk), only when knit_print()
is called by
knitr (not you) via the render
option (see
below). You can, of course, always manually set the inline option
`r knit_print(c("foo"), inline = TRUE)`
, but that’s a lot
of typing.
You can skip this section if you do not care about the low-level implementation details.
render
optionAs mentioned before, the chunk option render
is a
function that defaults to knit_print()
. We can certainly
use other render functions. For example, we create a dummy function that
always says “I do not know what to print” no matter what objects it
receives:
= function(x, ...) {
dummy_print cat("I do not know what to print!")
# this function implicitly returns an invisible NULL
}
Now we use the chunk option render = dummy_print
:
1 + 1
## I do not know what to print!
head(letters)
## I do not know what to print!
list(a = 1, b = 9:4)
## I do not know what to print!
head(mtcars)
## I do not know what to print!
cat('This is cool.')
## This is cool.
Note the render
function is only applied to visible
objects. There are cases in which the objects returned are invisible,
e.g. those wrapped in invisible()
.
1 + 1
## [1] 2
invisible(1 + 1)
invisible(head(mtcars))
= 1:10 # invisibly returns 1:10 x
The print function can have a side effect of passing “metadata” about
objects to knitr, and knitr will
collect this information as it prints objects. The motivation of
collecting metadata is to store external dependencies of the objects to
be printed. Normally we print an object only to obtain a text
representation, but there are cases that can be more complicated. For
example, a ggvis graph
requires external JavaScript and CSS dependencies such as
ggvis.js
. The graph itself is basically a fragment of
JavaScript code, which will not work unless the required libraries are
loaded (in the HTML header). Therefore we need to collect the
dependencies of an object beside printing the object itself.
One way to specify the dependencies is through the meta
argument of asis_output()
. Here is a pseudo example:
# pseudo code
= function(x, ...) {
knit_print.ggvis = ggvis::print_this_object(x)
res ::asis_output(res, meta = list(
knitrggvis = list(
version = '0.1.0',
js = system.file('www', 'js', 'ggvis.js', package = 'ggvis'),
css = system.file('www', 'www', 'ggvis.css', package = 'ggvis')
)
)) }
Then when knitr prints a ggvis
object, the meta
information will be collected and stored.
After knitting is done, we can obtain a list of all the dependencies via
knit_meta()
. It is very likely that there are duplicate
entries in the list, and it is up to the package authors to clean them
up, and process the metadata list in their own way (e.g. write the
dependencies into the HTML header). We give a few more quick and dirty
examples below to see how knit_meta()
works.
Now we define a print method for foo
objects:
library(knitr)
= function(x, ...) {
knit_print.foo = paste('> **This is a `foo` object**:', x)
res asis_output(res, meta = list(
js = system.file('www', 'shared', 'shiny.js', package = 'shiny'),
css = system.file('www', 'shared', 'shiny.css', package = 'shiny')
)) }
See what happens when we print foo
objects:
= function(x) structure(x, class = 'foo')
new_foo new_foo('hello')
This is a
foo
object: hello
Check the metadata now:
str(knit_meta(clean = FALSE))
## List of 2
## $ js : chr "/Users/yihui/R/shiny/www/shared/shiny.js"
## $ css: chr ""
## - attr(*, "knit_meta_id")= chr [1:2] "unnamed-chunk-9" "unnamed-chunk-9"
Another foo
object:
new_foo('world')
This is a
foo
object: world
Similarly for bar
objects:
= function(x, ...) {
knit_print.bar asis_output(x, meta = list(head = '<script>console.log("bar!")</script>'))
}= function(x) structure(x, class = 'bar')
new_bar new_bar('> **hello** world!')
hello world!
new_bar('> hello **world**!')
hello world!
The final version of the metadata, and clean it up:
str(knit_meta())
## List of 6
## $ js : chr "/Users/yihui/R/shiny/www/shared/shiny.js"
## $ css : chr ""
## $ js : chr "/Users/yihui/R/shiny/www/shared/shiny.js"
## $ css : chr ""
## $ head: chr "<script>console.log(\"bar!\")</script>"
## $ head: chr "<script>console.log(\"bar!\")</script>"
## - attr(*, "knit_meta_id")= chr [1:6] "unnamed-chunk-9" "unnamed-chunk-9" "unnamed-chunk-11" "unnamed-chunk-11" ...
str(knit_meta()) # empty now, because clean = TRUE by default
## list()