6

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

 3 years ago
source link: https://cosx.org/2012/06/reproducible-research-with-knitr/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
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 都来了,抓紧学点儿基本网页知识,相信不久的将来(如果还有将来的话)你一定会意识到它无穷的回报。

中国人民大学统计硕士,爱荷华州立大学统计学博士,R 包 knitr 的主要作者。现为 RStudio 软件工程师,曾负责 Shiny 包相关开发工作,后转入 R Markdown 相关扩展包的开发,包括 bookdownblogdown。对统计计算、可视化、以及各类网页相关技术感兴趣,有志于对技术写作工具做减法工作,坚信人类浪费了太多时间在期刊论文、学位论文、书籍的排版上。平时主要活跃在 Github 上。个人主页在 https://yihui.name,思想偏激,流水账、意识流甚多,小人之心甚重,慎入。谢益辉

敬告各位友媒,如需转载,请与统计之都小编联系(直接留言或发至邮箱:[email protected]),获准转载的请在显著位置注明作者和出处(转载自:统计之都),并在文章结尾处附上统计之都微信二维码。

统计之都微信二维码

← COS 数据分析沙龙第一期(北京) 第五届中国 R 语言会议(北京会场)纪要 →

发表 / 查看评论


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK