possible to create latex multicolumns in xtable? - r

I am using xtable with R Markdown and knitr to produce .tex files that I call with \input{}. Works great, but I have not figured out how to create multicolumns like the one shown here. Does anyone know how to to this?
So far I am using:
tbl <- xtable(data, align="l r r r r r")
colnames(tbl) <- c("Variable",
"Mean", "Std Dev",
"Mean", "Std Dev",
"Difference")
caption(tbl) <- c("Table Title")
print(tbl,
include.rownames=FALSE,
caption.placement="top",
booktabs=TRUE,
type="latex",
file="output.tex")
I'd like to have a different grouping header over each "Mean" and "Std Dev" ("Treatment" and "Control").
Alternatively, is there a better method for using R Markdown/knitr to automatically generate tables? I don't want to manually edit the tables because the report needs to generate automatically.
UPDATE:
#agstudy: I'm new to latex, but I think this is the output I am looking to produce automatically with xtable (or something like xtable):
\begin{tabular}{lrrrrr}
\toprule
& \multicolumn{2}{c}{Treatment} & \multicolumn{2}{c}{Control} & \\
\cmidrule(lr){2-3} \cmidrule(lr){4-5}
Variable & Mean & Std Dev & Mean & Std Dev & Difference \\
\midrule
var1 & 1 & 2 & 3 & 4 & 5 \\
\bottomrule
\end{tabular}
UPDATE 2:
#Jonathan: it took me a few reads to understand what you were suggesting. I took your recommendation, and it worked.
In the R markdown chunk I now use:
tbl <- xtable(data)
print(tbl,
only.contents=TRUE,
include.rownames=FALSE,
type="latex",
digits(tbl) <- c(0,1,1,1,1,1),
file="output/tblout.tex")
Then in the text, I use:
\begin{tabular}{lddddd}
\toprule
& \multicolumn{2}{c}{Treatment} & \multicolumn{2}{c}{Control} & \\
\cmidrule(lr){2-3} \cmidrule(lr){4-5}
Variable & \multicolumn{1}{r}{Mean} & \multicolumn{1}{r}{Std Dev} & \multicolumn{1}{r}{Mean} & \multicolumn{1}{r}{Std Dev} & \multicolumn{1}{r}{Difference} \\
\midrule
\input{../output/tblout}
\bottomrule
\end{tabular}
I'll see if anyone has any other suggestions for a native xtable (or other package) solution. Otherwise, I will accept your answer. Thanks!

I think the add.to.row option in xtable achieves this perfectly.
Example code here:
require(xtable)
age <- sample(c('30-50', '50-70', '70+'), 200, replace=T)
sex <- sample(c('Male', 'Female'), 200, replace=T)
val <- table(age, sex)
val <- rbind(val, formatC(prop.table(val)*100, format='f', digits=1))
val <- structure(val, dim=c(3, 4))
val <- rbind(c('n', '%'), val)
rownames(val) <- c('', sort(unique(age)))
val <- xtable(val)
addtorow <- list()
addtorow$pos <- list(0)
addtorow$command <- paste0(paste0('& \\multicolumn{2}{c}{', sort(unique(sex)), '}', collapse=''), '\\\\')
print(val, add.to.row=addtorow, include.colnames=F)

Assuming the form of the table is the same across runs (i.e., only the numbers are changing), my suggestion would be to use the only.contents argument to print.xtable and code the multi-column headers in by hand. To the best of my knowledge xtable is not capable of doing multi-column cells itself.

Consider using the tables package.

This is a child's game with the kableExtra package.
\documentclass{article}
\usepackage{booktabs}
\begin{document}
<<setup, include=FALSE>>=
library(knitr)
opts_chunk$set(echo=FALSE)
library(kableExtra)
options(knitr.table.format = "latex")
mx <- matrix(1:6, ncol=3)
rownames(mx) <- LETTERS[1:NROW(mx)]
colnames(mx) <- sprintf("Col %s", LETTERS[1:NCOL(mx)])
#
<<results='asis'>>=
kable(mx, booktabs = TRUE, caption = "My table", align = "c") %>%
add_header_above(c(" ", "First"=2, "Second"=1)) %>%
kable_styling(latex_options = "hold_position")
#
<<results='asis'>>=
kable(mx, booktabs = TRUE, caption = "My other table", align = "c") %>%
add_header_above(c(" ", "First"=2, "Second"=1)) %>%
kable_styling(latex_options = "hold_position") %>%
group_rows("Nice!", 1, 2)
#
\end{document}

