用 markdown 的逻辑写 R 脚本注释(2)

上回我抱怨没有现成的函数直接把 .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 文档。这个规则高明在哪里呢?

  1. 完全没有破坏 R 代码的基本规则。
  2. 常用的格式基本都囊括了。
  3. 照这个规则写的 R 代码赏心悦目。
  4. 够简单。前三条规则可以称为“约法三章”。照这三条来写注释就行了,第 4 第 5 条不用管,第 6 条很少用。

我用这个规则写了一个脚本,成功转换,欢喜得紧。

然而,唯一的不完美在于:章节标题。

mindr::r2rmd():将 .R 脚本转换成 .Rmd 文档

我常常用 RStudio 的快捷键 ctrl + shift + r 在 .R 脚本里生成下面这样格式的章节标签:

# Section label ------------

这样的话,在 RStudio 里会以大纲视图来显示章节标签,方便跳转。大纲视图在两处显示,即下图的右侧栏和底部按钮:

outline.jpg

很容易联想到,这个标签相当于 .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 脚本,以符合上面约定的规则。

天下终于太平了。

comments powered by Disqus