3

概述

随着近年来,Rstudio 通过 shiny 将R语言推向Web化,Rmarkdown 借助 Shiny 已经不断演化形成了一个动态可交互文档生态。对于数据科学的研究可以说已经是Every Thing in Rmd!本文我将介绍Rmd如何以文档定义应用的方式(Docs As an App)成为数据科学中的标准交付。

clipboard.png

什么是Rmd

Rmd 是Rmarkdown的文件保存格式,而Rmarkdown 是一个由谢益辉老师(XRAN)十年磨一剑的动态可重复文档解决方案。参考前文:基于RStudio Webinars的统计报告Web化与工程化实践总结,我们可以知道,Rmarkdown缘起谢大大对LaTex论文写作的深恶痛绝,在谢大大深入研究各种姿势的论文撰写方式后决定自成一派,于是推出了Rmarkdown极大地简化了论文写作的排版问题并快速成为了学界的论文写作新标准。

借用谢大大的例子,你可以运行下面的代码来生成一个pdf的beamer。

download.file("https://raw.githubusercontent.com/rstudio/webinars/master/13-R-Markdown-Ecosystem/presentation.Rmd",destfile = "presentation.Rmd") # 下载Rmd文件
download.file("https://raw.githubusercontent.com/rstudio/webinars/master/13-R-Markdown-Ecosystem/references.bib",destfile = "references.bib") # 下载bib引用
rmarkdown::render("presentation.Rmd", rmarkdown::pdf_document()) # 生成pdf

bib

yaml中可以指定bibliography的路径来解决引用文献的问题。

---
title: "Sample Document"
output: html_document
bibliography: bibliography.bib
---

这里的引用我们可以很容易在Google Scholar生成,当然,除了BibTeX,Rmd 也支持 EndNote,RefMan,RefWorks以及直接在yaml中的引用方式。

---
references:
- id: fenner2012a
  title: One-click science marketing
  author:
  - family: Fenner
    given: Martin
  container-title: Nature Materials
  volume: 11
  URL: 'http://dx.doi.org/10.1038/nmat3283'
  DOI: 10.1038/nmat3283
  issue: 4
  publisher: Nature Publishing Group
  page: 261-263
  type: article-journal
  issued:
    year: 2012
    month: 3
---

Rmd 原理分析

如果有用过APIDoc或者Swagger这样的API文档撰写工具,或者利用Jekyll(Ruby)、Hexo(JS)、Pelican(Python)搭建个人网站的开发者应该很容易就明白,Rmarkdown的原理和这些应用非常类似。

静态渲染

首先,我们利用markdown事先声明好一定的文档格式。其次,利用文档解析器对markdown中的内容和dom树做解析。最后,再用相应的css、js做渲染生成h5页面,这样一个文档定义应用的过程就完成了。

动态渲染

而文档的动态化则是用一个后端服务器来host这些前端html页面资源。这里Rmarkdown可以利用Shiny-server作为后端服务器,在运行前端h5页面的同时通过后端的服务器做实时的数据处理,实现文档的动态化。(在文章最后会举一些相应的例子)

注意,这里如果在shiny-server的相关目录下同时存在shiny的app.R文件,shiny-server将会优先识别app.R而不是.Rmd。

想要动手尝试的同学可以参考前文:打造数据产品的快速原型:Shiny的Docker之旅

数据科学与 Rmd

首先,参考前文:数据科学部门如何使用Python和R组合完成任务,让我们来回顾一下之前谈到了数据科学理论上会经历的几个阶段:

需求定义=》数据收集=》数据转化=》数据分析=》数据可视化

而在需求快速变化的项目初期,为了快速确定需求,如何敏捷打造最小可用原型(MVP)显然比项目工程化来得更有意义。

所以在现实中,这时候流程常常就会缩短成:

需求定义=》数据整理=》数据可视化

场景一:需求分析多格式文档交付

需求定义阶段,我们需要通过大量的Email、word、paper、keynote、访谈等等资料针对相关的业务场景进行初步了解,这一阶段我们最大的需求不是着急码代码,而是在起跑之前看准方向,系好鞋带,弄明白真实的业务需求,对实际需求进行优先级排序(做什么)。

