knitr与可重复的统计研究(花絮篇)

2010年年底我写了章,关于Sweave/LyX/pgfSweave,顺便引出可重复研究(Reproducible Research)的概念。一年过后,我逐渐意识到这一系列基于Sweave的工具都有致命的设计缺陷,束缚感越来越强,屡屡冒出要重复造轮子的想法。于是就在“造乎?不造乎?”的犹豫中最终痛下决心全盘重造,knitr包就诞生了。在第五届中国R语言会议上魏太云已经对它作了初步介绍,我会在统计之都以系列文章全面介绍它,本篇先以各种花絮开头。过去几天里我和RStudio的作者先后在我们Ames村办大学、明尼苏达R用户组和纽约R用户组分别做了knitr与RStudio的报告,下周R官方会议useR! 2012在田纳西州举办,我们也有幸得到了在会上做邀请报告的机会。在这个报告里,我要谈的就是一些开发中的思考,本文先给出这些思考的一个预览。如果你之前不熟悉Sweave,下面的内容可能不太容易理解,但没关系,一来很多东西你已经没有理解的必要了(旧世界的糟粕),二来今后我还会详细介绍knitr的功能。

我自从09年来美帝开始,所有的作业和报告都是用Sweave写的(纯数学的除外),因此Sweave里面的边边角角我都比较熟悉,源代码也是看了一遍又一遍,包括后来基于Sweave扩展的pgfSweave包,我也是翻了很多遍源代码。最终结论是,Sweave继承了一个伟大的想法,但在具体实现上走入了一个死角,默认功能不强,扩展性又太差。随后在我给一门R课程做助教的时候,每次看学生用Word文档交来的作业都觉得丑陋不堪(少数人会精心调整排版,但你懂的),要重跑他们的代码实在太麻烦了。没有可重复的作业,何谈可重复的科学研究?在knitr的各种反馈中,我看到一条推特消息最令我欣慰:

学knitr吧!

他描述的是一个普遍事实:大多人都还在复制粘贴时代。然而,表面上看起来最直接的办法往往深藏隐患。复制粘贴不仅麻烦,而且将结果置于难以重复的境地。要是别人想重复你的分析,你得详细交待每一个操作步骤。万一一个步骤出错,可能会导致后面的全都错掉,并且修改起来也麻烦。代码能很好避免这些问题,一处代码改动,可以让后续结果全都自动更新。

为了让多数人走上正确的道路,我们只有一个选择,那就是:让正确的路比错误的路更容易走。如果你做不到比复制粘贴更快更简单,那么任何说教都是无效的。基于这个想法,我列举knitr的九条设计原则如下。

1、默认美观

软件默认设定非常重要,它决定了用户的第一印象。knitr默认代码高亮(无论什么输出格式)以及代码重整理,这都是为了增强代码和结果的可读性,面对一堆毫无生气的代码,谁都觉得累。为了设计默认的高亮主题,我专门请教了我们颜林林大站长和李龑大设计师;如果对默认主题不满意,knitr自带上百个高亮颜色主题,很方便切换。代码重整理的意思是,无论你的源代码多乱,我都给你自动重新整理整齐,熟悉我的工作的人可能能猜出来这是formatR包的功能。当初我向Sweave作者进谏重整理代码的功能被谢绝了,后来pgfSweave作者采纳了我的建议,现在这功能回到我自己的包中了。

knitr代码高亮

2、自然输出

就像德鲁克说(管理方面)好的企业看起来平淡无奇一样,好的软件也不应该有太多“惊喜”。Sweave有很多让用户感到意外的特征,比如基于grid的图形(如lattice和ggplot2)必须要print()才能被画出来,一个代码段中最多只能产生一幅图,要让输出中有图形,必须专门设置选项,等等。knitr秉承的设计理念是,同样的代码粘贴在R中看到的结果全部都会在knitr的默认输出中看到,有图出图,有表出表,不需要设置任何选项,一切自然而然。让用户必须记忆选项的软件不是好软件。

3、以分析为中心

在Sweave旧社会我们经常看到诸如cat('\\includegraphics{}')之类的代码,这样的代码往往是设计缺陷的症状,因为设计中缺乏某些功能,导致用户必须在R的层面上去弥补那些缺陷,这样数据分析代码和那些暗黑代码就混在了一起,数据分析者一会儿考虑统计方面的东西,一会儿考虑LaTeX方面的问题,精力难免分散。knitr去掉了所有需要用黑客方式去解决的问题,比如过去每幅图形的输出宽度设置很麻烦,Sweave引进了一项非常暗黑的LaTeX技巧,叫\setkeys{Gin},如果你不知道这个东西,建议你永远不要知道它。knitr解放了图形大小设置的问题,你可以对每一幅图形设置输出宽度(out.width选项)。

4、可重用的输出

这个想法很简单,就是让那些提示符>和续行符+有多远滚多远。我们常常看到这样的输出:

> if (TRUE) {
+ 1}
[1] 1

提示符对我来说毫无意义,它唯一的作用就是糟蹋源代码,让我没办法复制粘贴代码去运行。knitr默认输出是这样的:

if (TRUE) {
  1
}
## [1] 1

这是我这两年做助教恨得咬牙切齿的问题之一,现在我终于可以把提示符去掉了,输出也被注释掉,不影响复制代码运行。

5、功能模块化

道理说起来谁都懂,可到了现实世界中,无数码农仍然是一个五百行的函数打天下,各种功能拉不开扯不散,混在一起,难维护、难测试、难扩展。knitr的设计主线也就三部分:文档解析器、代码运行器、输出生成器。一个文档拿来,先抽代码出来,运行它,再根据运行结果写入输出文档。knitr在生成器上解放了生产力,引进了输出钩子函数的概念,让用户可以自定义结果输出方式,比如1 + 1在R里面会打印一个字符串[1] 2,利用knitr的钩子函数,你可以决定如何装裱这个字符串,可以是特殊的LaTeX环境:

\begin{mySource}
[1] 2
\end{mySource}

也可以是HTML代码:

<div class="mySource">
[1] 2
</div>

又或者是Markdown:

```
[1] 2
```

总之,你愿意怎么安排就怎么安排,knitr把运行过的代码和结果都给你。

6、好的功能照单全收

过去大家对扩展Sweave做了各种尝试,如pgfSweave、cacheSweave和weaver等包。你仔细看看这些包就会觉得无奈,每个包都先把Sweave那上千行源代码先复制一遍,再在局部进行一些修改,以实现增加新功能的目的。随着R自身的更新,这些被复制的源代码逐渐也落后于R,于是包的维护渐渐就成了问题,我基本上亲眼目睹了pgfSweave的兴衰过程。knitr收录了大多数跟Sweave有关的包的功能,这些功能基本上都以更简单的代码重写了,并且不需要复制八百行代码。其中我个人比较喜欢的是tikz图形、缓存和动画功能。

7、照顾初学者

每当我说LaTeX可能是壁垒时,总有人怀疑我(会R的人怎么会不会LaTeX)。knitr自今年初出道一来,让我感觉推广阻力最大的人群是org-mode的人。Emacs是万能的,嗯。JSS上今年出的org-babel论文四个月下载九千次,我关于knitr的一篇日志四个星期浏览九千次。最可怕的开发者就是认为用户应该懂这懂那,最好是通读自己的源代码。有时候这种高期望是对的,比如统计学,你要是不懂统计方法最好不要乱用函数,但有时候用户即使无知也无害,比如怎么把Markdown转化为HTML,这种事情他知道与不知道又有什么关系呢?如果点一下按钮就能生成结果,那么让用户点就是了,不必非得了解背后是怎么回事。

为了让初学者尽快入门,我最初在LyX 2.0.3中加入了knitr模块支持,让一键生成PDF变得可能,但LyX背后仍然是LaTeX,所以我需要一个不是非用LaTeX不可的编辑器支持。大约两个月前,RStudio的开发者联系到我,我们首先对LaTeX文档添加了knitr的支持,后来在我的建议下,又陆续添加了HTML和Markdown的支持。最近各种R Markdown的应用风生水起,与RStudio的支持密不可分。我选择Markdown作为给初学者入门的媒介,原因就是它超级简单,你可以在五分钟之内基本学会它的用法,若再多花点时间,完全有可能学完它的用法,注意是“学完”。这世上能被学完的语言不多,因为大多数语言都想让自己功能多,而Markdown是为了让功能少。

8、开放源代码需要开放

knitr是一个R包,当然也是开放源代码的,但对“开源”二字来说,存在一个“到底有多开放”的问题。有些开源产品有很好的API设计(如Wordpress),但有些则未必。knitr里除了核心的运行代码部分,其它几乎处处开放,举一个小例子:尽管knitr基于R,但它不一定非得运行R代码,如果你乐意,你可以嵌入Python或AWK或其它语言代码,这体现在engine参数上。

9、文学化编程也是编程

文学化编程(Literate Programming)是整个设计的核心思想,但过去的模式局限在“代码+文档”的简单模型上,knitr使得一份文档变得可编程。为了说明这个可编程的特性,举一个钩子函数例子(伪代码):

```{r tweet-hook, cache=FALSE, include=FALSE}
knit_hooks$set(tweet = function(before, options, envir) {
  library(twitteR)
  # Authentication with OAuth here, then
  if (!before) {
    msg = paste('I have finished the chunk',
                options$label, ', my Lord!')
    tweet(msg)
  }
})
# enable the chunk hook
opts_chunk$set(tweet = TRUE)
```

所谓钩子函数就是挂在代码段选项上的函数,当选项不为空(NULL)的时候,这个函数就会被执行。上面的tweet钩子的大意就是用twitteR包发推特消息,每当一个代码段运行完之后,就把该代码段的标签写入一个消息,然后发推特,这样随着整个文档被编译,推特上就会逐渐显示编译进度。钩子函数让一份文档超越了仅仅运行代码段的功能,你还可以用它执行一些附加任务。顺便再说一则花絮,6月12日第8届国际R语言会议上有一位讲师最近在准备培训材料,突然冒出一个想法,试探性问我有没有可能明年的R会议做出来,结果是不用等一年,用钩子函数5分钟就够实现了。这样的事情在Sweave的世界里几乎不可能完成。

其实关于knitr这个包我早已经写完一份中文介绍,感兴趣的可以先下手了。大多数文档仍然处于英文状态,但除非你是高级忍者,否则所有的英文文档只需要看选项文档基本就够了。

最后向大家介绍两个应用的例子:

  1. 云端的报告生成器:你什么都不需要安装,只需要一个浏览器,就够你生成报告了,后台基于OpenCPU(一个年轻但相当猛的REST架构云端R);
  2. RPubs.com:这又是一个基于knitr的云端服务,但需要你在本地RStudio中事先生成报告,再上传过去,相信在不久的将来,我们的作业和报告会变得漂亮,彻底告别那恶心的Word文档;

还有其它诸如HTML5幻灯片的例子在此就先不介绍了。如果你要学习knitr,建议从RStudio和Markdown起步(示例)。到目前为止从knitr的反馈来看,大家对Markdown都比较感兴趣,它可能的确迎合了初学者的需要:简单、可用。2012都来了,抓紧学点儿基本网页知识,相信不久的将来(如果还有将来的话)你一定会意识到它无穷的回报。

关于谢益辉

RStudio码了个工,Iowa State University统计系博了个士。统计之都网站创办者;研究兴趣为统计图形及数据可视化,对统计模型方法的发展感兴趣但不喜欢纯粹抽象的数学理论,以直观、实用为学习标准;偏好以R语言为工具;Email:[email protected];个人主页:http://yihui.name

