knitr: Add figure notes - r

I have a figure that looks like this:
<<foo, fig.lp='', fig.cap='name', fig.subcap=c('left', 'right'),>>=
plot1
plot2
#
Now I would like to display a set of notes about this figure right below (i.e. a multiline text). Is there any convenient way to do this within the figure environment created by knitr?
As already pointed out in the comments above, there is currently no solution to my problem. I have filed a feature request.

I know this is a really late answer but here is what I ended up doing for the same type of a problem.
I defined a custom hook that would plot the image as I want it
# Custom knitr hook to add notes to the plot
knit_hooks$set(plot = function(x, options) {
paste("\n\\end{kframe}\n\\begin{figure}\n",
"\\includegraphics[width=\\maxwidth]{",
opts_knit$get("base.url"), paste(x, collapse = "."),
"}\n",
"\\textsc{Note} -- here is some car stuff with notes",
"\\caption{", options$fig.cap, "}\n",
"\n\\end{figure}\n\\begin{kframe}\n",
sep = '')
})
Here is the full .Rnw
\documentclass{article}
\usepackage[font=large,labelfont=sc]{caption}
\begin{document}
<<setup, echo=FALSE, message=FALSE, results='hide'>>=
suppressPackageStartupMessages({
library(ggplot2)
})
opts_chunk$set(echo=FALSE)
opts_chunk$set(results="hide")
#
<<foo, fig.cap='with notes', fig.height=4, fig.width=6>>=
# save a regular plotting function
regular_plot <- knit_hooks$get("plot")
# Custom knitr hook to add notes to the plot
knit_hooks$set(plot = function(x, options) {
paste("\n\\end{kframe}\n\\begin{figure}\n",
"\\includegraphics[width=\\maxwidth]{",
opts_knit$get("base.url"), paste(x, collapse = "."),
"}\n",
"\\textsc{Note} -- here is some car stuff with notes",
"\\caption{", options$fig.cap, "}\n",
"\n\\end{figure}\n\\begin{kframe}\n",
sep = '')
})
ggplot(data = mtcars) + geom_point(aes(disp,mpg))
#
<<bar, fig.cap='without notes', fig.height=4, fig.width=6>>=
# restore regular plotting function
knit_hooks$set(plot = regular_plot)
ggplot(data = mtcars) + geom_point(aes(disp,mpg))
#
\end{document}
and here is the resulting PDF:

akhmed answer was absolutely amazing!
I made an slighly modification for Rmd to get caption first and to generalize for every chunk in the document.
We have to add these lines at the beginning:
knit_hooks$set(plot = function(x, options, .notes = notes, .sources = sources) {
paste("\n\n\\begin{figure}\n",
"\\includegraphics[width=\\maxwidth]{",
opts_knit$get("base.url"), paste(x, collapse = "."),
"}\n",
"\\caption{",options$fig.cap,"}","\\label{fig:",opts_current$get("label"),"}","\\textsc{}",
"\n\\textsc{Notas} -- ",.notes,
"\n\\textsc{Fuentes} -- ", .sources,
"\n\\end{figure}\n",
sep = '')
})
Then in every chunk we only write the notes and sources of the plot
notes = "Notes to explain the plot"
sources = "Explain the sources"
Again, thanks a lot akhmed!!
Pd: I use "\\textsc{}" to generate an space among caption and notes & sources.
It would be nice to generalize this to use subcaptions with many figures in the same plot.

The solution from #akhmed was enormously helpful for me. I needed to make a few additional customizations which I pass along as an answer (it was too long for a comment).
First, I wanted a little more control over the margins for the note and found adding the minipage environment helped (\\begin{minipage} below set at 6 inches wide).
Second, I added a couple of minor formatting additions by setting a typeface size and left justifying the text (\\small and \\begin{flushleft} below).
Finally, for some figures, I wanted to use the fig.pos="h!" or figure position = "here" option of Knitr / Latex and it took me a minute to realize that this hook overwrites that chunk option so I manually added it as \\begin{figure}[h!].
Again, my thanks to #akhmed for offering this solution.
knit_hooks$set(plot = function(x, options) {
paste("\n\\end{kframe}\n\\begin{figure}[h!]\n",
"\\includegraphics[width=\\maxwidth]{",
opts_knit$get("base.url"), paste(x, collapse = "."),
"}\n",
"\\begin{minipage}{6in}\\small\\begin{flushleft}Note: Lorem ipsum \\end{flushleft}\\end{minipage}",
"\\caption{", options$fig.cap, " \\label{", options$fig.lp, opts_current$get("label"), "}}\n",
"\n\\end{figure}\n\\begin{kframe}\n",
sep = '')
})