Usually I am doing something like this:
tableLines <- print (xtable (mymatrix)) ## no file
multicolumns <- "& \\\\multicolumn{3}{c}{A} & \\\\multicolumn{3}{c}{B} \\\\\\\\"
tableLines <- sub ("\\\\toprule\\n", paste0 ("\\\\toprule\n", multicolumns, "\n"), tableLines) ## booktabs = TRUE
tableLines <- sub ("\\\\hline\\n", paste0 ("\\\\hline\n", multicolumns, "\n"), tableLines) ## booktabs = FALSE
writeLines (tableLines, con = "myfile")
Pay attention to the many \\\\ needed. Backslashes are lost in the sub and paste commands.

A little bit late to the game here is my answer, which is similar to ashkan, but more general and allows for different parameters.
First of all, why a new answer? Well, I needed an output without a table-environment (I want to write my captions etc inside my tex-document not inside my r-code) which kableExtra does not seem to provide (correct me if I am wrong).
But I also wanted flexibility with the inputs (i.e., with and without line, different spans etc).
The result is a function construct_header() that constructs the header for us.
First a short example:
library(xtable)
set.seed(123)
df <- matrix(round(rnorm(16), 2), ncol = 4)
df <- cbind(paste("Var", 1:4), df)
colnames(df) <- c("Var", rep(c("X", "Y"), 2))
df
# Var X Y X Y
# [1,] "Var 1" "-0.56" "0.13" "-0.69" "0.4"
# [2,] "Var 2" "-0.23" "1.72" "-0.45" "0.11"
# [3,] "Var 3" "1.56" "0.46" "1.22" "-0.56"
# [4,] "Var 4" "0.07" "-1.27" "0.36" "1.79"
a_header <- construct_header(
# the data.frame or matrix that should be plotted
df,
# the labels of the groups that we want to insert
grp_names = c("", "Group A", "Group B"),
# the number of columns each group spans
span = c(1, 2, 2),
# the alignment of each group, can be a single character (lcr) or a vector
align = "c"
)
print(xtable(df), add.to.row = a_header, include.rownames = F, hline.after = F)
# % latex table generated in R 3.4.2 by xtable 1.8-2 package
# % Fri Oct 27 16:39:44 2017
# \begin{table}[ht]
# \centering
# \begin{tabular}{lllll}
# \hline
# \multicolumn{1}{c}{} & \multicolumn{2}{c}{Group A} & \multicolumn{2}{c}{Group B} \\ \cmidrule(lr){2-3} \cmidrule(lr){4-5}
# Var & X & Y & X & Y \\
# \hline
# Var 1 & -0.56 & 0.13 & -0.69 & 0.4 \\
# Var 2 & -0.23 & 1.72 & -0.45 & 0.11 \\
# Var 3 & 1.56 & 0.46 & 1.22 & -0.56 \\
# Var 4 & 0.07 & -1.27 & 0.36 & 1.79 \\
# \hline
# \end{tabular}
# \end{table}
Note that we have to specify hline.after = FALSE (important for me, but omitted here is the possibility to specify floating = FALSE).
Which results in this table (note that this approach needs the booktabs package to be loaded in LaTeX):
You can specify to omit the lines construct_header(..., draw_line = FALSE), align the groups, and have them span in different ways, i.e.,
ugly_header <- construct_header(df, c("One", "Two", "Three"), c(2, 1, 2), c("l", "c", "r"))
print(xtable(df), add.to.row = ugly_header, include.rownames = F, hline.after = F)
which results in this:
The code for the function is this:
#' Constructs a header i.e., groups for an xtable
#'
#' #param df a data.frame or matrix
#' #param grp_names the names of the groups
#' #param span where the groups span
#' #param align the alignment of the groups, defaults to center
#' #param draw_line if the group-names should be underlined
#'
#' #return a list that can be given to the \code{add.to.row} argument of the of \code{print.xtable}
#' #export
#'
#' #examples
#' library(xtable)
#' mx <- matrix(rnorm(16), ncol = 4)
#' mx <- cbind(paste("Var", 1:4), mx)
#' colnames(mx) <- c("Var", rep(c("X", "Y"), 2))
#'
#' addtorow <- construct_header(mx, c("", "Group A", "Group B"), span = c(1, 2, 2), "c")
#' print(xtable(mx), add.to.row = addtorow, include.rownames = F, hline.after = F)
construct_header <- function(df, grp_names, span, align = "c", draw_line = T) {
if (length(align) == 1) align <- rep(align, length(grp_names))
if (!all.equal(length(grp_names), length(span), length(align)))
stop("grp_names and span have to have the same length!")
if (ncol(df) < sum(span)) stop("Span has to be less or equal to the number of columns of df")
header <- mapply(function(s, a, grp) sprintf("\\multicolumn{%i}{%s}{%s}", s, a, grp),
span, align, grp_names)
header <- paste(header, collapse = " & ")
header <- paste0(header, " \\\\")
if (draw_line) {
# where do we span the lines:
min_vals <- c(1, 1 + cumsum(span)[1:(length(span) - 1)])
max_vals <- cumsum(span)
line <- ifelse(grp_names == "", "",
sprintf("\\cmidrule(lr){%i-%i}", min_vals, max_vals))
line <- paste(line[line != ""], collapse = " ")
header <- paste0(header, " ", line, "\n ")
}
addtorow <- list(pos = list(-1, -1, nrow(df)),
command = c("\\hline\n ", header, "\\hline\n "))
return(addtorow)
}

