Summary of my knitr customizations

1,185 views
Skip to first unread message

Frank Harrell

unread,
Feb 17, 2012, 3:42:51 PM2/17/12
to knitr
With an amazing amount of assistance from Yihui I have been very
successful in customizing knitr to make good use of the listings
package and to automatically create figure environments when a
caption= parameter is specified inside <<>>=. I have slightly updated
the Sweavel LaTeX style file at http://biostat.mc.vanderbilt.edu/wiki/pub/Main/SweaveTemplate/Sweavel.sty,
and I use the following knitr hooks/customizations:

# All this goes into file setup.r
spar <- function(mar=if(!axes)
c(2.25+bot-.45*multi,1.5+left,.5+top+.25*multi,.5+rt)
else
c(3.25+bot-.45*multi,3.5+left,.5+top+.25*multi,.
5+rt),
lwd = if(multi)1 else 1.75,
mgp = if(!axes) mgp=c(.75, .1, 0) else
if(multi) c(1.5, .365, 0) else c(2.4-.4, 0.475,
0),
tcl = if(multi)-0.25 else -0.4,
bot=0, left=0, top=0, rt=0, ps=if(multi) 14 else 10,
mfrow=NULL, axes=TRUE, ...)
{
multi <- length(mfrow) > 0
par(mar=mar, lwd=lwd, mgp=mgp, tcl=tcl, ps=ps, ...)
if(multi) par(mfrow=mfrow)
}

knit_hooks$set(chunk = function(x, options) x) # do not wrap output in
kframe
## for those who read source code here: I cannot use echo=TRUE here
because
## the code \begin{lstlistings} and \end{lstlistings} will confuse the
## listings package and cause LaTeX errors

hook_lst_bf = function(x, options)
paste('\\begin{lstlisting}[basicstyle={\\bfseries}]\n', x,
'\\end{lstlisting}\n', sep = '')
## Scode and Soutput are defined in Sweavel.sty using listings

unlink('messages.txt') # Start fresh with each run
hook_log = function(x, options) cat(x, file='messages.txt',
append=TRUE)

knit_hooks$set(source = function(x, options)
paste('\\begin{Scode}\n', x, '\\end{Scode}\n', sep=''),
output = function(x, options)
paste('\\begin{Soutput}\n', x, '\\end{Soutput}\n',
sep=''),
warning = hook_log, message = hook_log, error =
hook_lst_bf)
set_header(highlight = '')

knit_hooks$set(plot=function (x, options) {
if (!options$include)
return("")
## Begin FH
cap <- options$caption
bfig <- efig <- ''
if(length(cap)) {
if(is.na(cap)) cap <- ''
lp <- options$label.prefix
lab <- paste(lp, options$label, sep='')
bfig <- sprintf('\\begin{figure}\\label{%s}\n', lab)
scap <- options$scaption
scap <- if(!length(scap) || is.na(scap)) '' else
paste('[', scap, ']', sep='')
efig <- sprintf('\\caption%s{\\smaller %s}\n\\end{figure}\n',
scap, cap)
} # End FH
rw = options$resize.width
rh = options$resize.height
resize1 = resize2 = ""
if (!is.null(rw) || !is.null(rh)) {
resize1 = sprintf("\\resizebox{%s}{%s}{", ifelse(is.null(rw),
"!", rw), ifelse(is.null(rh), "!", rh))
resize2 = "} "
}
tikz <- FALSE # FH
a = options$fig.align
fig.cur = options$fig.cur
fig.num = options$fig.num
if (is.null(fig.cur))
fig.cur = 0L
animate = options$fig.show == "animate"
if (!tikz && animate && fig.cur < fig.num)
return("")
align1 = switch(a, left = "\n\n", center = "\n\n\\centering{}",
right = "\n\n\\hfill{}", "")
align2 = switch(a, left = "\\hfill{}\n\n", center = "\n\n",
right = "\n\n", "")
hold = options$fig.show == "hold"
if (hold && fig.cur > 1L)
align1 = ""
if (hold && fig.cur > 0L && fig.cur < fig.num)
align2 = ""
size = paste(c(sprintf("width=%s", options$out.width),
sprintf("height=%s",
options$out.height)), collapse = ",")
paste(bfig, align1, resize1, if (tikz) { # FH added bfig,
sprintf("\\input{%s.tikz}", x[1])
}
else if (animate) {
aniopts = options$aniopts
aniopts = if (is.na(aniopts))
NULL
else gsub(";", ",", aniopts)
if (nzchar(size))
size = paste(size, sprintf("%s", aniopts), sep = ",")
if (nzchar(size))
size = sprintf("[%s]", size)
sprintf("\\animategraphics%s{%s}{%s}{%s}{%s}", size,
1/options$interval, sub(str_c(fig.num, "$"), "",
x[1]), 1L, fig.num)
}
else {
if (nzchar(size))
size = sprintf("[%s]", size)
sprintf("\\includegraphics%s{%s} ", size, x[1])
}, resize2, align2, efig, sep = "") # FH added efig,
})

