r - 如何为 rmarkdown PDF 输出格式化复杂表格

我有一个表格,我想从 rmarkdown 文档中以 PDF 格式输出。但是,由于我有限的 Latex 技能,我无法弄清楚如何使用 xtable 和各种 Latex 以我想要的方式获得跨列、单元格边框和字体面补充。

我能够使用 ReporteRs 包中的 FlexTable 函数获得几乎想要的东西,但看起来 FlexTable 只能与 rmarkdown 一起使用可生成 html 输出,但不能生成 PDF 输出。

因此,我正在寻求有关使用 xtable 或任何其他 R 包或(可能是自定义)R 函数来格式化我的表格的帮助,这些 R 包或(可能是自定义的)R 函数可用于以编程方式为 PDF 输出创建相当复杂的表格.此外,如果有某种方法可以让 FlexTable 与 PDF 输出一起工作,那也很棒。

下面我使用 FlexTable 创建了一个表格,因此您可以看到我的目标。在此之后,我提供了一个示例 rmarkdown 文档,显示了我在使用 xtable 创建类似表格的努力中(有些蹩脚)到目前为止所取得的进展。

ReporteRs::FlexTable 版本

首先,让我们创建将进入表的数据:

library(ReporteRs)

x = structure(c(34L, 6L, 9L, 35L), .Dim = c(2L, 2L), .Dimnames = structure(list(
    Actual = c("Fail", "Pass"), Predicted = c("Fail", "Pass")), .Names = c("Actual", 
"Predicted")), class = "table")

x=cbind(x, prop.table(x), prop.table(x, 1), prop.table(x,2))
x[, -c(1,2)] = sapply(x[,-c(1,2)], function(i) paste0(sprintf("%1.1f", i*100),"%"))
x = cbind(Actual=rownames(x), x)

现在开始创建和格式化 FlexTable:

# Set up general table properties and formatting
cell_p = cellProperties(padding.right=3, padding.left=3)
par_p = parProperties(text.align="right")

# Create table
ft = FlexTable(x, header.columns=FALSE, body.cell.props=cell_p, body.par.props=par_p)

# Add three header rows
ft = addHeaderRow(ft, text.properties=textBold(), c("","Predicted"),
                  colspan=c(1,8), par.properties=parCenter())

ft = addHeaderRow(ft, text.properties=textBold(), 
                  value=c("", "Count", "Overall\nPercent", "Row\nPercent", "Column\nPercent"),
                  colspan=c(1,rep(2,4)), par.properties=parCenter())

ft = addHeaderRow(ft, text.properties=textItalic(), par.properties=parCenter(),
                  value=colnames(x))

# Format specific cells
ft[1:2, 1, to="header", side="left"] = borderProperties(color="white")
ft[1:2, 1, to="header", side="top"] = borderProperties(color="white")

ft[3, 1, to="header"] = textProperties(font.style="normal", font.weight="bold")
ft[ , 1] = textProperties(font.style="italic")

ft[ , 2:3] = cellProperties(padding.right=7, padding.left=7)
ft[ , 1] = cellProperties(padding.right=10, padding.left=10)

# Display ft
ft

这是最终表格的样子(这是在浏览器窗口中显示的表格的 PNG 屏幕截图):

现在我尝试用 xtable 做同样的事情。

xtable 版本

这是 rmarkdown 文档和 header.tex 文件:

---
title: "Untitled"
author: "eipi10"
date: "11/19/2016"
output: 
  pdf_document:
    fig_caption: yes
    includes:
      in_header: header.tex 
---

```{r setup, include=FALSE}
library(knitr)
opts_chunk$set(echo = FALSE, message=FALSE)
```

```{r}
# Fake confusion matrix to work with
x = structure(c(34L, 6L, 9L, 35L), .Dim = c(2L, 2L), .Dimnames = structure(list(
    Actual = c("Fail", "Pass"), Predicted = c("Fail", "Pass")), .Names = c("Actual", 
"Predicted")), class = "table")

x=cbind(x, prop.table(x), prop.table(x, 1), prop.table(x,2))
x[, -c(1,2)] = sapply(x[,-c(1,2)], function(i) paste0(sprintf("%1.1f", i*100),"%"))
x = cbind(Actual=rownames(x), x)
```  