Related

Pasting within a function used to print with kableExtra

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))

rewrapping kable latex table to different tabular environment

I would like to keep just the "inside" lines of a latex table created by kable. I only know cumbersome and ugly ways to do so...attempts at cleaner versions failed. here is one clean flunk:
kable.rewrap <- function( df, newname= "mytable" ) {
kt <- kable( df, "latex", booktabs=T )
notop <- strsplit(kt, "\\midrule")[[1]][2]
nosur <- strsplit(notop, "\\bottomrule" )[[1]][1] ## fails: doesn't like "\\"!
newkt <- paste0("\\begin{", newname, "}", nosur, "\n\\end{",newname,"}\n")
## attr(newkt, "format") <- chr "latex" # wrong
newkt
}
print(kable.rewrap( data.frame( x=1:3, y=1:3 ), "mytable" ))
should produce
\begin{mytable}
\toprule
x & y\\
\midrule
1 & 1\\
2 & 2\\
3 & 3\\
\bottomrule
\end{mytable}
obviously, my latex code should define an environment mytable now. I am also puzzled by "bottomrule" in the nosur line works, but "\\bottomrule" fails.
(another alternative is to forego kable altogether and just work with the data frame, separating each line by a \ and each column by a &.)
advice appreciated.
1) The kable.envir argument will add the mytable part but that still causes tabulars to be generated which we remove using gsub:
kable(test_data, "latex", booktabs = TRUE, table.envir = "mytable") %>%
gsub(".(begin|end){tabular.*}", "", ., perl = TRUE)
giving:
\begin{mytable}
\toprule
x & y\\
\midrule
1 & 1\\
2 & 2\\
3 & 3\\
\bottomrule
\end{mytable}
2) Another possibility is to define your own mytabular environment and then use:
kable(test_data, "latex", booktabs = TRUE, table.envir = "mytable") %>%
kable_styling(latex_table_env = "mytabular")
which gives:
\begin{mytable}
\begin{mytabular}{rr}
\toprule
x & y\\
\midrule
1 & 1\\
2 & 2\\
3 & 3\\
\bottomrule
\end{mytabular}
\end{mytable}
Added
In (2) rather than defining a do-nothing environment to replace tabular tex/latex actually makes one available already, namely #empty.
kable(test_data, "latex", booktabs = TRUE, table.envir = "mytable") %>%
kable_styling(latex_table_env = "#empty")
Note
We named the data frame shown in the question as follows:
test_data <- data.frame(x = 1:3, y = 1:3)

Simple example of using tables + knitr for latex