Related

R Markdown List of Kabels output to PDF

I have a 500 x 500 dataset which I would like to be represented in kables in my PDF output. I've arbitrarily chosen 50 x 50 Kables for the output.
Each kabel cell will have an image in it^. Some of these examples will work with pure text so including the image is required in the minimum code.
In the code below, there is the setup and the 3 approaches I've taken:
the 500x500 approach which is obviously too big,
the list method which I can't get to actually render the tables,
the separate, dynamically named, objects^ approach which will render a table if called by exact name but can't get to render from a dynamic name.
The generation of the kables are in different code chunks to the output for ease of positioning in the PDF.
I have tried all these with results = 'asis' and it made no difference.
---
title: "Minimum Example"
date: "2022-11-19"
output: pdf_document
---
\section*{A Bit of Setup}
\renewcommand{\arraystretch}{1.7}
```{r setup}
library(kableExtra)
library(dplyr)
library(tidyr)
library(stringr)
library(png)
library(knitr)
# Image is displayed in each cell of the Kable, creating an example image
png(file="./ExampleImage.png")
par(bg='grey')
bob <- plot.new()
dev.off()
# Define size of data which needs to be represented in a kable.
totalSize_x <- 500
totalSize_y <- 500
```
\section*{Approach 1: Way too big to be displayed}
```{r Approach 1: The Big Boy}
TheBigBoi <- matrix('\\includegraphics[scale=1]{./ExampleImage.png}',totalSize_x, totalSize_y)
kable(TheBigBoi,
format = "latex",
escape = FALSE,
longtable = FALSE) |>
column_spec(1,border_left = T) |>
column_spec(totalSize_y,border_right = T) |>
kable_styling(latex_options=c("scale_down"))
# Gives the Errors
# Error: C stack usage 12426279 is too close to the limit
# Execution halted
```
\section*{Approach 2: A Nice List}
```{r Approach 2: A Nice List}
# Personally, I want this one to work as it's the least stupid solution.
maxKableCells <- 50 # As in that looks good for the images, I understand Kables can be bigger if they want to
TheLittleOne <- matrix('\\includegraphics[scale=1]{./ExampleImage.png}', maxKableCells, maxKableCells)
num_iterations <- (totalSize_x/maxKableCells) * (totalSize_y/maxKableCells)
myList <- vector(mode = "list", length = num_iterations)
for (i in 1:(num_iterations)){
myList[i] <- kable(TheLittleOne,
format = "latex",
escape = FALSE,
longtable = FALSE) |>
column_spec(1,border_left = T) |>
column_spec(maxKableCells,border_right = T) |>
kable_styling(latex_options=c("scale_down"))
}
```
Gonna put some text here
```{r Approach 2 Output}
myList[1] # - ! Undefined control sequence.
#l.177 ``\textbackslash begin\{table\}\n
# \textbackslash centering\n\textbacksl...
```
```{r Approach 2 Output Continued}
noquote(myList[1]) # - just prints out the latex code with extra slashes
```
\section*{Approach 3: Objectively the worst way to acheive this}
```{r Approach 3: Individual Kable Objects AKA Foolishness}
# The most stupid solution but the closest to working
maxKableCells <- 50 # As in that looks good for the images, I understand Kables can be bigger if they want to
TheLittleOne <- matrix('\\includegraphics[scale=1]{./ExampleImage.png}', maxKableCells, maxKableCells)
num_iterations <- (totalSize_x/maxKableCells) * (totalSize_y/maxKableCells)
for (i in 1:(num_iterations)){
assign(paste0('kable_', i), kable(TheLittleOne,
format = "latex",
escape = FALSE,
longtable = FALSE) |>
column_spec(1,border_left = T) |>
column_spec(maxKableCells,border_right = T) |>
kable_styling(latex_options=c("scale_down")))
}
```
Some filler text
```{r Approach 3 Output}
kable_1 # - This one works!
for (i in 1:num_iterations){
paste0('kabel_', i) #Returns nothing
print(paste0('kabel_', i)) # Prints like "kabel_1"
print(noquote(paste0('kabel_', i))) #Prints like kabel_1
noquote(paste0('kabel_', i)) # Returns nothing
}
```
^ I am aware this is a stupid thing to do, this whole thing is stupid but sometimes the tools we use are not our own choice.