在需求定义阶段,每次访谈、实地考察、活动会议等等都需要我们产出相应的研究纪录。经过来来回回的几轮讨论,我们还需要画很多流程图、示意图等等,最后产出一份完整的产品需求分析报告(为什么做)和产品需求文档(怎么做)。

在这个过程中,很多细节都需要反复推敲,多方论证(撕逼),而这个环节也是数据科学最有趣的一环。

面对大量的文档输出,很多人跟我一样都会首先想到马克飞象。马克飞象名声在外,是很优秀的一款产品。不过对于文档的同步目前是收费的服务,很可惜,因为预算有限,所以我还是没有考虑它,暂时是通过svn/git的方式解决云端同步的问题。

参考前文:解密Airbnb的数据科学部门如果构建知识仓库,作为一个谢大大的死忠,我很自然选择了 Rmarkdown 作为我文档输出的首选工具。一方面,我不仅可以方便地嵌入本地或者网络上的图片,通过'[@]'语法也可以利用yaml中的信息直接引用相关的参考文献。另一方面,在网络情况非常糟糕的情况下,我也不必担心通过Web编辑无法编辑的问题,而所有的编辑结果都会被存放在Rstudio的.Rdata文件中,实现和Web编辑器类似的实时编辑功能。

通过设置自己喜欢的css样式,我还可以个性化导出html文档,统一输出团队的VI系统,这方面Airbnb是一个很好的榜样,我们可以参考前文:解密Airbnb的数据科学部门如何使用R语言。你也可以参考V2EX里大家对于Markdown样式的热烈讨论

在需求报告的讨论环节,在yaml中简单声明shiny的runtime,再将Rmd文档上传到shiny-server上就可以在网络上(内网或外网)多人多端同步浏览,在Mac找不到转化头做投影的时候非常有用。

---
title: "文档定义应用:数据科学 Everything in Rmd"
author: "Harry Zhu"
date: "May 13, 2016"
output: html_document
runtime: shiny
---

更为实用的是,如果需要评审修改文档,我们也可以导出word来互相评审修改,我们甚至还能直接导出目录,完全不需要自己再手动生成目录。

option

在word和html之间自由切换,进可审阅、退可编辑,显然,在现实中我们没法决定我们身边的人是喜欢word还是喜欢html。

当然,除了html和word,Rmd还可以在多种酷炫的keynote、dashboard、pdf之间相互转化,不过这里由于篇幅有限暂时不展开讨论。

更多高级的Rmarkdown文档转换技巧可以前往rmarkdown官网

场景二:数据整理多语言混编

之所以说原来 数据收集=》数据转化=》数据分析 的流程可以简化为数据整理主要是因为这里数据源获取的方式一般比较dirty,比如从一个Excel的 xlsx文件读取、通过现场勘查记录自己制作的临时数据又或者通过Python爬虫抓取的一部分数据、从某些文章中直接复制粘贴的数据等等。而处理这些dirty的数据往往都会采用比较crud的方式,并不会做非常详细的分析工作(很多公司的数据分析实习生工作都是从这里起步的)。

在数据整理的过程中,我们可能会用到多种编程语言或者工具(比如Python、R、awk)、多种文件格式(比如csv、xlsx、json、pdf、word),这时候为了兼容多种工具并保证研究的可重复性,我们非常需要在一个文档中运行多种编程语言。

通过knitr的代码块管理,我们可以轻松选择代码编译环境来兼容多种编程语言,比如这样:

```{python}
x = 'hello, python world!'
print(x)
print(x.split(' '))
import matplotlib.pyplot as plt
plt.plot(range(10))
plt.show()
```
当然我们也可以自定义代码编译引擎:

> system("which python") # 查到 python 引擎的默认路径
/Users/harryzhu/Library/Enthought/Canopy_64bit/User/bin/python

进一步定义 python 引擎的路径:

```{r engine=python,engine.path='/Users/harryzhu/Library/Enthought/Canopy_64bit/User/bin/python'}
x = 'hello, python world!'
print(x)
print(x.split(' '))
import matplotlib.pyplot as plt
plt.plot(range(10))
plt.show()
```

更多高级的knitr代码块使用技巧可以移步谢大大的knitr官网

场景三:数据产品敏捷响应

