草庐IT

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

coder 2023-04-23 原文

我有一个表格,我想从 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)
```

关于r - 如何为 rmarkdown PDF 输出格式化复杂表格,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/40699550/

有关r - 如何为 rmarkdown PDF 输出格式化复杂表格的更多相关文章

  1. ruby - 使用 ruby​​ 将 HTML 转换为纯文本并维护结构/格式 - 2

    我想将html转换为纯文本。不过,我不想只删除标签,我想智能地保留尽可能多的格式。为插入换行符标签,检测段落并格式化它们等。输入非常简单,通常是格式良好的html(不是整个文档,只是一堆内容,通常没有anchor或图像)。我可以将几个正则表达式放在一起,让我达到80%,但我认为可能有一些现有的解决方案更智能。 最佳答案 首先,不要尝试为此使用正则表达式。很有可能你会想出一个脆弱/脆弱的解决方案,它会随着HTML的变化而崩溃,或者很难管理和维护。您可以使用Nokogiri快速解析HTML并提取文本:require'nokogiri'h

  2. ruby - 检查 "command"的输出应该包含 NilClass 的意外崩溃 - 2

    为了将Cucumber用于命令行脚本,我按照提供的说明安装了arubagem。它在我的Gemfile中,我可以验证是否安装了正确的版本并且我已经包含了require'aruba/cucumber'在'features/env.rb'中为了确保它能正常工作,我写了以下场景:@announceScenario:Testingcucumber/arubaGivenablankslateThentheoutputfrom"ls-la"shouldcontain"drw"假设事情应该失败。它确实失败了,但失败的原因是错误的:@announceScenario:Testingcucumber/ar

  3. ruby - 如何为 emacs 安装 ruby​​-mode - 2

    我刚刚为fedora安装了emacs。我想用emacs编写ruby。为ruby​​提供代码提示、代码完成类型功能所需的工具、扩展是什么? 最佳答案 ruby-mode已经包含在Emacs23之后的版本中。不过,它也可以通过ELPA获得。您可能感兴趣的其他一些事情是集成RVM、feature-mode(Cucumber)、rspec-mode、ruby-electric、inf-ruby、rinari(用于Rails)等。这是我当前用于Ruby开发的Emacs配置:https://github.com/citizen428/emacs

  4. ruby - 通过 erb 模板输出 ruby​​ 数组 - 2

    我正在使用puppet为ruby​​程序提供一组常量。我需要提供一组主机名,我的程序将对其进行迭代。在我之前使用的bash脚本中,我只是将它作为一个puppet变量hosts=>"host1,host2"我将其提供给bash脚本作为HOSTS=显然这对ruby​​不太适用——我需要它的格式hosts=["host1","host2"]自从phosts和putsmy_array.inspect提供输出["host1","host2"]我希望使用其中之一。不幸的是,我终其一生都无法弄清楚如何让它发挥作用。我尝试了以下各项:我发现某处他们指出我需要在函数调用前放置“function_”……这

  5. ruby - 如何进行排列以有效地定制输出 - 2

    这是一道面试题,我没有答对,但还是很好奇怎么解。你有N个人的大家庭,分别是1,2,3,...,N岁。你想给你的大家庭拍张照片。所有的家庭成员都排成一排。“我是家里的friend,建议家庭成员安排如下:”1岁的家庭成员坐在这一排的最左边。每两个坐在一起的家庭成员的年龄相差不得超过2岁。输入:整数N,1≤N≤55。输出:摄影师可以拍摄的照片数量。示例->输入:4,输出:4符合条件的数组:[1,2,3,4][1,2,4,3][1,3,2,4][1,3,4,2]另一个例子:输入:5输出:6符合条件的数组:[1,2,3,4,5][1,2,3,5,4][1,2,4,3,5][1,2,4,5,3][

  6. ruby-on-rails - 将 Ruby 中的日期/时间格式化为 YYYY-MM-DD HH :MM:SS - 2

    这个问题在这里已经有了答案:Railsformattingdate(4个答案)关闭4年前。我想格式化Time.Now函数以显示YYYY-MM-DDHH:MM:SS而不是:“2018-03-0909:47:19+0000”该函数需要放在时间中.现在功能。require‘roo’require‘roo-xls’require‘byebug’file_name=ARGV.first||“Template.xlsx”excel_file=Roo::Spreadsheet.open(“./#{file_name}“,extension::xlsx)xml=Nokogiri::XML::Build

  7. ruby - 我可以将我的 README.textile 以正确的格式放入我的 RDoc 中吗? - 2

    我喜欢使用Textile或Markdown为我的项目编写自述文件,但是当我生成RDoc时,自述文件被解释为RDoc并且看起来非常糟糕。有没有办法让RDoc通过RedCloth或BlueCloth而不是它自己的格式化程序运行文件?它可以配置为自动检测文件后缀的格式吗?(例如README.textile通过RedCloth运行,但README.mdown通过BlueCloth运行) 最佳答案 使用YARD直接代替RDoc将允许您包含Textile或Markdown文件,只要它们的文件后缀是合理的。我经常使用类似于以下Rake任务的东西:

  8. ruby - 是否有用于序列化和反序列化各种格式的对象层次结构的模式? - 2

    给定一个复杂的对象层次结构,幸运的是它不包含循环引用,我如何实现支持各种格式的序列化?我不是来讨论实际实现的。相反,我正在寻找可能会派上用场的设计模式提示。更准确地说:我正在使用Ruby,我想解析XML和JSON数据以构建复杂的对象层次结构。此外,应该可以将该层次结构序列化为JSON、XML和可能的HTML。我可以为此使用Builder模式吗?在任何提到的情况下,我都有某种结构化数据-无论是在内存中还是文本中-我想用它来构建其他东西。我认为将序列化逻辑与实际业务逻辑分开会很好,这样我以后就可以轻松支持多种XML格式。 最佳答案 我最

  9. ruby - 将 spawn() 的标准输出/标准错误重定向到 Ruby 中的字符串 - 2

    我想使用spawn(针对多个并发子进程)在Ruby中执行一个外部进程,并将标准输出或标准错误收集到一个字符串中,其方式类似于使用Python的子进程Popen.communicate()可以完成的操作。我尝试将:out/:err重定向到一个新的StringIO对象,但这会生成一个ArgumentError,并且临时重新定义$stdxxx会混淆子进程的输出。 最佳答案 如果你不喜欢popen,这是我的方法:r,w=IO.pipepid=Process.spawn(command,:out=>w,:err=>[:child,:out])

  10. ruby-on-rails - Prawn - 表格单元格内的链接 - 2

    我正在尝试用Prawn生成PDF。在我的PDF模板中,我有带单元格的表格。在其中一个单元格中,我有一个电子邮件地址:cell_email=pdf.make_cell(:content=>booking.user_email,:border_width=>0)我想让电子邮件链接到“mailto”链接。我知道我可以这样链接:pdf.formatted_text([{:text=>booking.user_email,:link=>"mailto:#{booking.user_email}"}])但是将这两行组合起来(将格式化文本作为内容)不起作用:cell_email=pdf.make_c

随机推荐