Inserting r code into text that is not hardcoded in Rmarkdown file

I am creating a document with Rmarkdown. The document will have lots of different versions and all of the text for the different versions has been written in an Excel spreadsheet, which is then read into the Rmarkdown file. Inside the text, which sometimes differs between reports, there are keywords in square brackets which need to be replaced with r code. I am having trouble getting the rcode to evaluate inside the text and print out properly in the Rmarkdown output.
# Text like that in the Excel spreadsheet
report_text <- ("There are [NumberFruit] fruits in the fruitbowl. [HighestPercent]% of the fruit are [HighestPercentType].")
#Extract variables within the square brackets
variables <- str_extract_all(report_text, "\\[[A-Z,a-z]+\\]")
# Define all varaibles - the variables are the same in each report. The data in the actual report differs and is defined from a dataframe.
for (i in unlist(variables)){
if(grepl("NumberFruit", i)){
NumberFruit <- 10
} else if(grepl("HighestPercent", i)){
HighestPercent <- 56
} else if(grepl("HighestPercentType", i)){
HighestPercentType <- "apples"
} else if (length(unlist(variables)) > 3){
stop("Additional VARIABLE:",i, "has not been assigned")
}
}
Once the variables have been defined I would normally use something like below, but as the text isn't hardcoded into the Rmarkdown file this approach isn't possible.
final_text <- paste0("There are ",NumberFruit, " fruits in the fruitbowl. ",HighestPercent, "% are ",HighestPercentType, ".")
So I have tried formatting the text as per the paste option above, but this does not produce the desired output.
report_text2 <- gsub('\\[', '",',(gsub('\\]', ', "', report_text)))
#Also tried
report_text2 <- paste0(gsub('\\[', '",',(gsub('\\]', ', "', report_text))))
I then use r final text in the Rmarkdown text to create the text in the report. Both versions of the above code have the same result shown below.
Current Rmarkdown output: There are ",NumberFruit," fruits in the fruitbowl. ",HighestPercent, "% of the fruit are ",HighestPercentType, "."
Desired Rmarkdown output: There are 10 fruits in the fruitbowl. 56% of the fruit are apples.
I have googled for clues on what else to try but have not been able to find anything and am a bit stuck on where to go from here. Any advice on how to get this working would be greatly appreciated. I do not normally deal with text strings and feel like I am missing something fundamental here.
If we need to interpolate the variables created in RMD file, an option is with glue
library(glue)
library(stringr)
final_text <- glue::glue(str_replace_all(str_replace_all(report_text,
"\\[", "{"), "\\]", "}"))
Full code in RMD file
---
title: "test"
author: "akrun"
date: "16/03/2021"
output: html_document
---
```{r setup, include=FALSE}
knitr::opts_chunk$set(echo = TRUE)
```
## R Markdown
```{r test1}
report_text <- "There are [NumberFruit] fruits in the fruitbowl. [HighestPercent]% of the fruit are [HighestPercentType]."
NumberFruit <- 10
HighestPercent <- 56
HighestPercentType <- "apples"
```
```{r test2}
library(stringr)
final_text <- glue::glue(str_replace_all(str_replace_all(report_text, "\\[", "{"), "\\]", "}"))
```
`r final_text`
-output

knitr changes (1) to <ol> when rendering html?

