Quarto 入门:R Markdown 的下一站
R Markdown 用久了会碰到几处别扭:块选项挤在花括号里越写越长、交叉引用要额外装包、Python 和 R 想放进同一份文档得绕一圈。Quarto 是 Posit 对这些问题的正面回应。它不是把 R Markdown 推倒重来,而是把同一套写作模型抽出来重写成独立工具。
和 R Markdown 是什么关系
Section titled “和 R Markdown 是什么关系”共同点比差异多:YAML 元数据、三个反引号的代码块、行内代码、knitr 引擎跑 R、Pandoc 负责最终转换。你不必从头学一门新标记语言,/r/reporting/rmarkdown-basics/ 里讲的 YAML 头、行内代码、渲染流程,在 Quarto 里几乎原样成立。
差异是结构性的。Quarto 是一个独立的命令行程序,不依附 R 包,所以同一套工具也能跑 Python 和 Julia;块选项从花括号挪进块内的注释行;交叉引用、多格式输出、项目配置全部内置,不用再装 bookdown 之类的扩展。
「Quarto 会取代 R Markdown 吗」,实际的答案是:Rmd 仍然可以渲染,没有停掉支持的时间表,但新特性只进 Quarto。新项目选 Quarto 不需要犹豫,旧项目也不必专门做迁移——哪天要重排格式时顺手改掉就行。
format 字段
Section titled “format 字段”output 在 Quarto 里叫 format,而且可以一次声明多个:
---title: "齿生长数据报告"author: "张三"date: "2026-01-06"format: html: toc: true code-fold: true docx: default---一次渲染同时得到网页和 Word 两份成品,default 表示用默认设置。HTML 端常用的几个键:toc: true 生成目录,code-fold: true 把代码折叠起来、读者想看点开,theme 换配色,number-sections: true 给章节自动编号。
PDF 仍然走 LaTeX,中文配置和 R Markdown 里同一套思路:把引擎换成 XeLaTeX,再指定中文字体。
format: pdf: pdf-engine: xelatex CJKmainfont: "Noto Serif CJK SC"注意键名是 pdf-engine(连字符),这是 Pandoc 参数的原名,和 R Markdown 里 latex_engine(下划线)不同,迁移时最容易漏掉这一处。
代码块选项:从花括号到注释行
Section titled “代码块选项:从花括号到注释行”R Markdown 把选项塞在块头:三个反引号后面紧跟 {r, echo=FALSE, fig.width=6}。Quarto 的块头只留引擎,选项逐行写在块内,以 #| 开头:
#| label: fig-dose#| echo: false#| fig-cap: "剂量与齿长的关系"#| fig-width: 6#| fig-height: 4
boxplot(len ~ dose, data = ToothGrowth, xlab = "剂量 (mg)", ylab = "齿长", col = "steelblue")对照关系很规整:echo=FALSE 对应 #| echo: false,fig.width=6 对应 #| fig-width: 6,fig.cap="..." 对应 #| fig-cap: "..."。规律是点号换成连字符、等号换成冒号,值的语法从 R 表达式变成 YAML,所以布尔值要写小写的 true/false,写 TRUE 会报解析错误。
这种写法的好处是长选项列表不再挤成一行,也不会因为漏了一个逗号整块失败;#| 开头的行会被 knitr 当作选项解析,不会出现在输出的代码里。
Quarto 原生支持交叉引用,这是相对 R Markdown 最省事的一处。规则是给块起一个带类型前缀的标签,正文里用 @ 引用:
fig-图、tbl-表、eq-公式、sec-章节- 正文里写
@fig-dose,渲染后自动变成「图 1」,编号按出现顺序重排,中间插一张图不用手工改后面所有编号 - 表格用
#| label: tbl-agg搭配#| tbl-cap: "描述",正文引用写@tbl-agg
前缀写错是最常见的失败方式:标签写成 dose-plot,或者引用时漏了 fig-,@fig-dose 就原样渲染成文本,不报错,只是不变成编号。渲染完扫一眼正文里还有没有残留的 @,能立刻发现。
一份能跑的最小文档,YAML 头加正文段落:
---title: "PlantGrowth 组间差异"format: html---正文里写「三组均值是否存在差异,方差分析结果见表 @tbl-aov」,接着是代码块:
#| label: tbl-aov#| tbl-cap: "单因素方差分析:weight ~ group"#| echo: true
data(PlantGrowth)summary(aov(weight ~ group, data = PlantGrowth))渲染进文档的表格内容是:
Df Sum Sq Mean Sq F value Pr(>F)group 2 3.766 1.8832 4.846 0.0159 *Residuals 27 10.492 0.3886---Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1F = 4.846、p = 0.0159 说明三组均值至少有一组和其他组不同。p 值的含义是:在「三组均值完全相等」这个原假设成立的前提下,观察到当前数据或更极端情况的概率,小于 0.05 通常就认为差异有统计学意义。至于具体是哪两组有差异,还需要事后多重比较,比如 TukeyHSD(aov(weight ~ group, data = PlantGrowth))。
要把这份报告换成 PDF,只改 YAML 里的 format: html 为 format: pdf,代码一个字不动。
引擎决定谁在跑:knitr 引擎以 R 为主,Python 代码块通过 reticulate 交给 Python 执行;jupyter 引擎反过来以 Python 为主。同一份文档里可以两种语言都有:
import numpy as np
x = np.array([4.2, 11.5, 7.3, 5.8])print(x.mean())7.2同样四个数在 R 里是 mean(c(4.2, 11.5, 7.3, 5.8)),输出 [1] 7.2:值一致,显示格式不同,一个带 [1] 索引前缀,一个不带。跨语言协作通常就停在这一步——R 做统计、Python 画图或接模型,结果写进同一份报告,而不是来回导 CSV。
混排有真实代价:reticulate 需要先指定用哪个 Python 环境(reticulate::use_virtualenv() 或 use_condaenv()),两台机器的 Python 路径一旦不一致,文档就只在你本机跑得通。Python 只是零星几行,值得;两种语言各占一半,建议拆成两个项目,用文件交换数据。
单篇文档靠自己的 YAML 头,一个项目(比如整门课的实验报告)可以在根目录放一份 _quarto.yml,项目内所有文档共享:
project: type: website output-dir: _site
format: html: theme: cosmo toc: true code-fold: trueoutput-dir 把渲染产物统一收进 _site,源文件目录保持干净。单篇文档只写自己特有的字段,公共设置不必重复。项目模式下 Quarto 会记录文档间的依赖,公共配置改动后重新渲染时,受影响的文档会一起更新。
$ quarto render report.qmd$ quarto render report.qmd --to docx$ quarto preview report.qmdrender 生成成品;加 --to docx 临时指定格式,不改动 YAML。preview 会起一个本地服务,保存文件后浏览器自动刷新,写长报告时挂着它效率最高。Quarto 是独立程序,要单独安装;想在 R 里调用,可以用 quarto 包的 quarto_render(),CLI 路径由这个包自动探测。
RStudio 用户不用记命令,.qmd 文件上方的 Render 按钮等价于 quarto render。
output:改成format:,latex_engine改成pdf-engine- 块头
{r, echo=FALSE}拆成#| echo: false,逐行写在块内 fig.cap=改成#| fig-cap:,选项名里的点号一律换连字符- 图表的
\@ref(fig:xxx)改成@fig-xxx,bookdown 老用户改得最多 rmarkdown::render()换成quarto render;Rmd 文件不用改名,但 Quarto 项目默认只渲染 .qmd- 报错的文档先单独渲染一遍,多数问题出在 YAML 缩进和布尔值的大小写上
从 R Markdown 过来的人,第一天就能上手;真正花时间的是把散落在各篇文档里的排版设置收进 _quarto.yml,让格式由项目统一决定,而不是每篇各写一份。