数据科学部门在推进项目的过程中如果只是拿着一些示意图和文档就去和各个部门讨论而没有拿出实际可用的产品必然会出现几个问题:

  1. 交流低效。对产品和需求的理解停留在纸面,往往多方反复沟通后对产品依赖概念模糊。

  2. 信心不足。当我们没有拿出实际可用的产品,项目战线上的成员对项目的认可度比较低,各自相应的投入也就不会太多。

众所周知,数据可视化是R非常擅长的领域。得益于R语言社区大量开发者对大量JS作图框架热情洋溢的封装,我们可以利用R接口调用诸如echats、highcharts、leaflet、datatable这样的前端组件,轻松完成数据产品的快速原型。

现在通过shinydashoboardflexdashboard,我们可以快速开始数据产品的原型构建,而这一切都可以轻松集成在一个.Rmd文件中。

shinydashboard

shinydashboard官网 Demo: https://gallery.shinyapps.io/...

GitHub源码地址:https://github.com/rstudio/sh...

这里你可以执行运行示例。

# 安装依赖
install.packages(c("shiny", "dplyr", "htmlwidgets", "digest", "bit"))
devtools::install_github("rstudio/shinydashboard")
devtools::install_github("jcheng5/bubbles")
devtools::install_github("hadley/shinySignals")
# 运行
shiny::runGitHub("rstudio/shiny-examples",subdir = "087-crandash")


引用官网的另外一个例子,当然你也可以在Rmd中直接省去定义ui和server的步骤,写成这样:

---
title: "something"
author: "Harry Zhu"
date: "May 13, 2016"
output: html_document
runtime: shiny
---

library(shinydashboard)
dashboardBody(
    tabItems(
      # 第一个tab
      tabItem(tabName = "dashboard",
        fluidRow(
          box(plotOutput("plot1", height = 250)),

          box(
            title = "Controls",
            sliderInput("slider", "Number of observations:", 1, 100, 50)
          )
        )
      ),

      # 第二个tab
      tabItem(tabName = "widgets",
        h2("Widgets tab content")
      )
    )
  )
# 直接在后面定义作图函数
set.seed(122)
  histdata <- rnorm(500)

  output$plot1 <- renderPlot({
    data <- histdata[seq_len(input$slider)]
    hist(data)
  })

省去的ui和server的定义,直接渲染Rmd。

flexdashboard

flexdashboard Demo: https://beta.rstudioconnect.c...

sales report

开源源码如下:

```
---
title: "Sales Report with Highcharter"
author: "Joshua Kunst"
output:
flexdashboard::flex_dashboard:

orientation: columns
social: menu
source_code: embed

---
```
```{r setup, include=FALSE}
library(highcharter)
library(dplyr)
library(viridisLite)
library(forecast)
library(treemap)
library(flexdashboard)

thm <-
hc_theme(

colors = c("#1a6ecc", "#434348", "#90ed7d"),
chart = list(
  backgroundColor = "transparent",
  style = list(fontFamily = "Source Sans Pro")
),
xAxis = list(
  gridLineWidth = 1
)

)

```

Column {data-width=600}
-----------------------------------------------------------------------

### Sales Forecast

```{r}
AirPassengers %>%
forecast(level = 90) %>%
hchart() %>%
hc_add_theme(thm)
```

### Sales by State

```{r}
data("USArrests", package = "datasets")
data("usgeojson")

USArrests <- USArrests %>%
mutate(state = rownames(.))

n <- 4
colstops <- data.frame(
q = 0:n/n,
c = substring(viridis(n + 1), 0, 7)) %>%
list.parse2()

highchart() %>%
hc_add_series_map(usgeojson, USArrests, name = "Sales",

                value = "Murder", joinBy = c("woename", "state"),
                dataLabels = list(enabled = TRUE,
                                  format = '{point.properties.postalcode}')) %>%

hc_colorAxis(stops = colstops) %>%
hc_legend(valueDecimals = 0, valueSuffix = "%") %>%
hc_mapNavigation(enabled = TRUE) %>%
hc_add_theme(thm)
```

Column {.tabset data-width=400}
-----------------------------------------------------------------------

### Sales by Category

data("Groceries", package = "arules")
dfitems <- tbl_df(Groceries@itemInfo)

set.seed(10)

