来自 赵鹏 | November 16, 2018
上回我抱怨没有现成的函数直接把 .R 脚本转换成 .Rmd 文档,于是自作聪明写了一个,结果很快 yihui 就留言说,有啊,knitr::spin()
就可以。我就去研究了一下这个函数,发现比我写的那个要漂亮得多。
我的心情很复杂。
话说诗仙李白当年游山玩水,看见什么就写什么,留下的诗篇个个照耀千古。有一天,他来到了黄鹤楼,照例诗兴大发,正待提笔,突然看见了墙上有前辈崔颢题写的一首七律:
昔人已乘黄鹤去,此地空余黄鹤楼。
黄鹤一去不复返,白云千载空悠悠。
晴川历历汉阳树,芳草萋萋鹦鹉洲。
日暮乡关何处是,烟波江上使人愁。
李白又惊又喜,提笔想写,却发现根本写不出来。又好笑又好气,李白终于憋出了四句:
一拳捶碎黄鹤楼,一脚踢翻鹦鹉洲。
眼前有景道不得,崔颢题诗在上头。
莫要笑我拿李白往自己脸上贴金,嗯,内心就是那种想拳捶脚踢的冲动。
我常常懊恼没有时间做调研,如果事先找到巨人,再爬到他肩上会看得有多远。现在,巨人来了,我打算爬上去看一看。
knitr::spin()
的巨人之肩
knitr::spin()
的官方文档 给出了一个示例,详细介绍了用 markdown 的逻辑来写 .R 代码的策略,而这个示例本身就是个可以转换成 .Rmd 文档的 .R 脚本。转换规则我归纳了一下,大致有六条:
.R 脚本书写规则 | spin(.R脚本) 得到的 .Rmd 文档 |
---|---|
1. #' 开头的注释行 |
成为正文文字 |
2. #+ 开头的注释行 |
成为代码块的选项(chunk options) |
3. 双重花括号(如{{mean(x)}} ) |
成为行内代码(如`r mean(x)` ) |
4. 代码行 | 前后添加一对连续的三个反引号,成为代码块 |
5. # 开头的普通注释行 |
保留不变,成为代码块里的注释 |
6. # /* 与 # */ 之间的部分 |
跳过,不进入 .Rmd |
只要按上表的第一列规则写 .R 代码,就可以一键转换成 .Rmd 文档。这个规则高明在哪里呢?
- 完全没有破坏 R 代码的基本规则。
- 常用的格式基本都囊括了。
- 照这个规则写的 R 代码赏心悦目。
- 够简单。前三条规则可以称为“约法三章”。照这三条来写注释就行了,第 4 第 5 条不用管,第 6 条很少用。
我用这个规则写了一个脚本,成功转换,欢喜得紧。
然而,唯一的不完美在于:章节标题。
mindr::r2rmd()
:将 .R 脚本转换成 .Rmd 文档
我常常用 RStudio 的快捷键 ctrl + shift + r 在 .R 脚本里生成下面这样格式的章节标签:
# Section label ------------
这样的话,在 RStudio 里会以大纲视图来显示章节标签,方便跳转。大纲视图在两处显示,即下图的右侧栏和底部按钮:
很容易联想到,这个标签相当于 .Rmd 里的章节标题。如果在 .R 向 .Rmd 转换的时候,能把这个映射过去就好了。
这好办,在 knitr::spin()
的基础上,我新写了个 mindr::r2rmd()
函数,增补了规则 7:
.R 脚本书写规则 | 经spin(.R脚本) 转换得到的 .Rmd 文档 |
---|---|
7. #= 开头的注释行 |
成为章节标题 |
例如,#= ## Section 1.1 ----
经过 mindr::r2rmd()
转换后,去掉了头尾,得到的是## Section 1.1
。
也许有人会问:为啥不直接用约法三章的第一条,#' ## Section 1.1 ----
来写章节标签呢?少一条规则不更好吗?
我原先也是这么想的,后来发现,#'
开头的章节标签在RStudio 是不会以大纲视图来显示的。
这个问题我觉得可以择吉日提交给 RStudio。目前暂时按规则 7 行事吧。
这里有一个按上述 7 条规则写的 .R 脚本示例。
mindr::rmd2r()
: 将 .Rmd 文档转换成 .R 脚本
上回说过,knitr::purl()
可以将 .Rmd 文档转换成 .R 脚本。乍一看以为它和 knitr::spin()
互为反函数,但是仔细一看,并非如此,例如 chunk options 和 {{code}} 就没按原来的格式还原回去。现在,一个 .R 脚本经过上面的 mindr::r2rmd()
一折腾,得到的 .Rmd 更是没法原样转换回到原来的 .R 脚本了。
怎么办?我新写了个 mindr::rmd2r()
函数。它跟 mindr::r2rmd()
互为逆操作,目前看算是比较严格的互换:
.Rmd 文档格式 | 经r2rmd(.Rmd文档) 转换得到的 .R 脚本 |
---|---|
1. 正文文字 | 成为#' 开头的注释行 |
2. 代码块的选项(chunk options) | 成为#+ 开头的注释行 |
3. 行内代码(如`r mean(x)` ) |
成为双重花括号(如{{mean(x)}} ) |
4. 代码块 | 成为代码行 |
5. 代码块里的注释 | 成为 # 开头的普通注释行 |
6. <!-- 和--> 包围的内容 |
忽略,不进入 .R 脚本 |
7. 章节标题 | 成为 #= 开头、---- 结尾的章节标签 |
跟前面的表格算是一一对应。
这样一来,一对儿 .R 脚本和 .Rmd 文档,只需维护其中任意一个,就能直接更新到另一个里了。
mindr::r2mm()
和mindr::mm2r()
:.R 脚本和思维导图的相互转换
上面说的,纯属 mindr 狗拿耗子多管闲事。mindr, 本来是做思维导图的。
然而,这一切都是做思维导图的伏笔。
既然我们按上面表格约定的规则来写 .R 脚本,那么先mindr::r2rmd()
再mindr::md2mm()
,两步就可以把脚本里的大纲提取出来,生成思维导图了。我把这两步打了个包,新写了函数叫 r2mm()
,直接从 .R 脚本生成思维导图。当然,反过来就是 mm2r()
,从思维导图来创建一个 .R 脚本,以符合上面约定的规则。
天下终于太平了。