The following content of a .Rmd file:
---
title: "Untitled"
output:
html_document: default
---
```{r cars}
mtcars$am <- sprintf("(%s)", as.character(mtcars$am))
knitr::kable(mtcars, format = "html")
```
Will show ordered lists <ol><li></li></ol> in the am column, instead of the numbers in brackets (as produced with the sprintf) after rendering to html.
Is this intended? How can I work around this and have numbers in brackets show as they are in the html output?
The output of knitr::kable seems to be fine, showing:
<td style="text-align:left;"> (1) </td>
Details:
Using knitr 1.20
RStudio Server 1.1.453
note that removing format = "html" does not resolve the issue as in the real-life context I would like to do advanced formatting with css e.g. based on the classes of the produced tables
A quick workaround solution based on Michael Harper's accepted answer may be a method like so:
replacechars <- function(x) UseMethod("replacechars")
replacechars.default <- function(x) x
replacechars.character <- function(x) {
x <- gsub("(", "&lpar;", x, fixed = TRUE)
x <- gsub(")", "&rpar;", x, fixed = TRUE)
x
}
replacechars.factor <- function(x) {
levels(x) <- replacechars(levels(x))
x
}
replacechars.data.frame <- function(x) {
dfnames <- names(x)
x <- data.frame(lapply(x, replacechars), stringsAsFactors = FALSE)
names(x) <- dfnames
x
}
Example use:
mtcars <- datasets::mtcars
# Create a character with issues
mtcars$am <- sprintf("(%s)", as.character(mtcars$am))
# Create a factor with issues
mtcars$hp <- as.factor(mtcars$hp)
levels(mtcars$hp) <- sprintf("(%s)", levels(mtcars$hp))
replacechars(mtcars)
If you don't want to remove the format="html" argument, you could try using the HTML character entities for the parentheses (&lpar and &rpar) and then add the argument escape = FALSE:
```{r cars}
mtcars$am <- sprintf("&lpar;%s&rpar;", as.character(mtcars$am))
knitr::kable(mtcars, format = "html", escape = FALSE)
```
Still not entirely sure of what is causing the error though. It seems that the specific combination of parentheses is being processed strangely by knitr.
An alternative solution is to escape the parentheses, e.g.,
mtcars$am <- sprintf("\\(%s)", as.character(mtcars$am))
Then you won't need escape = FALSE.
See https://pandoc.org/MANUAL.html#backslash-escapes in Pandoc's Manual.

Hmisc latex fuction need to remove the first line