I'm struggling with a tables package, all the examples in the packable docs are so complex and none of them works for me with knitr and latex. Could somebody help be out and display a simple table with some formulas and multiline labels in the header? 
Something like this:
df <- data.frame(matrix(1:9, nrow = 3))
colnames(df) <- c("first column", "second \\frac{1}{2}", "third column first line \\ second line")
Thank you in advance
It is possible to create multi-line headers for tables in LaTeX using the xtable package. These can be compiled from either .Rmd or .Rnw files. Building on the example by mattw and using add.to.row in the print method for xtable:
df <- data.frame(matrix(1:50, nrow = 10))
print(
xtable(df),
include.rownames = FALSE,
include.colnames = FALSE,
add.to.row = list(
pos = list(0),
command = c(
"& \\multicolumn{4}{c}{4 column header} \\\\
\\cline{2-5}
col1 & col2 & col3 & col4 & col5 \\\\ "
)
)
)
Note that add.to.row requires a list with two elements: pos and command. The first must be a list, the second a character string or vector, see ?print.xtable. pos gives the row number for the LaTeX insertion, and command is the insertion. Be a bit careful with formatting this, as it is will run directly into the next cell of the first column if you don't put in spaces or \n.
There are lots of options for customisation, allowing you to create quite complex tables with a bit of tweaking.
print(
xtable(df),
include.rownames = FALSE,
include.colnames = FALSE,
hline.after = c(-1,0),
add.to.row = list(
pos = list(0,5,10),
command = c(
"& \\multicolumn{4}{c}{4 column header} \\\\
\\cline{2-5}
col1 & col2 & col3 & col4 & col5 \\\\ ",
"\\hline \\multicolumn{5}{l}{Separator in table.} \\\\ \\hline",
"\\hline \\multicolumn{5}{l}{Notes at end of table.} \\\\ "
)
)
)
In this example I change the default settings for where xtable puts \hline, allowing me to add the last \hline above the notes - useful for explaining superscripts in the table.
Note also the use of \cline{2-5} giving me a line over columns 2 - 5.
See gist for fully reproducible example.
I don't think that this is possible with RMarkdown if you want the table to be in LaTeX style. However, you can easily do this with the xtable package when you write your code in an .Rnw file:
\documentclass{article}
\begin{document}
<<>>=
library("xtable")
df <- data.frame(matrix(1:9, nrow = 3))
colnames(df) <- c("first column", "second $\\frac{1}{2}$",
"third column")
#
<<xtable, results = "asis">>=
print(xtable(df), floating = TRUE,
sanitize.colnames.function = identity, type = "latex")
#
\end{document}

Test statistic (e.g. chisquare test) inside latex table using the tables-package in R/Knitr/Rstudio

