The V Lab
适合读者R Markdown 初学者
学习时间约 60–90 分钟
前置知识了解基础 R 更佳

1 学习目标

本教程介绍如何用 R Markdown 创建一份可复现的动态报告,把说明文字、R 代码、计算结果、图形和表格放在同一个源文件中。完成本教程后,你应该能够:

  • 用 YAML、Markdown 和 R 代码块组织 .Rmd 文件;
  • 使用 knitr 代码块选项(chunk options)控制代码和输出;
  • 把源文件渲染为可独立分享的 HTML;
  • 用 knitr::kable() 创建清晰、可移植的表格;
  • 识别缓存、路径、环境和表格格式中的常见问题。

先澄清术语:knitr 是一个 R package; kable() 是 knitr package 导出的函数,并不是另一个 package。需要合并表头、分组行等高级样式时,可以另选 kableExtra package,但本教程的示例不依赖它。

2 R Markdown 是什么

R Markdown 是一种纯文本源文件格式。分析说明和实际执行的代码保存在一起, 数据或代码改变后重新渲染,就能同步更新正文中的数字、图和表。

渲染流程
.Rmd 源文件 → knitr 执行代码并生成 Markdown/图片 → Pandoc 转换格式 → HTML、Word、PDF 或其他输出

流程中的两个函数职责不同:

  • knitr::knit() 执行代码块,通常生成中间 Markdown 文档;
  • rmarkdown::render() 协调 knitr、Pandoc 和输出格式,生成最终文档。

R Markdown 适合数据分析报告、课程作业、实验记录、模型报告、自动化周期报告、 技术文档以及把分析过程交付给他人复现。

3 安装一次,重复渲染

在 R 控制台中安装依赖。不要把自动执行的 install.packages() 放进正式报告, 否则一次渲染可能意外修改读者的 R package library。

install.packages(c("rmarkdown", "knitr"))

可以点击 RStudio 的 Knit 按钮,也可以在 R 控制台运行:

rmarkdown::render(
  "r_markdown_guide_zh.Rmd",
  output_file = "r_markdown_guide_zh.html",
  encoding = "UTF-8"
)

渲染还需要 Pandoc。RStudio 已经自带 Pandoc;未使用 RStudio 时,需要单独安装 Pandoc 并让 rmarkdown 能找到它。

4 .Rmd 文件的组成

一份 R Markdown 文档通常包含三个部分。

4.1 1. YAML 前置元数据

文件开头的 YAML 保存标题、参数和输出设置。缩进必须使用空格,不能用制表符。

---
title: "分析报告标题"
author: "分析者姓名"
date: "2026-08-11"
params:
  min_mpg: 20
output:
  html_document:
    toc: true
    number_sections: true
    code_folding: show
    self_contained: true
---

HTML 常用选项包括目录(toc)、章节编号、语法高亮、浮动目录和代码折叠。 self_contained: true 会把本地 CSS、JavaScript 和生成的图嵌入 HTML,便于以 单个文件分享。

4.2 2. Markdown 正文

Markdown 用少量符号表达常见排版:

# 一级标题
## 二级标题

**粗体**、*斜体*和`行内代码`

- 无序项目
- 另一个项目

1. 有序项目
2. 另一个项目