Im using Hmisc in rmarkdown file. when I create a table this is what I do
---
output: pdf_document
---
```{r Arrests Stats, results ='asis', message = FALSE, warning = FALSE, echo = FALSE}
# render the table
options(digits=1)
library(Hmisc)
latex(head(mtcars), file="")
```
The latex output has the first row showing as below
%latex.default(cstats, title= title....
\begin{table}...
.
.
.
\end{tabular}
Notice the '%' I need to figure out to remove the first line as it shows on the PDF document when its weaved
Looks like that's hard-coded into latex.default (cat("%", deparse(sys.call()), "%\n", file = file, append = file != "", sep = "") is in the body, with no conditional surrounding it).
I think your best guess then would be to capture.output the cat-d output and strip the comment yourself.
cat(capture.output(latex(head(mtcars), file=''))[-1], sep='\n')
The capture.output catches all the stuff that latex(...) cats, the [-1] removes the first line (being the '%latex.default'), the cat prints out everything else, with newline separator.
You might define your own mylatex to do this, and be a little more clever (e.g. instead of blindly stripping the first line of the output, you could only strip it if it started with '%').
mylatex <- function (...) {
o <- capture.output(latex(...))
# this will strip /all/ line-only comments; or if you're only
# interested in stripping the first such comment you could
# adjust accordingly
o <- grep('^%', o, inv=T, value=T)
cat(o, sep='\n')
}
mylatex(head(mtcars), file='')

knitr xtable highlight and add horizontal lines for the same row,

I am using knitr and xtable to automate my reporting procedure. I want to highlight a few rows of a table and have a horizontal line right above each row highlighted. The .Rnw file I am using reads as below:
\usepackage{colortbl, xcolor}
\usepackage{longtable}
\begin{document}
<<do_table, results = "asis">>=
library(xtable)
mydf <- data.frame(id = 1:10, var1 = rnorm(10), var2 = runif(10))
print(xtable(mydf), add.to.row = list(pos = list(0,2), command = rep("\\rowcolor[gray]{0.75}",2)),hline.after=c(0,2))
#
\end{document}
This works just fine, however, the table I am working with should be a longtable, if I adjust the last line of code to
print(xtable(mydf), add.to.row = list(pos = list(0,2), command = rep("\\rowcolor[gray]{0.75}",2)),hline.after=c(0,2),tabular.environment="longtable",floating=FALSE)
the output is quite ugly, and the rows are not highlighted as expected. Anyone might know an answer to this question?
thanks,
David
Sorry, slightly offtopic, but demonstrating a markdown-only solution for highlighting cells/rows easily:
> mydf <- data.frame(id = 1:10, var1 = rnorm(10), var2 = runif(10))
> library(pander)
> emphasize.strong.rows(c(1, 3))
> pander(mydf)
---------------------------
id var1 var2
----- ---------- ----------
**1** **0.7194** **0.6199**
2 0.8094 0.1392
**3** **-1.254** **0.5308**
4 0.4505 0.8235
5 -0.3779 0.7534
6 -0.3518 0.3055
7 1.759 0.5366
8 0.9822 0.9938
9 1.549 0.3589
10 -1.077 0.5153
---------------------------
That can be converted to LaTeX or pdf directly.
You are on the right track, but I am a bit confused: do you want the selected rows highlighted by hline and rowcolor? In my experience, rowcolor alone looks better, so I will assume that in my answer below (but you could easily use both, just append the \\hline command).
As a bonus, all code below assumes you use the LaTeX booktabs package, which gives correctly weighted rules (unlike hline). To be honest, I always work with booktabs, and I couldn't bother to adjust the code to use hline -- but if you prefer hline, replace all \toprule, \midrule and \bottomrule macros with \hline.
You seem to have missed that LaTeX longtables require a special header, and we need to supply that too as an element to the command vector of the add.to.row list (this may be the reason your typeset table looks bad).
longtable.xheader <-
paste("\\caption{Set your table caption.}",
"\\label{tab:setyourlabel}\\\\ ",
"\\toprule ",
attr(xtable(mydf), "names")[1],
paste(" &", attr(xtable(mydf), "names")[2:length(attr(xtable(mydf), "names"))], collapse = ""),
"\\\\\\midrule ",
"\\endfirsthead ",
paste0("\\multicolumn{", ncol(xtable(mydf)), "}{c}{{\\tablename\\ \\thetable{} -- continued from previous page}}\\\\ "),
"\\toprule ",
attr(xtable(mydf), "names")[1],
paste("&", attr(xtable(mydf), "names")[2:length(attr(xtable(mydf), "names"))], collapse = ""),
"\\\\\\midrule ",
"\\endhead ",
"\\midrule ",
paste0("\\multicolumn{", as.character(ncol(xtable(mydf))), "}{r}{{Continued on next page}}\\\\ "),
"\\bottomrule \\endfoot ",
"\\bottomrule \\endlastfoot ",
collapse = "")
With that taken care of, go ahead and print the xtable:
print(xtable(mydf),
floating = FALSE, % since longtable never floats
hline.after = NULL, % hline off since I use booktabs
add.to.row = list(pos = list(-1,
c(0, 2),
nrow(xtable(mydf))),
command = c(longtable.xheader,
"\\rowcolor[gray]{0.75}\n",
"%")), % comments out a spurious \hline by xtable
include.rownames = FALSE, % depends on your preference
include.colnames = FALSE, % depends on your preference
type = "latex",
tabular.environment = "longtable",
% xtable tries to escape TeX special chars, can be annoying sometimes
sanitize.text.function = function(x){x},
% not all dashes are meant to be math negative sign, set according to your data
math.style.negative = FALSE)
I hope my use of booktabs in the answer did not confuse you too much.
Keep knitting!
You might have more luck posting this on a latex forum. You should note that xcolor/longtable are not compatible: http://www.ukern.de/tex/xcolor.html.

Resources