possible to create latex multicolumns in xtable?

后端 未结 6 1544
南笙
南笙 2020-12-01 07:22

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

6条回答
  •  既然无缘
    2020-12-01 08:27

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

提交回复
热议问题