knit_hooks$set(par=function(before, options, envir)
if(before && options$fig.show != 'none')
{
p <-
c('bty','mfrow','ps','bot','top','left','rt','lwd',
'mgp','tcl', 'axes')
pars <- opts_current$get(p)
pars <- pars[!is.na(names(pars))]
if(length(pars)) do.call('spar', pars) else spar()
})

opts_knit$set(eval.opts = c('eval', 'echo', 'caption', 'scaption',
'mfrow', 'mgp', 'tcl'))
## allows symbolic captions, mfrow, etc., short captions for table of
figures
## see http://yihui.name/knitr/options#package_options
## Allow symbolic mfrom, mgp, etc. because user may specify e.g.
c(1,2)
options(replace.assign=TRUE)

I don't put warnings in the report output but rather to a separate
file messages.txt

In knit_hooks$set(plot=function ....) see lines marked with FH to see
what I changed over what is provided with knitr.

Then in my individual documents I use:


<<echo=FALSE>>=
source('../setup.r')
@

\SweaveOpts{highlight=FALSE, fig.path=PREFIX-, fig.align=center,
fig.width=4, fig.height=3, fig.show=hold, comment=NA,
label.prefix=fig:gen-, par=TRUE}

. . .
<<rcscomp,mfrow=c(1\,2),fig.height=4,fig.width=6,caption="Restricted
cubic spline component variables for $k = 5$ and knots at $X = .05\, .
275\, .5\, .725$\, and $.95$. The left panel is a $y$--magnification
of the right panel. Fitted functions such as those in Figure \
\ref{fig:gen-rcsex} will be linear combinations of these basis
functions as long as knots are at the same locations used
here.",scaption="Restricted cubic spline component variables for 5
knots">>=
. . .
@

<<rcsex,fig.height=6,fig.width=7,bot=2,mfrow=c(2\,
2),ps=13,caption="Some typical restricted cubic spline functions for
$k = 3\, 4\, 5\, 6$. The $y$--axis is $X\\beta$. Arrows indicate
knots. These curves were derived by randomly choosing values of $\\beta
$ subject to standard deviations of fitted functions being
normalized.",scaption="Some typical restricted cubic spline
functions">>=
. . .
@

You can also stuff captions into R variables in earlier chunks then
call out the caption with caption=variable name in a later chunk.

I'm very pleased with how this is working and plan to re-do my book
using this approach.

Thanks to Yihui and others who have helped so much. If any of this
becomes built-in to knitr I'll have even less work to do next time.

Frank

Yihui Xie

unread,
Feb 18, 2012, 3:22:52 AM2/18/12
to Frank Harrell, knitr
Awesome. Thanks a lot, Frank.

I have ported Sweavel.sty to knitr; basically you can use
render_listings() in the first setup chunk, and knitr will use the
correct hooks to wrap up the output.

Your log hook is an interesting and useful application which I did not
think of before; I guess I will need to use that too. I have a comment
on the plot hook (probably no need to copy my hook), and I will do it
tomorrow.

Regards,
Yihui
--
Yihui Xie <xiey...@gmail.com>
Phone: 515-294-2465 Web: http://yihui.name
Department of Statistics, Iowa State University
2215 Snedecor Hall, Ames, IA

Yihui Xie

unread,
Feb 22, 2012, 7:50:30 PM2/22/12
to Frank Harrell, knitr
I have added three new options fig.cap (caption), fig.scap (short
caption) and fig.lp (label prefix) to knitr:
http://yihui.name/knitr/options

Note I did not hard-code \smaller in the caption. I think such tasks
should be done using LaTeX packages (e.g. caption2).

The next step is to think how to make chunk options real R function
arguments so it is easier to write captions. This does not seem to be
difficult, but it can take a while to update all the documentation and
deal with backward compatibility due to the syntax change.

Yihui Xie