I would like to use the tabular()-function from the tables-package to do a cross-tabulation of two variables (e.g. v1 and v2), and present the p-value of the chisq-test in the table. It is easy to get the crosstabulation, but I cant get the p-value inside the table. This is what I've been trying, without any luck:
\documentclass{article}
\begin{document}
<<echo=TRUE,message=FALSE>>=
library(Hmisc)
library(tables)
v1 <- sample(letters[1:2],200,replace=TRUE)
v2 <- sample(month.name[1:3],200,replace=TRUE)
df <- data.frame(v1,v2)
#
It is straight forward to get the crosstabulation:
<<results='asis'>>=
latex( tabular( Factor(v1) ~ Factor(v2) , data=df) )
#
But I cant get the p-value inside the table:
<<results='asis'>>=
latex( tabular( Factor(v1)*chisq.test(v1,v2)$p.value ~ Factor(v2) , data=df) )
#
\end{document}
I don't know how to do it with tables::tabular but this will do it with Hmisc::summary.formula.reverse assuming you have your system configured to produce pdf files through latex(). I had to do searching of the Rhelp archives to figure out that the 'exclude1' argument needed to go in the latex argument list. Once you go back through the documentation exclude1 does appear in the usage example of latex.summary.formula.reverse although I thought I was reading the help page for summary.rms:
library(Hmisc)
latex(summary( v2 ~ v1, data=df, method="reverse" ,test=TRUE), exclude1=FALSE)
You can intercept the latex output "along the way" if you want to embed it in a longer document by assigning the output to a named file.
latex(summary( v2 ~ v1, data=df, method="reverse" ,test=TRUE), exclude1=FALSE, file="")
#--------
% latex.default(cstats, title = title, caption = caption, rowlabel = rowlabel, col.just = col.just, numeric.dollar = FALSE, insert.bottom = legend, rowname = lab, dcolumn = dcolumn, extracolheads = extracolheads, extracolsize = Nsize, ...)
%
\begin{table}[!tbp]
\caption{Descriptive Statistics by v2\label{summary}}
\begin{center}
\begin{tabular}{lcccc}
\hline\hline
\multicolumn{1}{l}{}&\multicolumn{1}{c}{February}&\multicolumn{1}{c}{January}&\multicolumn{1}{c}{March}&\multicolumn{1}{c}{Test Statistic}\tabularnewline
&\multicolumn{1}{c}{{\scriptsize $N=56$}}&\multicolumn{1}{c}{{\scriptsize $N=73$}}&\multicolumn{1}{c}{{\scriptsize $N=71$}}&\tabularnewline
\hline
v1~:~a&43\%~{\scriptsize~(24)}&47\%~{\scriptsize~(34)}&44\%~{\scriptsize~(31)}&$ \chi^{2}_{2}=0.21 ,~ P=0.901 $\tabularnewline
~~~~b&57\%~{\scriptsize~(32)}&53\%~{\scriptsize~(39)}&56\%~{\scriptsize~(40)}&\tabularnewline
\hline
\end{tabular}
\end{center}
Numbers after percents are frequencies.\\\noindent Test used:\\Pearson test\end{table}
You can also paste text from a chi-squared statistic into a caption via xtable(). For example:
#sample data
var1<-sample(c('A', 'B'), 10, replace=T)
var2<-sample(c('Red', 'Blue'), 10, replace=T)
#join in frequency table
tab<-table(var1, var2)
#conduct chisq.test
test<-chisq.test(tab)
#show values of chisq.test()
name(test)
#Use xtable, use print.xtable for further manipulations
out<-xtable(tab, caption=paste('Important table, chi-squared =', test$statistic, ', p=', test$p.value,',' ,test$parameter, 'df', sep=' '))
#print
out

xtable header manipulation

I am using xtable in R to get LaTeX code for tables. I want to modify the first element of the "header" (first row in LaTeX table) but I only saw an option to add text to the end of a line (using add.to.row in xtable).
The following example should clarify what I want.
Running
xtable(matrix(c(1,0.25,3,0.75),nrow=2,dimnames=list(c('absolute','relative'),c('GNU','Leo'))))
gives the output
% latex table generated in R 2.13.0 by xtable 1.5-6 package
% Tue Jun 19 22:39:49 2012
\begin{table}[ht]
\begin{center}
\begin{tabular}{rrr}
\hline
& GNU & Leo \\
\hline
absolute & 1.00 & 3.00 \\
relative & 0.25 & 0.75 \\
\hline
\end{tabular}
\end{center}
\end{table}
I would like to get the line
frequency & GNU & Leo \\
instead of the line
& GNU & Leo \\
but I was unable to accomplish this using xtable. Let me know if you have an idea how to do this. Also, I apologize in advance if this is in the wrong place to ask or if I overlooked an answer or an obvious solution.
Another option is to use the latex function from Hmisc:
library(Hmisc)
m <- matrix(c(1,0.25,3,0.75),nrow=2,dimnames=list(c('absolute','relative'),c('GNU','Leo')))
latex(m,file = "",rowlabel = "frequency")
One possibility:
dat1 <- data.frame(matrix(c(1,0.25,3,0.75),nrow=2,dimnames=list(c('absolute','relative'),c('GNU','Leo'))))
dat <- data.frame(Freq = rownames(dat1), dat1)
print(
xtable(
x = dat
, caption = "Your Caption"
, label = "tab:dat"
, align = paste(paste("l|", paste(rep("r", ncol(dat)-1), collapse=''), sep = ""), "|r", sep = "")
, digits = c(0, rep(3, ncol(dat)))
)
, table.placement = "H"
, caption.placement = "top"
, include.rownames = FALSE
, include.colnames = TRUE
, size = "normalsize"
, hline.after = c(-1, 0, nrow(dat))
)

Resources