I am wanting to get more into using R markdown to perform analyses and generate output. Maybe I'm missing something simple, but I just want to be able to set the number of decimal places to show either 2 or 3 digits, depending on the output (e.g. t-statistic vs p-value).
I have previously used r options(digits=2), which works until the last digit you want to include is 0. I have gotten around this with the sprintf function, but having to specify for each number.
Is there a way to set a 'global' sprintf option so that for all numbers following, the same number of decimal places are shown?
Thank you,
Paul
Defining a format for inline code output is feasible with a knitr inline hook (hooks are the hidden gems of knitr).
Example #1
With this Rmd file, the number of decimals is controlled without using sprintf() in all inline codes:
---
title: "Use an inline hook"
---
```{r setup, include=FALSE}
# Register an inline hook:
knitr::knit_hooks$set(inline = function(x) {
x <- sprintf("%1.2f", x)
paste(x, collapse = ", ")
})
```
Now, get 3.14 with just writing `r pi`.
Example #2
Want to change the inline output format in some part of the report?
This Rmd file does the job:
---
title: "Use a closure and an inline hook"
---
```{r setup, include=FALSE}
# Register an inline hook
knitr::knit_hooks$set(inline = function(x) {
paste(custom_print(x), collapse = ", ")
})
# Define a function factory (from #eipi10 answer)
op <- function(d = 2) {
function(x) sprintf(paste0("%1.", d, "f"), x)
}
# Use a closure
custom_print <- op()
```
Now, get 3.14 with `r pi`...
```{r three-decimals, include=FALSE}
custom_print <- op(d = 3)
```
...and now 3.142 with `r pi`.
```{r more-decimals, include=FALSE}
custom_print <- op(d = 10)
```
Finally, get 3.1415926536 with `r pi`.
Example #3
Want to display different formats for t-statistic and p-value?
One can use S3 objects and an inline hook as in this Rmd file:
---
title: "Use S3 methods and an inline hook"
---
```{r setup, include=FALSE}
# Register an inline hook
knitr::knit_hooks$set(inline = function(x) {
paste(custom_print(x), collapse = ", ")
})
# Define a generic
custom_print <- function(x, ...) {
UseMethod("custom_print", x)
}
# Define a method for p-values
custom_print.p.value <- function(x, ...) paste(sprintf("%1.2f", x), collapse = ", ")
# Define a method for t-statistics
custom_print.t.stat <- function(x, ...) paste(sprintf("%1.1f", x), collapse = ", ")
```
Estimate models...
```{r fake-results, include=FALSE}
t <- c(2.581, -1.897)
class(t) <- "t.stat"
p <- c(0.025, 0.745)
class(p) <- "p.value"
```
Want to show T-stats: `r t` (get 2.6, -1.9).
And p-values: `r p` (get 0.03, 0.74).
Who said knitr is a wonderful package?
I don't know of a way to set a global option (though there may be one). But you can write a convenience output function to reduce the amount of typing. For example, put this function at the beginning of your document:
op = function(x, d=2) sprintf(paste0("%1.",d,"f"), x)
Then, later in your document, when you want to output numbers, you can, for example, do:
op(mtcars$mpg)
Or if you want 3 digits instead of the default 2, you can do:
op(mtcars$mpg, 3)
As found in the tutorial here by Yihui, this is how I've successfully implemented it in my Rmd file.
{r setup, include=FALSE, cache=FALSE}
options(scipen = 1, digits = 2) #set to two decimal
Related
I have some R code in a package. I don't want to copy that code, but I want to display it in a pretty way in Word with syntax highlighting without any manual steps.
I looked at styler::style_text in combination of capture.output and that looks nice in the browser, but all the formatting is lost when knitting to Word. Is there some way to preserve it? I'm thinking the best thing would be to have Word native styling but the next best (acceptable) thing would be to somehow render the output to an image and include that. Has anyone done these things to document their code in a report?
show_code = function (fun) {
stopifnot(is.function(fun))
out = capture.output(fun)
n = length(out)
without_bytecode_and_env_lines = -1*c(n-1, n)
code = paste(out[without_bytecode_and_env_lines], collapse = "\n")
styler::style_text(code)
}
I believe you are trying to use syntax highlighting on the output of show_code and to do that, you simply need to use the options comment="" and class.output="r" and syntax highlighting will apply to the output.
---
title: "Source Code highlighting"
output:
word_document:
highlight: kate
---
```{r setup, include=FALSE}
knitr::opts_chunk$set(echo = TRUE)
```
## R Markdown
```{r echo=FALSE}
show_code = function (fun) {
stopifnot(is.function(fun))
out = capture.output(fun)
n = length(out)
without_bytecode_and_env_lines = c(n-1, n)
code = paste0(out[-without_bytecode_and_env_lines], collapse = "\n")
styler::style_text(code)
}
```
### The source code for `lm`
```{r comment='', echo=FALSE, class.output = "r"}
show_code(lm)
```
When using rmarkdown to render pdf document, we can use three options for printing data.frame: default, kable and tibble (see here)
With the default option, it is possible to limit the number of rows printed, with the option: max.print
For tibble, we can use: dplyr.print_max
I can't find a way to limit the number of rows for kable. Is it possible?
kable renders the full data frame passed to it as a table in the output document. AFAIK there's no argument that will limit the number of rows. However, you can preselect the number of rows in the output table (for example, kable(head(dat)) or kable(dat[1:5, ])). If you want to avoid having to select the rows each time, you could write a helper function to limit the number of rows printed. For example:
---
output: pdf_document
---
```{r setup, include=FALSE}
knitr::opts_chunk$set(echo = FALSE)
library(knitr)
```
```{r}
my_kable = function(x, max.rows=6, ...) {
kable(x[1:max.rows, ], ...)
}
```
```{r}
my_kable(mtcars, caption="My first caption")
```
```{r}
iris$Sepal.Length = 1000 * iris$Sepal.Length
my_kable(iris, 3, caption="My second caption", format.args=list(big.mark=","))
```
A really simple solution is to wrap kable around head:
kable(head(mtcars, n = 5))
You can create a custom method for printing data frames like this.
Adapted from Yihui Xie's explanation here
---
title: "Untitled"
output: html_document
---
```{r include=FALSE}
knit_print.data.frame <- function(x, ...) {
head(x, 5) |>
knitr::kable() |>
paste(collapse = "\n") |>
knitr::asis_output()
}
registerS3method(
genname = "knit_print",
class = "data.frame",
method = knit_print.data.frame,
envir = asNamespace("knitr")
)
```
```{r}
iris
tibble::as_tibble(iris)
```
I recently submitted a package to rOpenSci, and they prefer the use of message() rather than cat() for user-side console output. When I made the switch for my package, I noticed a disconcerting change in the formatting of the rendered vignettes. I have reproduced the problem in the following R Markdown report.
---
title: "MWE"
author: "Will Landau"
date: "11/20/2017"
output: html_document
---
```{r testcat}
for(x in LETTERS[1:3]){
cat(x, "\n")
}
```
```{r testmessage}
for(x in LETTERS[1:3]){
message(x)
}
```
```{r testmessage2}
for(x in LETTERS[1:3]){
message(x, "\n", appendLF = FALSE)
}
```
For the first code chunk, I get the desired output: all three lines string together in a single gray box.
## A
## B
## C
But for the second and third chunks, each line is given its own separate gray box.
## A
.
## B
.
## C
How do I keep using message() without chopping up the knitr output like this?
I think I solved it: knitr has a collapse chunk option. All I needed was to put this chunk before any of the other chunks.
```{r setup}
knitr::opts_chunk$set(collapse = TRUE)
```
The output is more condensed than I expected, but after some touching up, the formatting actually looks much better now.
You can try and build the string and put the message function outside the loop, like this:
```{r testmessage}
single_message <- c()
for(x in LETTERS[1:3]){
single_message <- paste(single_message , x, sep = "\n")
}
message(single_message )
```
Note that this example does add a newline at the start, you can prevent this with an extra if, or use the first element outside the loop to initialize single_message.
I'd like to show parameter values and not params$... in R Markdown output. For example, the first code chunk below displays params$file in the output, but I'd like to replace that with samples.txt. I tried adding a second chunk with message, but that outputs a white code chunk and I'd like a gray background like all other R code blocks.
---
output: html_document
params:
file: samples.txt
---
```{r read, message=FALSE, collapse=TRUE, comment=""}
x <- read_tsv(params$file)
x
```
This just needs a gray background
```{r print, echo=2, collapse=TRUE, comment=""}
message('x <- read_tsv("', params$file, '")')
x
```
You could modify the source hook. A solution tailored to your need follows immediately. For a more general approach, that replaces all elements in params, scroll down.
---
output:
pdf_document: default
html_document: default
params:
file: samples.txt
---
```{r, include=FALSE}
library(knitr)
library(stringr)
default_source_hook <- knit_hooks$get('source')
knit_hooks$set(source = function(x, options) {
x <- str_replace_all(x, pattern = 'params\\$file', paste0("'",params$file,"'"))
default_source_hook(x, options)
})
```
```{r print, echo=T, comment="", eval = T}
print(params$file)
```
First we save the default hook that would be used depending on the output file type (render_html or render_latex etc.). Then we change the source hook: we replace all occurrences of params$file with its value and then throw the source code back into the default hook we saved before.
In this case this results in:
This magic works, because we only modify the source code that will be printed, not the one being evaluated!
Update: A more general Approach
I played a bit with your example and created a more general hook. It should replace all elements of the form params$... in your code chunks. It even checks for the type of value and adds quotes if it is a character value.
Check the following MRE:
---
output:
pdf_document: default
html_document: default
params:
file: samples.csv
age: 28
awesome: true
34: badname
_x: badname
---
```{r, include=FALSE}
library(knitr)
library(gsubfn)
default_source_hook <- knit_hooks$get('source')
knit_hooks$set(source = function(x, options) {
x <- gsubfn(x = x, pattern = "params\\$`?([\\w_]+)`?", function(y) {
y <- get(y, params)
ifelse(is.character(y), paste0("'", y, "'"), y)
})
default_source_hook(x, options)
})
```
```{r print, echo=T, comment="", eval = T}
file <- params$file
age <- params$age
awsm <- params$awesome
# dont name your variables like that! works though...
badexmpls <- c(params$`34`, params$`_x`)
```
We make use of gsubfn(). This function allows us to use a function for the replacement attribute (not possible in common gsub). This function takes on the elements found, but, thanks to regex only the part after the $. So in this chunk, y equals file, age and awesome.
How can I use a variable as the chunk name? I have a child document which gets called a number of times, and I need to advance the chunk labels in such a manner than I can also cross reference them.
Something like this:
child.Rmd
```{r }
if(!exists('existing')) existing <- 0
existing = existing + 1
myChunk <- sprintf("myChunk-%s",existing)
```
## Analysis Routine `r existing`
```{r myChunk,echo = FALSE}
#DO SOMETHING, LIKE PLOT
```
master.Rmd
# Analysis Routines
Analysis for this can be seen in figures \ref{myChunk-1}, \ref{myChunk-2} and \ref{myChunk-3}
```{r child = 'child.Rmd'}
```
```{r child = 'child.Rmd'}
```
```{r child = 'child.Rmd'}
```
EDIT POTENTIAL SOLUTION
Here is one potential workaround, inspired by SQL injection of all things...
child.Rmd
```{r }
if(!exists('existing')) existing <- 0
existing = existing + 1
myChunk <- sprintf("myChunk-%s",existing)
```
## Analysis Routine `r existing`
```{r myChunk,echo = FALSE,fig.cap=sprintf("The Caption}\\label{%s",myChunk)}
#DO SOMETHING, LIKE PLOT
```
A suggestion to preknit the Rmd file into another Rmd file before knitting&rendering as follows
master.Rmd:
# Analysis Routines
Analysis for this can be seen in figures `r paste(paste0("\\ref{", CHUNK_NAME, 1:NUM_CHUNKS, "}"), collapse=", ")`
###
rmdTxt <- unlist(lapply(1:NUM_CHUNKS, function(n) {
c(paste0("## Analysis Routine ", n),
paste0("```{r ",CHUNK_NAME, n, ", child = 'child.Rmd'}"),
"```")
}))
writeLines(rmdTxt)
###
child.Rmd:
```{r,echo = FALSE}
plot(rnorm(100))
```
To knit & render the Rmd:
devtools::install_github("chinsoon12/PreKnitPostHTMLRender")
library(PreKnitPostHTMLRender) #requires version >= 0.1.1
NUM_CHUNKS <- 5
CHUNK_NAME <- "myChunk-"
preknit_knit_render_postrender("master.Rmd", "test__test.html")
Hope it helps. Cheers!
If you're getting to this level of complexity, I suggest you look at the brew package.
That provides a templating engine where you can dynamically create the Rmd for knitting.
You get to reference R variables in the outer brew environment, and build you dynamic Rmd from there.
Dynamic chunk names are possible with knitr::knit_expand(). Arguments are referenced in the child document, including in the chunk headers, using {{arg_name}}.
So my parent doc contains:
```{r child_include, results = "asis"}
###
# Generate a section for each dataset
###
species <- c("a", "b")
out <- lapply(species, function(sp) knitr::knit_expand("child.Rmd"))
res = knitr::knit_child(text = unlist(out), quiet = TRUE)
cat(res, sep = "\n")
```
And my child doc, which has no YAML header, contains:
# EDA for species {{sp}}
```{r getname-{{sp}}}
paste("The species is", "{{sp}}")
```
See here in the RMarkdown cookbook.