unread,
Feb 25, 2012, 2:29:24 AM2/25/12
to Frank Harrell, knitr
I have implemented the new syntax for options now.

'eval.opts' has been deprecated because it does not make any sense to
the new syntax: all options will be evaluated if they are not
constants (numeric, character, logical, ...), but some options can be
postponed by the package option 'eval.after'. In your case, you can
set

opts_knit$set(eval.after = 'fig.cap')

The new syntax requires all options to be valid R code, so strings
must be quoted, and backslashes in strings must be escaped as \\ (e.g.
"\\textwidth").

To other knitr users: I'm in the process of updating all
documentations, and when I'm done, I will point you all to a page of
the comparison between new and old syntax of writing chunk options.
Good news is that I have kept maximum backward compatibility: if new
syntax fails, old syntax will be used, so there should not be horrible
transition problems.

Regards,
Yihui
--
Yihui Xie <xiey...@gmail.com>
Phone: 515-294-2465 Web: http://yihui.name
Department of Statistics, Iowa State University
2215 Snedecor Hall, Ames, IA

On Thu, Feb 23, 2012 at 4:02 PM, Frank Harrell <harr...@gmail.com> wrote:
> Thanks Yihui.  I look forward to the formals implementation so I can
> do it that way, if formals turns out to work for your plans.
>
> All the very best,
> Frank
>
>
> On Thu, Feb 23, 2012 at 3:57 PM, Yihui Xie <xiey...@gmail.com> wrote:
>> I can say this is possible, but requires a little bit rearrangement of
>> my code, so some options are evaluated before the chunk is run, and
>> others after the chunk. I do not need to know the caption value before
>> the chunk, so I can postpone its evaluation. This is better to be done
>> after formals() is done; then you do not need \Sexpr{}, instead it is
>> just fig.cap=paste("the coefficient is ", spe[1]).

Frank Harrell

unread,
Feb 25, 2012, 9:01:51 AM2/25/12
to knitr
This is wonderful!

I am having a problem getting aliases to work with the new evaluation
method:

opts_knit$set(aliases=c(h='fig.height', w='fig.width', cap='fig.cap',
scap='fig.scap'))
opts_knit$set(eval.after = c('cap','scap')) # also doesn't work if use
'fig.cap' unless invocation is with fig.cap=
<<physiol-transcan,h=6,w=6,ps=9,cap=paste("a",spe[1])>>=
...

Frank



On Feb 25, 1:29 am, Yihui Xie <xieyi...@gmail.com> wrote:
> I have implemented the new syntax for options now.
>
> 'eval.opts' has been deprecated because it does not make any sense to
> the new syntax: all options will be evaluated if they are not
> constants (numeric, character, logical, ...), but some options can be
> postponed by the package option 'eval.after'. In your case, you can
> set
>
> opts_knit$set(eval.after = 'fig.cap')
>
> The new syntax requires all options to be valid R code, so strings
> must be quoted, and backslashes in strings must be escaped as \\ (e.g.
> "\\textwidth").
>
> To other knitr users: I'm in the process of updating all
> documentations, and when I'm done, I will point you all to a page of
> the comparison between new and old syntax of writing chunk options.
> Good news is that I have kept maximum backward compatibility: if new
> syntax fails, old syntax will be used, so there should not be horrible
> transition problems.
>
> Regards,
> Yihui
> --
> Yihui Xie <xieyi...@gmail.com>
> Phone:515-294-2465Web:http://yihui.name
> Department of Statistics, Iowa State University
> 2215 Snedecor Hall, Ames, IA
>
>
>
>
>
>
>
> On Thu, Feb 23, 2012 at 4:02 PM, Frank Harrell <harre...@gmail.com> wrote:
> > Thanks Yihui.  I look forward to the formals implementation so I can
> > do it that way, if formals turns out to work for your plans.
>
> > All the very best,
> > Frank
>
> > On Thu, Feb 23, 2012 at 3:57 PM, Yihui Xie <xieyi...@gmail.com> wrote:
> >> I can say this is possible, but requires a little bit rearrangement of
> >> my code, so some options are evaluated before the chunk is run, and
> >> others after the chunk. I do not need to know the caption value before
> >> the chunk, so I can postpone its evaluation. This is better to be done
> >> after formals() is done; then you do not need \Sexpr{}, instead it is
> >> just fig.cap=paste("the coefficient is ", spe[1]).
>
> >> Regards,
> >> Yihui
> >> --
> >> Yihui Xie <xieyi...@gmail.com>
> >> Phone:515-294-2465Web:http://yihui.name