[R Markdown 文档](https://pkgs.rstudio.com/rmarkdown/)

> 这是一段引用。

行内公式:$\bar{x} = \frac{1}{n}\sum_{i=1}^{n}x_i$

独立公式:
$$
s^2 = \frac{1}{n-1}\sum_{i=1}^{n}(x_i-\bar{x})^2
$$

空行很重要:它用来分隔段落、列表和其他块级输出。

4.3 3. R 代码块与行内 R 代码

R 代码块以三个反引号和 {r} 开始,以三个反引号结束。每个实际执行的代码块 都应使用唯一且有意义的标签。


``` r
summary(mtcars$mpg)
```

```
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##   10.40   15.43   19.20   20.09   22.80   33.90
```

行内 R 代码把短小的计算结果直接插入句子。例如,mtcars 数据有 32 行,平均油耗为 20.1 英里/加仑。 行内表达式应保持简短,并且只能使用文档中更早创建的对象。

5 一个可复现的小型分析

本节在文档内部创建所有对象,因此不依赖交互式 Global Environment 中遗留的 变量。

cars_data <- data.frame(
  Model = rownames(mtcars),
  mtcars[, c("mpg", "cyl", "hp", "wt")],
  row.names = NULL,
  check.names = FALSE
)

names(cars_data) <- c("Model", "MPG", "Cylinders", "Horsepower", "Weight")
head(cars_data)
Model MPG Cylinders Horsepower Weight
Mazda RX4 21.0 6 110 2.620
Mazda RX4 Wag 21.0 6 110 2.875
Datsun 710 22.8 4 93 2.320
Hornet 4 Drive 21.4 6 110 3.215
Hornet Sportabout 18.7 8 175 3.440
Valiant 18.1 6 105 3.460

下面的图会在 knitting 时生成。fig.cap 添加图题;fig.width 控制绘图设备 尺寸(通常以英寸为单位),out.width 则控制图片在 HTML 页面中的显示宽度。

plot(
  mtcars$wt,
  mtcars$mpg,
  xlab = "重量(1000 磅)",
  ylab = "油耗(英里/加仑)",
  pch = 19,
  col = "#2c7fb8"
)

weight_fit <- lm(mpg ~ wt, data = mtcars)
abline(weight_fit, col = "#d95f0e", lwd = 2)
每加仑英里数与汽车重量的散点图,并带有拟合回归直线。

汽车重量越大,燃油经济性通常越低。

6 深入理解 knitr

knitr 是 R Markdown 的文学化编程(literate programming)层:它识别代码块, 按顺序执行代码,捕获文本、图形、message 和 warning,再把结果写入中间文档。

6.1 全局选项与局部选项

文件开头隐藏的 setup 代码块使用 knitr::opts_chunk$set() 设置整份文档的 默认值。写在某个代码块头部的局部选项,只会覆盖该代码块的全局默认值。

## 虽然这个代码块隐藏了源代码,但计算结果仍然可见。

上面的代码仍被执行,因为 eval 还是 TRUE;echo=FALSE 只隐藏了源代码。

6.2 常用代码块选项

常用 knitr 代码块选项
选项 作用 关键区别
echo 显示或隐藏源代码 echo = FALSE 时代码仍然执行
eval 执行或跳过代码 eval = FALSE 只显示代码而不执行
include 是否纳入代码及全部捕获输出 include = FALSE 仍执行,但代码和结果都隐藏
results 控制文本结果:markup、asis、hide 或 hold results = ‘asis’ 把生成文本当作文档标记
message 是否显示代码产生的消息 message 与 warning 是不同的条件
warning 是否显示代码产生的警告 隐藏 warning 并没有修复其原因
error 出现错误后是否允许 knitting 继续 应明确处理错误,不要掩盖失败的分析
fig.width / fig.height 设置绘图设备尺寸,通常单位为英寸 它不等同于 HTML 显示尺寸
out.width 设置图片在最终输出中的显示宽度 HTML 常使用 ‘90%’ 这样的百分比
fig.align 把图形设为左对齐、居中或右对齐 作用于渲染后的图形
fig.cap 添加图题 让图形输出更容易理解
cache 复用未变化代码块的已保存结果 外部文件依赖需要特别处理

最容易混淆的三个选项是:

  • echo=FALSE:执行代码,隐藏源代码,保留结果;
  • eval=FALSE:显示源代码,但不执行,因此没有计算结果;
  • include=FALSE:执行代码,但隐藏源代码和所有捕获结果。

开发阶段应先保留 message 和 warning,确认理解其原因之后,再决定是否在最终报告 中隐藏。隐藏信息只是排版决定,不等于解决问题。

6.3 用 results='asis' 生成 Markdown

普通字符输出会按控制台结果处理。results='asis' 告诉 knitr:这段可信文本已经是 Markdown 或 HTML,应直接作为文档内容解释。

cat("### 由 R 动态生成的标题\n\n")

6.3.1 由 R 动态生成的标题

cat("这句话是在 knitting 过程中生成的。\n")

这句话是在 knitting 过程中生成的。

未经清理的用户输入不能直接作为原始 HTML 输出。

6.4 图形与外部图片

Base R、ggplot2 等绘图系统产生的图会被 knitr 自动捕获。插入已有图片时, knitr::include_graphics() 会按照当前输出格式正确记录图片:

knitr::include_graphics(
  "figures/model-diagnostics.png",
  auto_pdf = TRUE
)

应使用相对项目路径。不要在报告中使用 setwd(),因为它会使路径行为更难预测。

6.5 缓存耗时计算

缓存适合运行缓慢、确定性强、没有副作用的计算:

```{r fit-expensive-model, cache=TRUE, cache.extra=tools::md5sum("data/input.csv")}
model <- expensive_model(read.csv("data/input.csv"))
```

不要全局打开缓存。代码或代码块选项改变时,普通缓存会失效;但外部文件改变未必会 被自动发现。cache.extra 可以把文件哈希或其他版本标记加入缓存键。怀疑缓存过期 时,应清理缓存并重新执行。

6.6 其他常用 knitr 工具

  • knitr::knit():创建 knitting 后的中间文档;
  • knitr::purl():从 .Rmd 提取 R 代码并生成 .R 脚本;
  • knitr::include_graphics():插入外部图形;
  • knitr::knit_child():把可复用的子文档组合进主报告;
  • knitr::opts_knit:控制 knitting 过程本身,不要与控制代码块的 knitr::opts_chunk 混淆;
  • knitr::knit_hooks 和 knitr::opts_hooks:用于高级输出定制。

knitr 还可以把代码块交给其他语言引擎,但相应引擎及其运行环境必须另行安装。

7 深入理解 knitr::kable()

kable() 把 data frame、matrix 等规则矩形数据转换为适合当前输出格式的简单表格。 它的功能刻意保持简洁,因此普通报告中的表格更容易阅读,也更容易在不同格式之间 迁移。

7.1 基础表格

cars_table <- cars_data[1:8, ]

knitr::kable(
  cars_table,
  digits = 2,
  row.names = FALSE,
  col.names = c(
    "车型", "油耗(英里/加仑)", "气缸数", "马力", "重量(1000 磅)"
  ),
  align = "lrrrr",
  caption = "前八种车型的部分变量"
)
前八种车型的部分变量
车型 油耗(英里/加仑) 气缸数 马力 重量(1000 磅)
Mazda RX4 21.0 6 110 2.62
Mazda RX4 Wag 21.0 6 110 2.88
Datsun 710 22.8 4 93 2.32
Hornet 4 Drive 21.4 6 110 3.21
Hornet Sportabout 18.7 8 175 3.44
Valiant 18.1 6 105 3.46
Duster 360 14.3 8 245 3.57
Merc 240D 24.4 4 62 3.19

这个示例体现了报告表格的实用默认值:

  • 行名没有实际含义时,用 row.names=FALSE 关闭;
  • 把程序中的变量名改成易读的显示列名;
  • 文本左对齐,数值右对齐;
  • 只改变显示精度,不修改原始数据;
  • 使用简短、明确的表题。

7.2 主要参数

kable() 的重要参数
参数 控制内容
x 规则矩形 data frame 或 matrix;部分格式也支持它们的 list
format 表格语法,例如 pipe、simple、html、latex 或 rst
digits 传给 round() 的舍入位数;向量会在表格的全部列上循环使用
row.names 是否显示行名
col.names 用于显示并替代输入列名的标签
align 列对齐方式:l、c 或 r
caption 随表格显示的表题
label 表格标识;能否引用取决于输出框架
format.args 传给 format() 的 list,例如 big.mark 和 scientific
escape 是否转义 HTML 或 LaTeX 特殊字符
… 格式专属设置,例如 HTML 的 table.attr

通常应省略 format,让 knitr 自动选择。pipe 和 simple 是可移植的 Markdown 格式;html 和 latex 能使用输出格式专属功能,但会降低可移植性。

7.3 分组汇总表

先完成统计汇总,再把干净的矩形对象交给 kable()。

cylinder_values <- sort(unique(mtcars$cyl))

cylinder_summary <- data.frame(
  Cylinders = cylinder_values,
  N = vapply(
    cylinder_values,
    function(value) sum(mtcars$cyl == value),
    numeric(1)
  ),
  Mean_MPG = vapply(
    cylinder_values,
    function(value) mean(mtcars$mpg[mtcars$cyl == value]),
    numeric(1)
  ),
  SD_MPG = vapply(
    cylinder_values,
    function(value) sd(mtcars$mpg[mtcars$cyl == value]),
    numeric(1)
  )
)

knitr::kable(
  cylinder_summary,
  digits = c(0, 0, 2, 2),
  row.names = FALSE,
  col.names = c("气缸数", "样本量", "平均油耗", "油耗标准差"),
  align = "rrrr",
  caption = "按气缸数汇总燃油经济性"
)
按气缸数汇总燃油经济性
气缸数 样本量 平均油耗 油耗标准差
4 11 26.66 4.51
6 7 19.74 1.45
8 14 15.10 2.56

7.4 分列舍入与数字格式

digits 控制显示时的舍入,但它本身不保证补齐末尾的零。digits 向量会在表格的 全部列上循环使用,其中数值列会传给 round()。format.args 会传给 base R 的 format(),可添加千位分隔符或避免科学记数法。这两个参数都不会改变输入数据框 中的实际数值。

numeric_example <- data.frame(
  Rate = c(0.12345, 0.98765),
  Count = c(1250, 98340),
  Amount = c(15230.4, 824500.8)
)

knitr::kable(
  numeric_example,
  digits = c(3, 0, 2),
  format.args = list(big.mark = ",", scientific = FALSE),
  row.names = FALSE,
  col.names = c("比率", "数量", "金额"),
  align = "rrr",
  caption = "分列舍入与千位分隔符"
)
分列舍入与千位分隔符
比率 数量 金额
0.123 1,250 15,230.4
0.988 98,340 824,500.8

7.5 控制缺失值显示

缺失值默认显示为 NA。全局 option knitr.kable.NA 可以改变显示方式。使用前应 保存旧 option,表格生成后再恢复,以免意外影响后续表格。

missing_example <- data.frame(
  学生 = c("甲", "乙", "丙"),
  成绩 = c(87, NA, 91),
  check.names = FALSE
)

old_options <- options(knitr.kable.NA = "—")

knitr::kable(
  missing_example,
  row.names = FALSE,
  caption = "用破折号显示缺失值"
)
用破折号显示缺失值
学生 成绩
甲 87
乙 —
丙 91
options(old_options)

7.6 用 table.attr 设置 HTML 样式

如果文档只输出 HTML,可以用 format="html" 和 table.attr 添加 CSS class。 这种写法不如格式无关的表格容易迁移到 PDF 或 Word。

knitr::kable(
  head(iris, 8),
  format = "html",
  digits = 1,
  row.names = FALSE,
  col.names = c("萼片长", "萼片宽", "花瓣长", "花瓣宽", "物种"),
  table.attr = 'class="table kable-custom"',
  caption = "使用 CSS class 设置样式的 HTML 表格"
)
使用 CSS class 设置样式的 HTML 表格
萼片长 萼片宽 花瓣长 花瓣宽 物种
5.1 3.5 1.4 0.2 setosa
4.9 3.0 1.4 0.2 setosa
4.7 3.2 1.3 0.2 setosa
4.6 3.1 1.5 0.2 setosa
5.0 3.6 1.4 0.2 setosa
5.4 3.9 1.7 0.4 setosa
4.6 3.4 1.4 0.3 setosa
5.0 3.4 1.5 0.2 setosa

7.7 escape:默认安全转义

默认 escape=TRUE 会转义对 HTML 或 LaTeX 有特殊含义的字符,保护文档结构。 只有在内容由作者控制并且有意生成标记时,才应设置 escape=FALSE。

trusted_summary <- data.frame(
  指标 = c("平均油耗", "最大油耗"),
  数值 = c(
    sprintf("<strong>%.1f</strong>", mean(mtcars$mpg)),
    sprintf("<em>%.1f</em>", max(mtcars$mpg))
  ),
  check.names = FALSE
)

knitr::kable(
  trusted_summary,
  format = "html",
  escape = FALSE,
  row.names = FALSE,
  caption = "由作者生成并确认可信的 HTML 标记"
)
由作者生成并确认可信的 HTML 标记
指标 数值
平均油耗 20.1
最大油耗 33.9

绝不能把未经处理的用户输入直接交给 escape=FALSE。这样做可能破坏 页面结构,还可能造成 HTML 或脚本注入风险。

7.8 在循环中生成多张表

处于顶层时,kable() 的结果通常会自动打印;进入循环或函数后,必须显式调用 print()。同时使用 results='asis',并用空行或 HTML 注释分隔表格,让 Pandoc 正确识别每一个块。

for (number_of_cylinders in sort(unique(mtcars$cyl))) {
  subset_rows <- mtcars[
    mtcars$cyl == number_of_cylinders,
    c("mpg", "hp", "wt"),
    drop = FALSE
  ]

  subset_rows <- head(subset_rows, 4)
  subset_rows <- data.frame(
    Model = rownames(subset_rows),
    subset_rows,
    row.names = NULL,
    check.names = FALSE
  )

  print(knitr::kable(
    subset_rows,
    format = "pipe",
    digits = 2,
    row.names = FALSE,
    col.names = c("车型", "油耗", "马力", "重量"),
    caption = paste(number_of_cylinders, "缸车型示例")
  ))

  cat("\n\n<!-- table separator -->\n\n")
}
4 缸车型示例
车型 油耗 马力 重量
Datsun 710 22.8 93 2.32
Merc 240D 24.4 62 3.19
Merc 230 22.8 95 3.15
Fiat 128 32.4 66 2.20
6 缸车型示例
车型 油耗 马力 重量
Mazda RX4 21.0 110 2.62
Mazda RX4 Wag 21.0 110 2.88
Hornet 4 Drive 21.4 110 3.21
Valiant 18.1 105 3.46
8 缸车型示例
车型 油耗 马力 重量
Hornet Sportabout 18.7 175 3.44
Duster 360 14.3 245 3.57
Merc 450SE 16.4 180 4.07
Merc 450SL 17.3 180 3.73

需要一次传入多张表时,可使用 knitr::kable(list(table1, table2))。如果每张表的 参数不同,可分别创建 kable() 对象,再用 knitr::kables() 组合。

7.9 何时换用其他工具

kable() 的设计目标是简单矩形表格。以下需求适合选择其他工具:

  • kableExtra:跨列表头、分组行、脚注以及更细致的 HTML/LaTeX 样式;
  • DT:带排序、搜索和分页的交互式 HTML 表格;
  • gt、flextable 等:更丰富的出版级表格排版。

普通 html_document 可以显示 caption,但自动编号和稳定的交叉引用通常需要 bookdown 等支持交叉引用的输出框架。原始 HTML 表格代码也不会自动转换成适合 Word 或 PDF 的样式。

8 参数化报告

本教程的 YAML 在 params 下面定义了 min_mpg。在 R 代码中用 params$min_mpg 读取它,就能用一份源文件为不同阈值、地区、日期或客户生成 不同报告。

selected_cars <- subset(cars_data, MPG >= params$min_mpg)

knitr::kable(
  selected_cars,
  digits = 2,
  row.names = FALSE,
  col.names = c("车型", "油耗", "气缸数", "马力", "重量"),
  caption = paste0(
    "油耗不低于 ",
    params$min_mpg,
    " 英里/加仑的车型"
  )
)
油耗不低于 20 英里/加仑的车型
车型 油耗 气缸数 马力 重量
Mazda RX4 21.0 6 110 2.62
Mazda RX4 Wag 21.0 6 110 2.88
Datsun 710 22.8 4 93 2.32
Hornet 4 Drive 21.4 6 110 3.21
Merc 240D 24.4 4 62 3.19
Merc 230 22.8 4 95 3.15
Fiat 128 32.4 4 66 2.20
Honda Civic 30.4 4 52 1.61
Toyota Corolla 33.9 4 65 1.83
Toyota Corona 21.5 4 97 2.46
Fiat X1-9 27.3 4 66 1.94
Porsche 914-2 26.0 4 91 2.14
Lotus Europa 30.4 4 113 1.51
Volvo 142E 21.4 4 109 2.78

渲染时可以覆盖参数:

rmarkdown::render(
  "r_markdown_guide_zh.Rmd",
  output_file = "r_markdown_guide_zh_mpg22.html",
  params = list(min_mpg = 22),
  envir = new.env(parent = globalenv()),
  encoding = "UTF-8"
)

使用新的执行环境可避免把报告创建的对象留在 Global Environment。若要做更严格的 可复现性检查,应从全新的 R session(例如通过 Rscript)渲染。

9 输出格式与引用文献

同一份源文件可以设置多种目标格式:

output:
  html_document: default
  word_document: default
  pdf_document: default

HTML 需要 Pandoc;PDF 通常还需要 LaTeX 发行版。只有确定不需要跨格式输出时, 才应大量使用 HTML 或 LaTeX 专属写法。

需要文献引用时,在 YAML 中加入 bibliography: references.bib,然后用 [@citation-key] 引用条目。Pandoc 会在生成最终文档时处理 bibliography 元数据。

10 常见问题与实用规则

  1. Knit 时提示找不到对象。 所有对象都应在文档内部创建或导入,并按执行顺序 排列。
  2. YAML 无法解析。 使用空格而不是 Tab,并对齐同一层级的 key。
  3. 出现重复标签错误。 每个代码块都要有唯一标签。
  4. 最终报告看不到 warning。 临时恢复 warning=TRUE 并诊断;隐藏警告不是修复。
  5. 交互运行时路径正常,Knit 时失败。 使用稳定的项目相对路径,避免 setwd()。
  6. 缓存结果已经过期。 外部数据、配置或 package 版本改变时,应使缓存失效。
  7. 表格多出意外的第一列。 明确设置 row.names=FALSE。
  8. 显示列名数量不匹配。 col.names 长度必须等于实际显示的数据列数。
  9. 循环中的表格没有出现。 使用 print(kable(...)),并设置 results='asis'。
  10. HTML 标签被原样显示。 保持安全默认值 escape=TRUE;只有可信且有意的标记 才能使用 HTML 格式和 escape=FALSE。

分享之前,应在干净的 R session 中重新渲染。只有先在控制台手动创建对象后才能 成功的文档,还不能称为可复现报告。

11 可复现性检查清单

  • 把 .Rmd、数据和辅助文件放在同一个项目中;
  • 使用相对路径并记录 package 依赖;
  • 含随机过程的示例应设置随机种子;
  • knitting 过程中避免安装操作和不受控副作用;
  • 使用信息明确且唯一的代码块标签;
  • 在干净环境中完成最终渲染;
  • 遇到版本相关问题时,保留 sessionInfo() 便于诊断。
sessionInfo()
## R version 4.6.1 (2026-06-24)
## Platform: aarch64-apple-darwin23
## Running under: macOS Tahoe 26.5.1
##
## Matrix products: default
## BLAS:   /Library/Frameworks/R.framework/Versions/4.6/Resources/lib/libRblas.0.dylib
## LAPACK: /Library/Frameworks/R.framework/Versions/4.6/Resources/lib/libRlapack.dylib;  LAPACK version 3.12.1
##
## locale:
## [1] C.UTF-8/C.UTF-8/C.UTF-8/C/C.UTF-8/C.UTF-8
##
## time zone: America/Edmonton
## tzcode source: internal
##
## attached base packages:
## [1] stats     graphics  grDevices utils     datasets  methods   base
##
## loaded via a namespace (and not attached):
##  [1] digest_0.6.39   R6_2.6.1        fastmap_1.2.0   xfun_0.60       cachem_1.1.0
##  [6] knitr_1.51      htmltools_0.5.9 rmarkdown_2.31  lifecycle_1.0.5 cli_3.6.6
## [11] sass_0.4.10     jquerylib_0.1.4 compiler_4.6.1  tools_4.6.1     evaluate_1.0.5
## [16] bslib_0.12.0    yaml_2.3.12     rlang_1.3.0     jsonlite_2.0.0