knitr与可重复的统计研究(花絮篇)》有49个想法

    1. 不可能。因为这两个包都有一长串依赖,R core必然不愿意维护所有这些依赖,而且我看现在进R core比登天还难。

  1. 好久没听到谢老大的声音了 激动ing~~
    料想knitr包在未来必将改变科研作者的生产方式

  2. 很赞!刚刚学会了一点sweave, 马上转到knitre似乎还有点犹豫。老大的这个包已经开发完毕了么?我来诉个苦,sweave.sty这个文件把我折磨的够呛,最终还是通过R, library(tools), tex2pdf()绕过去的。以后就用老大的包了。

    1. 这就是我说的“即使不清楚也没关系”的问题,Sweave.sty不知道害了多少人,因为它在R的安装目录下,并不是一个CTAN上能找到的LaTeX包,用户往往不知道上哪儿找以及怎么配置,所以对knitr来说,所有依赖的LaTeX包会首先被检查一下,如果能找到,就用系统已存在的包,否则knitr会拷一份过来供LaTeX使用,你永远都不必担心LaTeX包找不到的问题。

      这包都已经发布半年啦,主要功能早就开发完了,只不过对国内用户我还没太宣传而已。knitr兼容Sweave,所以不需要犹豫,对RStudio来说只是换个选项的问题。

  3. (上面一条邮箱填错了。。。。)原来谢大的声音是这么有磁性:)。knitr确实很方便,很适合我这样的懒人啊~

      1. 本来想象中是那种很粗犷的声音,没想到。。。还是很温柔的。

  4. 有幸在useR 2012上听到你的报告,讲的很好,其中穿插的冷笑话很是调动大家的情绪。

  5. 您好!我用RStudio,如果选择sweave选项中的LaTeX editing and compilation选项下的多个选项就会出现Error running C:/CTEX/MiKTeX/miktex/bin/pdflatex.exe (exit code 1)错误。如果只选择第一项Clean auxiliary output after compile就没有问题。请问这是肿么回事呢?谢谢!

    1. Windows的问题对我来说不方便考证,我只有遇到大问题的时候才到Windows下去测试。对这个问题,你一定需要下面的那些选项吗?

      1. 那倒是 不需要,只是有一次我选上了之后就出问题了,想问问您。谢谢您的回复。

  6. 感觉knitr这类工具对个人研究有很大帮助, 对大规模的研究项目, 研究任务的分工协作以及不同研究阶段的工作成果分享可能支持不够.

    1. 这不是knitr的事情了,knitr只是基础设施,你提的这些问题都是好问题,但我觉得更多是教育的问题,仅靠软件很难解决。

  7. 请教一个opencpu的问题。
    我可以把自己本地的数据加载到opencpu中进行处理吗?
    我直接用load(‘/home/usr/Documents/data.rda’),网站说有错误。

    1. 你可以上传到他的服务器上。直接加载本地的数据当然不会成功,因为服务器上不存在/home/usr/Documents/data.rda。参见其API:
      http://public.opencpu.org/pages/docs.html (你应该可以注册用户之后放在你的用户目录下,也可以不注册,放在临时文件夹下,我已经有阵子没看文档了)

      1. 感谢回复!
        我之前没在opencpu的网站找到可以注册的地方。
        刚仔细看了下,要在github注册用户。
        那就是说是在自己的github账户上放上自己的配置和数据文件,然后再调用opencpu的api吗?
        感觉好复杂,github的账户怎么跟opencpu关联啊?有没有什么教程?

        我现在开始注册github,希望能弄明白。

      2. 是的,目前只有通过Github验证的方式。没有别的教程,最好的教程就是OpenCPU自己的API文档。帐户关联可以在OpenCPU的网页里完成。

  8. 这个package确实极大的提升了所见即所得的程度。
    但是对于可重复研究,可能有些误导的成分。按我的理解,最大的好处就是可以在文档中嵌入代码,可呈现出最终效果。这样的出发思路是对的,latex不就是这样搞得么。但是实际上,对于绝大多数专业写文章的人,应该是使用简易的工具完成写作任务,而不是再去学一堆无关写作本身的命令,比如latex等的语法。按照你的设想,我认为其实这个package应该是做成插件的形式,支持office,wps等等的大众工具,文档本身的排版任务(超链接,段落格式等等以相对简单的操作来实现,而非用latex类似的语法)由这些工具实现,统计分析报告以你的package实现会更好。
    其实我认为这个应该是作为一个结题报告之类的工具,当然能做到集成到office等等中,我认为更有意义。我没有仔细查看过你的代码,不知道说的这些是否与你的初衷一致还是根本没理解,请指教。

  9. 请问一下,学过HTML,没怎么学LATEX,想开始学knitr和lyx该怎么学好,现在感觉学起来好乱啊~

    1. 没学过LaTeX的话我完全不建议你用LyX,你会晕掉的。成为LaTeX专家之后再來用LyX吧。用这两句古剑铭形容LyX在适合不过了:轻用其芒,动即有伤,是为凶器;深藏若拙,临机取决,是为利器。

      所以先去折腾Markdown吧,勇敢的少年!

      1. 谢谢! Markdown做的网页看起来的确很舒服,可惜老师要求作业要交PDF,Markdown做出来的网页弄成PDF总觉得不合适,看来还是得好好学学LATEX啊。

      2. 那我去专研专研吧,LATEX还是要弄好才行,谢谢啦

  10. 我升级了最新版本的R(3.0)和Rstudio(Version 0.98.49),为什么运行markdown会出现以下提示:R markdown requires the knitr package (version1.2 or higer).
    望解答

  11. > install.packages(‘knitr’, dependencies = TRUE)

    package ‘knitr’ is available as a source package but not as a binary

    Warning in install.packages :
    package ‘knitr’ is not available (for R version 3.1.0)
    我必须用3.0版本的R吗?

  12. “其实关于knitr这个包我早已经写完一份中文介绍”大大,这个连接出状况了。

发表评论

电子邮件地址不会被公开。 必填项已用*标注