```{r use_xtable, results="asis"}
# Output the confusion matrix created above as a latex table
library(xtable)
options(xtable.comment=FALSE)

# This is a modified version of a function created in the following SO answer:
# http://stackoverflow.com/a/38978541/496488
make_addtorow <- function(row.name, terms, colSpan, width) {
  # Custom row function
  paste0(row.name, 
  paste0('& \\multicolumn{', colSpan, '}{C{', width, 'cm}}{', 
         terms, 
         '}', 
        collapse=''), 
  '\\\\')
}

addtorow <- list()
addtorow$pos <- list(-1,-1,-1,-1) 
addtorow$command <- c(
  "\\hline",
  make_addtorow("", c("Predicted"), 8, 12),
  "\\hline",
  make_addtorow("", c("Count", "Percent", "Row Percent", "Column Percent"), 2, 3)
  )

xtbl = xtable(x, caption="Created with xtable")

align(xtbl) <- c("|L{0cm}|", "L{1.2cm}|", rep("R{1cm}|",8))

print(xtbl, 
      include.rownames=FALSE, 
      tabular.environment="tabularx", 
      width="0.92\\textwidth",
      add.to.row = addtorow)
```

文件header.tex,用于编织上面的rmarkdown文档:

% xtable manual: https://cran.r-project.org/web/packages/xtable/vignettes/xtableGallery.pdf
\usepackage{array}
\usepackage{tabularx}  
\newcolumntype{L}[1]{>{\raggedright\let\newline\\
\arraybackslash\hspace{0pt}}m{#1}}
\newcolumntype{C}[1]{>{\centering\let\newline\\
\arraybackslash\hspace{0pt}}m{#1}}
\newcolumntype{R}[1]{>{\raggedleft\let\newline\\
\arraybackslash\hspace{0pt}}m{#1}}
\newcolumntype{P}[1]{>{\raggedright\tabularxbackslash}p{#1}}

% Caption on top
% http://tex.stackexchange.com/a/14862/4762
\usepackage{floatrow}
\floatsetup[figure]{capposition=top}

这是表格在 PDF 输出中的样子:

最佳答案

引用 this comment :

I'm looking for a way to do this programmatically from within the rmarkdown document without having to hard-code the formatting, so that it's reproducible and flexible.

以下解决方案使用硬编码的"template",但模板可以填充任何数据(只要它具有相同的 2x8 结构)。

生成的表格如下所示:

完整代码如下。


最终的表格基本上由9列组成,所以基本的LaTeX结构是

\begin{tabular}{|c|c|c|c|c|c|c|c|c|}
% rest of table
\end{tabular}

但是,固定单元格的宽度很方便。这可以通过自定义列类型 C (取自 here on TEX.SE )实现,它允许具有固定宽度的居中内容。这个,连同 more compact syntax for repeating column types给出:

\begin{tabular}{|c *{8}{|C{1cm}}|}
% rest of table
\end{tabular}

(第一列以灵活宽度居中,然后是 8 列居中,每列 1 厘米宽)。

跨多列的单元格可以使用\multicolumn。这些单元格还应具有固定宽度,以便将单元格标题分成两行。请注意,假设跨越两个 1cm 列的单元格应该具有 2cm 的宽度是错误的,因为两个跨越的单元格之间有额外的填充。一些测量表明,大约 2.436 厘米可以产生良好的效果。

第一列备注:虽然\multicolumn{1}{...}{...}乍一看没什么用,但是对于改变列类型(包括left/右)单个单元格的边框。我用它来删除前两行中最左边的垂直线。

\cline{x-y} 提供仅跨越 xy 列的水平线。

将这些部分放在一起给出:

\begin{tabular}{|c *{8}{|C{1cm}}|} \cline{2-9}
    \multicolumn{1}{c|}{} & \multicolumn{8}{c|}{\textbf{Predicted}} \\ \cline{2-9}
    \multicolumn{1}{c|}{} & \multicolumn{2}{c|}{\textbf{Count}} & \multicolumn{2}{C{2.436cm}|}{\textbf{Overall Percent}} & \multicolumn{2}{C{2.436cm}|}{\textbf{Row \newline Percent}} & \multicolumn{2}{C{2.436cm}|}{\textbf{Column Percent}} \\ \hline
% rest of table
\end{tabular}

关于数据,我删除了生成样本数据的代码的最后一行以获取:

> x <- structure(c(34L, 6L, 9L, 35L), .Dim = c(2L, 2L), .Dimnames = structure(list(Actual = c("Fail", "Pass"), Predicted = c("Fail", "Pass")), .Names = c("Actual", "Predicted")), class = "table")
> x <- cbind(x, prop.table(x), prop.table(x, 1), prop.table(x,2))
> x[, -c(1,2)] <- sapply(x[,-c(1,2)], function(i) paste0(sprintf("%1.1f", i*100),"%"))
> x
     Fail Pass Fail    Pass    Fail    Pass    Fail    Pass   
Fail "34" "9"  "40.5%" "10.7%" "79.1%" "20.9%" "85.0%" "20.5%"
Pass "6"  "35" "7.1%"  "41.7%" "14.6%" "85.4%" "15.0%" "79.5%"

要设置斜体的列名和行名,应用

colnames(x) <- sprintf("\\emph{%s}", colnames(x)) # highlight colnames
rownames(x) <- sprintf("\\emph{%s}", rownames(x)) # highlight rownames

那么,就可以使用下面的xtable代码了:

print(xtable(x),
      only.contents = TRUE, 
      comment = FALSE,
      sanitize.colnames.function = identity, 
      sanitize.rownames.function = identity, 
      hline.after = 0:2)

参数 only.contents 抑制封闭的 tabular 环境。将标识函数分配给 sanitize.colnames.functionsanitize.rownames.function 意味着“不清理”。我们需要这个,因为列名和行名包含不应转义的特殊 LaTeX 字符 (\emph)。

输出应替换上面的 %rest of table 占位符。


从概念上讲,代码使用 xtable 只生成表体而不生成表头,因为手动编写表头要容易得多。

虽然整个表头是“硬编码”的,但数据可以根据需要更改。

别忘了用第二个 \ 转义所有 \!此外,必须将以下内容添加到标题(header.tex):

\usepackage{array}
\newcolumntype{C}[1]{>{\centering\let\newline\\\arraybackslash\hspace{0pt}}m{#1}} % https://tex.stackexchange.com/a/12712/37118

我将上面列出的所有元素包装在一个函数 PrintConfusionMatrix 中,该函数可以与任何提供数据和列/行名称的 2x8 数据框一起重复使用。


完整代码:

---
output:
  pdf_document: 
    keep_tex: yes
    includes:
      in_header: header.tex
---


```{r, echo = FALSE}
library(xtable)

# Sample data from question
x <- structure(c(34L, 6L, 9L, 35L), .Dim = c(2L, 2L), .Dimnames = structure(list(Actual = c("Fail", "Pass"), Predicted = c("Fail", "Pass")), .Names = c("Actual", "Predicted")), class = "table")
x <- cbind(x, prop.table(x), prop.table(x, 1), prop.table(x,2))
x[, -c(1,2)] <- sapply(x[,-c(1,2)], function(i) paste0(sprintf("%1.1f", i*100),"%"))
#x <- cbind(Actual=rownames(x), x) # dropped; better not to add row names to data

PrintConfusionMatrix <- function(data, ...) {

  stopifnot(all(dim(x) == c(2, 8)))

  colnames(x) <- sprintf("\\emph{%s}", colnames(x)) # highlight colnames
  rownames(x) <- sprintf("\\emph{%s}", rownames(x)) # highlight rownames

  cat('\\begin{tabular}{|c *{8}{|C{1cm}}|} \\cline{2-9}
    \\multicolumn{1}{c|}{} & \\multicolumn{8}{c|}{\\textbf{Predicted}} \\\\ \\cline{2-9}
    \\multicolumn{1}{c|}{} & \\multicolumn{2}{c|}{\\textbf{Count}} & \\multicolumn{2}{C{2.436cm}|}{\\textbf{Overall Percent}} & \\multicolumn{2}{C{2.436cm}|}{\\textbf{Row \\newline Percent}} & \\multicolumn{2}{C{2.436cm}|}{\\textbf{Column Percent}} \\\\ \\hline
    \\textbf{Actual} ')

  print(xtable(x),
        only.contents = TRUE, 
        comment = FALSE,
        sanitize.colnames.function = identity, 
        sanitize.rownames.function = identity, 
        hline.after = 0:2,
        ...)
  cat("\\end{tabular}")
}
```

```{r, results='asis'}
PrintConfusionMatrix(x)
```

https://stackoverflow.com/questions/40699550/

相关文章:

vba - 是否有将 Excel 文件格式/设置保存到对象的标准过程?

c# - 将零值格式化为空字符串?

html - 如何处理 XSLT 中嵌入的 XML 标记?

c++ - 使用 std::cout 的表格布局

visual-studio - 使用标准格式将 Visual Studio 复制/粘贴到 Outlo

r - 在r中将多列从字符转换为数字格式

intellij-idea - Intellij 中的链式方法和连续缩进

javascript - 逗号优先的 JS 格式化程序

ruby - Emacs ruby​​ 模式缩进行为

java - 格式化传递给Java函数的多个参数