dfitemsg <- dfitems %>%
  mutate(category = gsub(" ", "-", level1),
         subcategory = gsub(" ", "-", level2)) %>%
  group_by(category, subcategory) %>% 
  summarise(sales = n() ^ 3 ) %>% 
  ungroup() %>% 
  sample_n(31)

tm <- treemap(dfitemsg, index = c("category", "subcategory"),
              vSize = "sales", vColor = "sales",
              type = "value", palette = rev(viridis(6)))

highchart() %>% 
  hc_add_series_treemap(tm, allowDrillToNode = TRUE,
                        layoutAlgorithm = "squarified") %>% 
  hc_add_theme(thm)

### Best Sellers

```{r}
set.seed(2)

nprods <- 10

dfitems %>%
sample_n(nprods) %>%
.$labels %>%
rep(times = sort(sample( 1e4:2e4, size = nprods), decreasing = TRUE)) %>%
factor(levels = unique(.)) %>%
hchart(showInLegend = FALSE, name = "Sales", pointWidth = 10) %>%
hc_add_theme(thm) %>%
hc_chart(type = "bar")

```

我们可以看到这里的排版方式被大大的简化了,我们只需要定义好Column和Row,然后在相应的代码块里尽情的作图就可以在前端页面上可视化了,相比之下css真是操碎了心。

rowpng

更多关于flexdashboard的高级技巧可以前往flexdashboard官网

echarts

由于大家对echarts的呼声很高,这里再演示一下echart在R中的调用。

# 下载包
install.packages(
  'recharts',
  repos = c('http://yihui.name/xran', 'http://cran.rstudio.com')
)
# 画图
recharts::echart(iris, ~Sepal.Length, ~Sepal.Width, series = ~Species)

echarts

另外一个牛逼的功能是,通过REmap这个包(已经封装了各种百度API),我们可以将物流的收货地址直接输入不必太规则的中文文本即可实现地图数据的可视化。而且,最近特别火的莆田系医院数据爬取及分布可视化大多也是通过REmap包实现的。

更多echarts的作图这里不一一介绍,高级技巧可以前往recharts官网或者SupStat 郎大为:REmap

Notebook

根据雪晴数据网的最新消息,RStudio 也已经实现了类似iPython的Notebook功能。

下面是雪晴数据网的教程

配置步骤

  1. 下载最新的RStudio每日版

  2. 下载最新版本的Rmarkdown包:

devtools::install_github("rstudio/rmarkdown")
  1. 设置选项:Tools -> Global Options -> Rmarkdown -> Enable R Notebook -> Apply

  2. 像往常一样打开一个新的Rmarkdown文件

  3. 设置YAML输出选项:将output: html_document 改为 output: html_notebook: default

Rmd + Docker = liftr

liftr 视频教程

躺坑排雷

在安装最新版本后,由于我的镜像源设成了 MRAN,导致这里更新的 evaluateknitr包的版本过低需要指定CRAN源重新升级。

> getOption("repos")
                                                      CRAN 
"https://mran.revolutionanalytics.com/snapshot/2015-08-27" 
install.packages(c("evaluate","knitr"),repos="http://mirror.bjtu.edu.cn/cran/")

Notebook API

通过 Notebook API,我们还可以把任意的Rmd文档输出成Notebook。

rmarkdown::render("FinanceR.Rmd",out_format="html_notebook")
notebook <- parse_html_notebook("FinanceR.nb.html")

参考资料

推荐工具

文末,推荐一款类似于 Airbnb 用 Flask 生成博客的解决方案,可以有效在团队内部做知识仓库的共享:

比较可惜的是,我尝试了多个 Leanote 的Docker方案都没有成功,如果读者有成功案例欢迎在留言区留言。

另外,还推荐一个类似于 Endnote 的论文神器:

利用 Zotero 我们可以轻松的通过 drap and drop 实现多人协同论文跟踪管理的功能,并且支持Chrome插件、多平台客户端。

作为分享主义者(sharism),本人所有互联网发布的图文均遵从CC版权,转载请保留作者信息并注明作者 Harry Zhu 的 FinanceR专栏:https://segmentfault.com/blog...,如果涉及源代码请注明GitHub地址:https://github.com/harryprince。微信号: harryzhustudio
商业使用请联系作者。


HarryZhu
2.2k 声望2.2k 粉丝