本教程介绍如何用 R Markdown 创建一份可复现的动态报告,把说明文字、R 代码、计算结果、图形和表格放在同一个源文件中。完成本教程后,你应该能够:
.Rmd 文件;knitr::kable() 创建清晰、可移植的表格;先澄清术语:knitr 是一个 R package;
kable() 是 knitr package
导出的函数,并不是另一个
package。需要合并表头、分组行等高级样式时,可以另选
kableExtra package,但本教程的示例不依赖它。
R Markdown 是一种纯文本源文件格式。分析说明和实际执行的代码保存在一起, 数据或代码改变后重新渲染,就能同步更新正文中的数字、图和表。
渲染流程
.Rmd 源文件 →
knitr 执行代码并生成 Markdown/图片 → Pandoc
转换格式 → HTML、Word、PDF 或其他输出
流程中的两个函数职责不同:
knitr::knit() 执行代码块,通常生成中间 Markdown
文档;rmarkdown::render() 协调 knitr、Pandoc
和输出格式,生成最终文档。R Markdown 适合数据分析报告、课程作业、实验记录、模型报告、自动化周期报告、 技术文档以及把分析过程交付给他人复现。
在 R 控制台中安装依赖。不要把自动执行的
install.packages() 放进正式报告,
否则一次渲染可能意外修改读者的 R package library。
可以点击 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 能找到它。
.Rmd 文件的组成一份 R Markdown 文档通常包含三个部分。
文件开头的 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,便于以 单个文件分享。
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
$$空行很重要:它用来分隔段落、列表和其他块级输出。
本节在文档内部创建所有对象,因此不依赖交互式 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)汽车重量越大,燃油经济性通常越低。
knitrknitr 是 R Markdown 的文学化编程(literate
programming)层:它识别代码块, 按顺序执行代码,捕获文本、图形、message
和 warning,再把结果写入中间文档。
文件开头隐藏的 setup 代码块使用
knitr::opts_chunk$set() 设置整份文档的
默认值。写在某个代码块头部的局部选项,只会覆盖该代码块的全局默认值。
## 虽然这个代码块隐藏了源代码,但计算结果仍然可见。
上面的代码仍被执行,因为 eval 还是
TRUE;echo=FALSE 只隐藏了源代码。
| 选项 | 作用 | 关键区别 |
|---|---|---|
| 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,确认理解其原因之后,再决定是否在最终报告 中隐藏。隐藏信息只是排版决定,不等于解决问题。
results='asis' 生成 Markdown普通字符输出会按控制台结果处理。results='asis' 告诉
knitr:这段可信文本已经是 Markdown 或 HTML,应直接作为文档内容解释。
Base R、ggplot2 等绘图系统产生的图会被 knitr
自动捕获。插入已有图片时, knitr::include_graphics()
会按照当前输出格式正确记录图片:
应使用相对项目路径。不要在报告中使用
setwd(),因为它会使路径行为更难预测。
缓存适合运行缓慢、确定性强、没有副作用的计算:
```{r fit-expensive-model, cache=TRUE, cache.extra=tools::md5sum("data/input.csv")}
model <- expensive_model(read.csv("data/input.csv"))
```
不要全局打开缓存。代码或代码块选项改变时,普通缓存会失效;但外部文件改变未必会
被自动发现。cache.extra
可以把文件哈希或其他版本标记加入缓存键。怀疑缓存过期
时,应清理缓存并重新执行。
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
还可以把代码块交给其他语言引擎,但相应引擎及其运行环境必须另行安装。
knitr::kable()kable() 把 data frame、matrix
等规则矩形数据转换为适合当前输出格式的简单表格。
它的功能刻意保持简洁,因此普通报告中的表格更容易阅读,也更容易在不同格式之间
迁移。
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 关闭;| 参数 | 控制内容 |
|---|---|
| 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 能使用输出格式专属功能,但会降低可移植性。
先完成统计汇总,再把干净的矩形对象交给 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 |
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 |
缺失值默认显示为 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 |
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 表格"
)| 萼片长 | 萼片宽 | 花瓣长 | 花瓣宽 | 物种 |
|---|---|---|---|---|
| 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 |
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 标记"
)| 指标 | 数值 |
|---|---|
| 平均油耗 | 20.1 |
| 最大油耗 | 33.9 |
绝不能把未经处理的用户输入直接交给
escape=FALSE。这样做可能破坏 页面结构,还可能造成 HTML
或脚本注入风险。
处于顶层时,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")
}| 车型 | 油耗 | 马力 | 重量 |
|---|---|---|---|
| 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 |
| 车型 | 油耗 | 马力 | 重量 |
|---|---|---|---|
| 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 |
| 车型 | 油耗 | 马力 | 重量 |
|---|---|---|---|
| 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() 组合。
本教程的 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,
" 英里/加仑的车型"
)
)| 车型 | 油耗 | 气缸数 | 马力 | 重量 |
|---|---|---|---|---|
| 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)渲染。
同一份源文件可以设置多种目标格式:
HTML 需要 Pandoc;PDF 通常还需要 LaTeX 发行版。只有确定不需要跨格式输出时, 才应大量使用 HTML 或 LaTeX 专属写法。
需要文献引用时,在 YAML 中加入
bibliography: references.bib,然后用
[@citation-key] 引用条目。Pandoc 会在生成最终文档时处理
bibliography 元数据。
warning=TRUE 并诊断;隐藏警告不是修复。setwd()。row.names=FALSE。col.names
长度必须等于实际显示的数据列数。print(kable(...)),并设置
results='asis'。escape=TRUE;只有可信且有意的标记 才能使用 HTML 格式和
escape=FALSE。分享之前,应在干净的 R session 中重新渲染。只有先在控制台手动创建对象后才能 成功的文档,还不能称为可复现报告。
.Rmd、数据和辅助文件放在同一个项目中;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