Frank Harrell

unread,
Feb 25, 2012, 9:38:25 AM2/25/12
to knitr
P.S. I am also having trouble if not using aliases:

opts_knit$set(eval.after = c('fig.cap','fig.scap'))
<<physiol-transcan,fig.cap=paste('speb','b',spe[1])>>=
. . .
spe <- round(c(spearman(heart.rate, blood.pressure),
spearman(t[,'heart.rate'], t[,'blood.pressure'])), 2)
@

|>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> |
78%
ordinary text without R code

|>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> |
89%
Error : Input to str_c should be atomic vectors

Yihui Xie

unread,
Feb 25, 2012, 1:39:44 PM2/25/12
to Frank Harrell, knitr
Sorry I did not test it thoroughly last night. Both issues should be
fixed now. Thanks!

Regards,
Yihui
--
Yihui Xie <xiey...@gmail.com>
Phone: 515-294-2465 Web: http://yihui.name


Department of Statistics, Iowa State University
2215 Snedecor Hall, Ames, IA

Frank Harrell

unread,
Feb 25, 2012, 2:23:27 PM2/25/12
to knitr
Working great now.

knitr has come at a great time for me as I'm updating a 300-page short
course handout and re-writing my book. I'm converting both to knitr.
The elegance of how knitr implements the listings package, the LaTeX
figure environment, labels, and captions is stunning. Thanks for all
your great efforts Yihui!

Soon I'll modify our Sweave publication-ready template at
http://biostat.mc.vanderbilt.edu/SweaveTemplate to list my knitr
setup. The current setup is below:

spar <- function(mar=if(!axes)
c(2.25+bot-.45*multi,2+left,.5+top+.25*multi,.5+rt)
else
c(3.25+bot-.45*multi,3.5+left,.5+top+.25*multi,.
5+rt),
lwd = if(multi)1 else 1.75,
mgp = if(!axes) mgp=c(.75, .1, 0) else
if(multi) c(1.5, .365, 0) else c(2.4-.4, 0.475,
0),
tcl = if(multi)-0.25 else -0.4,
bot=0, left=0, top=0, rt=0, ps=if(multi) 14 else 10,
mfrow=NULL, axes=TRUE, ...)
{
multi <- length(mfrow) > 0
par(mar=mar, lwd=lwd, mgp=mgp, tcl=tcl, ps=ps, ...)
if(multi) par(mfrow=mfrow)
}
render_listings()
unlink('messages.txt') # Start fresh with each run
hook_log = function(x, options) cat(x, file='messages.txt',
append=TRUE)
knit_hooks$set(warning = hook_log, message = hook_log)
knit_hooks$set(par=function(before, options, envir)
if(before && options$fig.show != 'none')
{
p <-
c('bty','mfrow','ps','bot','top','left','rt','lwd',
'mgp','tcl', 'axes')
pars <- opts_current$get(p)
pars <- pars[!is.na(names(pars))]
if(length(pars)) do.call('spar', pars) else spar()
})
opts_knit$set(aliases=c(h='fig.height', w='fig.width',
cap='fig.cap', scap='fig.scap'))
opts_knit$set(eval.after = c('fig.cap','fig.scap'))

\SweaveOpts{fig.path='chaptername-', fig.align='center', w=4, h=3,
fig.show='hold', fig.lp='fig:chaptername-', par=TRUE, tidy=FALSE}

. . .
<<physiol-
transcan,h=6,w=6,ps=9,mfrow=c(2,2),cap=paste('Transformations fitted
using \\co{transcan}. Tick marks indicate the two imputed values for
blood pressure. The lower left plot contains raw data (Spearman $\
\rho=',spe[1],'$); the lower right is a scatterplot of the
corresponding transformed values ($\\rho=',spe[2],'$). Data courtesy
of the SUPPORT study~\\cite{kna95sup}.'),scap='\\protect\\co{transcan}
transformations for two physiologic variables'>>=
...
spe <- ...
@


Frank

On Feb 25, 12:39 pm, Yihui Xie <xieyi...@gmail.com> wrote:
> Sorry I did not test it thoroughly last night. Both issues should be
> fixed now. Thanks!
>
> Regards,
> Yihui
> --
> Yihui Xie <xieyi...@gmail.com>
> Phone:515-294-2465Web:http://yihui.name
Reply all
Reply to author
Forward
0 new messages