Pasting within a function used to print with kableExtra - r

I use kableExtra for producing several tables and I'd like to use a function instead of repeating all the code. But with my limited knowledge of R, I am not able to.
Below is a simplified example (so simple it doesn't show why I want to collapse all the code into a function). First code without any added convenience function.
library(kableExtra)
library(glue)
library(tidyverse)
data <- mtcars[1:10, ] |> select(mpg, cyl, am, carb)
# KableExtra, without added convenience function
kbl(data, booktabs = T, linesep = "", digits = 2,
caption = "Ordinary kbl()") |>
add_header_above(c(" ", "Engine" = 2 , "Other" = 2))
)
Trying to make the same, now with a function where different calls can use different arguments for caption and added headers. The caption part works fine, it's the added headers I'm struggling with.
# Call kableExtra with a function
print_kable <- function(df, caption, header) {
kbl(data, booktabs = T, linesep = "", digits = 2,
# Caption works fine
caption = caption) |>
# I'm unable to develop code that uses a string argument here
add_header_above(c(header)) # 1 col instead of 5
# add_header_above(c({header})) # Same here
# add_header_above(c({{header}})) # and here
# add_header_above(c("{header}")) # and here
# add_header_above(c("{{header}}")) # and here
# add_header_above(c(glue({header}))) # and here
# add_header_above(c(paste(header))) # and here
}
Kable should print with the code below
print_kable(mtcars, caption = "kbl() called with a function",
header = ' " ", "Engine" = 2 , "Other" = 2 ')
Here is a related question:
How to evaluate a glue expression in a nested function?

Placing the function c() in the function call rather than in the function itself works fine. Is this what you're looking for?
print_kable <- function(df, caption, header) {
kbl(data, booktabs = T, linesep = "", digits = 2,
caption = caption) |>
add_header_above(header)
}
print_kable(mtcars, caption = "kbl() called with a function",
header = c(" ", "Engine" = 2 , "Other" = 2))

Related

Output from mediate function in RMarkdown

