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