Im trying to inculde the output of the mediate function from mediation package in my RMarkdown (PDF) document. Using summary it gives me the table with the results from the bootstrapping analysis when I knitr the RMarkdown to a PDF document, but:
I don't like the look of the output and wuld like to have a more shiny table.
I can't lable this table with a caption and it's not included in the autmatic nummeration of RMarkdown (and as a consequence I can't reference it in the text).
I tried to use kable or xtabs with the output of the 'mediate` function but it won't work since both functions don't accept the class ("summary.mediate" "mediate") of the output.
This is how the code chunk in my RMarkdown document loks like:
```{r med.y1.z6.z7.c, echo = F, comment = "", strip.white = T, fig.cap="test"}
regDFM <- na.omit(as.data.frame(cbind(Y1, X1, Z1, Z6, Z7)))
regFIT1.C.medY <- lm(Y1 ~ X1+Z1+Z6+Z7+X1:Z1, data = regDFM)
regFIT1.C.medM <- lm(Z7 ~ X1+Z1+Z6+X1:Z1, data = regDFM)
fitMED <- mediation::mediate(regFIT1.C.medM, regFIT1.C.medY,
boot = T, sims = 10, treat="Z6", mediator="Z7")
summary(fitMED)
```
Any help or ideas are very appreciated!
With kable and the elements of the mediate function I finally created a nice RMarkdown > PDF output.
First I created a data.frame using the relevant elements of the mediate function (are the called 'elements'? what would yu call them?). Then just passing the data.frame to kable.
Thanks to #henrik_ibsen for the hint.
Here is my code:
regDFM <- na.omit(as.data.frame(cbind(Y1, X1, Z1, Z6, Z7)))
regFIT1.C.medY <- lm(Y1 ~ X1+Z1+Z6+Z7+X1:Z1, data = regDFM)
regFIT1.C.medM <- lm(Z7 ~ X1+Z1+Z6+X1:Z1, data = regDFM)
fitMED <- mediation::mediate(regFIT1.C.medM, regFIT1.C.medY,
boot = T, sims = 10, treat="Z6", mediator="Z7")
bt_effect <- c("Indirekter Effekt", "Direkter Effekt", "Gesamt Effekt",
"Anteil direkter Effekt")
bt_est <- c(fitMED$d1, fitMED$z1, fitMED$tau.coef, fitMED$n1)
#bt_p <- format.pval(c(fitMED$d1.p, fitMED$z1.p, fitMED$tau.p, fitMED$n1.p))
bt_p <- c(fitMED$d1.p, fitMED$z1.p, fitMED$tau.p, fitMED$n1.p)
bt_stars <- c(stars.pval(fitMED$d1.p), stars.pval(fitMED$z1.p),
stars.pval(fitMED$tau.p), stars.pval(fitMED$n1.p))
bt_DF <- data.frame(row.names = bt_effect, format(bt_est, digits = 2),
format(bt_p, nsmall = 3), bt_stars)
colnames(bt_DF) <- c("Koeffizienten", "p-Werte", "")
kable(bt_DF, booktabs = T, align = "c",
caption = "Bootstraping-Analyse für Mediation") %>%
footnote(general = c("Simulationen: 1000", "Signifikanzniveaus: ∗ p<0.05;
∗∗ p<0.01; ∗∗∗ p<0.001"),
general_title = "Anmerkungen:")

How can you specify grouped column names inside a function with kableExtra?

library(tidyverse)
library(knitr)
library(kableExtra)
kable(head(mpg)) %>% kable_styling %>% add_header_above(c(" " = 6,
"foobar" = 5))
Works well when you want to specify the grouped column header manually:
However I want to put this feature inside a function, I've tried:
a_function <- function(my_column_head_name){
kable(head(mpg)) %>% kable_styling %>% add_header_above(c(" " = 6,
my_column_head_name = 5))
}
a_function("foobar")
But this returns the named argument as the column header name:
I'm guessing this is something to do with lazy evaluation?
The header is a named character vector with colspan as values. You have to create the vector and assign names with names().
a_function <- function(my_column_head_name){
myHeader <- c(" " = 6, my_column_head_name = 5)
names(myHeader) <- c(" ", my_column_head_name)
kable(head(mpg)) %>% kable_styling %>% add_header_above(myHeader)
}
a_function("foobar")

knitr kable horizontal line not appearing in second last line pdf

library(knitr)
library(kableExtra)
df <- data.frame("r1" = c(1,2,3,4), "r2"=c(4,5,6,6), "r3"=c(7,8,9,8), "r4"=c(11,12,13,89))
kable(df, format = "latex", booktabs = T, linesep = c('','','\\hline'))
actually this code should get a horizontal line at the second last line
But, i am not getting it. Is this a bug in kable or anything else?
I am trying to get a line above the last line for total. I am using Knitr Kable for this and knitting to pdf. Please Help
As far as I know, this is not how linesep works for kable. Instead you could use xtable:
library(xtable)
df <- data.frame("r1" = c(1,2,3,4), "r2"=c(4,5,6,6), "r3"=c(7,8,9,8), "r4"=c(11,12,13,89))
print(xtable(df), hline.after = c(0,3))
Why it does not work
This is the internal code in kable that produces the linesep:
linesep = if (nrow(x) > 1) {
c(rep(linesep, length.out = nrow(x) - 2), linesep[[1L]], '')
} else rep('', nrow(x))
linesep = ifelse(linesep == "", linesep, paste0('\n', linesep))
In line 2 you can see that your linesep argument is going to be repeated nrow(x)-2 times. So if you pass linesep = c("", "", "\\hline") to kable and you only have 4 rows, then this vector will be repeated 2 times. But since the vectors length is greater than 2, it only uses the first 2 elements which are empty. At the end of the snippet you have an empty character vector with 4 elements and therefore no horizontal ruler appears.

Sanitize column names for Bold and Identity with xtable in knitr

Is there a way to pass xtable's identity function to sanitize the column names AND another custom function to bold the column names? There are two code chunks below, one to set up the dummy function and then another to print the xtable. It fails on the $ symbol in the first column name and the $ symbol in the table value is properly sanitized.
Thanks!
<<setup>>=
library(knitr)
library(xtable)
two_functions = function(x){
paste("\\textbf{", x, "}", sep = "")
# use xtable's 'identity' function to convert special characters
}
options(xtable.sanitize.colnames.function = two_functions)
#
<<xtable, results='asis'>>=
xtab = data.frame(a = c("Horse and $buddy", "Paddy Wagon", "Hospital Care", "Peanut butter and toast", "Cheese Whiz with Mayo"),
b = c(10000000, 200000.4533, 3098765435.65456, 408765467.654456, 50.00000))
colnames(xtab) = c("Hello money $ bag$", "Numbers")
print(xtable(xtab))
#
I think the solution maybe as simple as using gsub within the two_functions call.
\documentclass{article}
\begin{document}
<<<setup>>=
library(knitr)
library(xtable)
two_functions = function(x){
gsub("\\$", "\\\\$", paste("\\textbf{", x, "}", sep = ""))
}
options(xtable.sanitize.colnames.function = two_functions,
xtable.sanitize.rownames.function = NULL,
xtable.sanitize.text.function = NULL)
#
<<xtable, results='asis'>>=
xtab = data.frame(a = c("Horse and $buddy", "Paddy Wagon", "Hospital Care", "Peanut butter and toast", "Cheese Whiz with Mayo"),
b = c(10000000, 200000.4533, 3098765435.65456, 408765467.654456, 50.00000))
colnames(xtab) = c("Hello money $ bag$", "Numbers")
print(xtable(xtab))
#
\end{document}
Edit
To use the default xtable function for sanitizing a string replace the two_functions function above with the following:
two_functions = function(x){
paste0("\\textbf{", xtable::sanitize(x, type = "latex"), "}")
}
Here the xtable::sanitize function is called first and then the resulting stings are placed inside of the LaTeX \textbf{} environment.
The resulting table is:

merge columns every other row using Sweave/R/Latex

I am writing a conference abstract booklet using R/Sweave. I have already made the program booklet for printing that contains the id, author, title only.
Now I want to modify it to include the abstract (not for print). But abstracts are lengthy. My thought is to take the cell with the abstract info, and have it display below the row with the author info - expanded across the full width of the page.
ID--author--------title--------------------------------
abstract-----------------------------------------------
So every other row has only one column spanning the width of the entire table.
Is there a way to add multicolmn{x} to every other row?
If a solution can't be figured out, advice for how to print full abstracts in a nice way would be welcome. (Something other than "just use landscape" or "adjust column widths")
Also, it doesn't have to be PDF. I could switch to markdown/html - and make it look closer to real conference program schedules that have full abstracts on them. Again, one I figure out how to print a table where every other row has only one column that is the width of the entire table.
If you want to try - Here is a complete MWE for what I have working now. Note that it uses the R package lipsum which has to be installed via devtools/github.
\documentclass{article}
\usepackage{booktabs, multicol, array}
\usepackage[margin=0.75in]{geometry}
%%%%%%%%%%% Let tables to span entire page
\newcolumntype{L}[1]{>{\raggedright\let\newline\\\arraybackslash\hspace{0pt}}m{#1}}
<<echo=FALSE, warning=FALSE, message=FALSE>>=
# devtools::install_github("coolbutuseless/lipsum")
library(lipsum)
library(xtable)
knitr::opts_chunk$set(echo = FALSE, warning=FALSE, message=FALSE)
options(xtable.comment = FALSE)
tblalign <- "lL{0.5cm}|L{4cm}L{6cm}L{8cm}"
# fake data setup
dat <- data.frame(ID = c(1:3), author = substr(lipsum[1:3], 1, 40),
title = substr(lipsum[4:6], 1, 100),
abstract = lipsum[7:9])
names(dat)=c("\\multicolumn{1}{c}{\\textbf{\\large{ID}}}",
"\\multicolumn{1}{c}{\\textbf{\\large{Author List}}}",
"\\multicolumn{1}{c}{\\textbf{\\large{Title}}}",
"\\multicolumn{1}{c}{\\textbf{\\large{Abstract}}}")
#
\begin{document}
<<results='asis'>>=
print(
xtable(x = dat
, align = tblalign)
, table.placement = "H"
, sanitize.colnames.function=function(x){x}
, include.rownames = FALSE
, include.colnames = TRUE
, size = "small"
, floating = FALSE
, hline.after = c(0,1:nrow(dat))
)
#
\end{document}
Split data from abstract manually
out <- dat[,-4]
ab.list <- dat$abstract
then add.to.row
, add.to.row = list(pos = as.list(1:nrow(out)),
command = paste0("\\multicolumn{3}{L{15cm}}{\\textbf{Abstract: }", ab.list, "} \\\\"))
One approach using my package huxtable. I couldn't install lipsum for some reason, so just hacked it. This is in a .Rmd file with output pdf_document.
```{r, results = 'asis'}
lipsum <- rep(do.call(paste, list(rep('blah ', 100), collapse = '')), 10)
dat <- data.frame(ID = c(1:3), author = substr(lipsum[1:3], 1, 40),
title = substr(lipsum[4:6], 1, 100),
abstract = lipsum[7:9], stringsAsFactors = FALSE)
library(huxtable)
# shape data
datmat <- matrix(NA_character_, nrow(dat) * 2, 3)
datmat[seq(1, nrow(datmat), 2), ] <- as.matrix(dat[, c('ID', 'author', 'title')])
datmat[seq(2, nrow(datmat), 2), 1] <- dat$abstract
# print as PDF
ht <- as_huxtable(datmat)
colspan(ht)[seq(2, nrow(ht), 2), 1] <- 3
wrap(ht) <- TRUE
col_width(ht) <- c(.2, .2, .6)
number_format(ht) <- 0
ht
```

Resources