Skip to content

Commit f176534

Browse files
authored
automate with gh actions (#76)
1 parent 5225fda commit f176534

File tree

5 files changed

+230
-1
lines changed

5 files changed

+230
-1
lines changed

07-project.Rmd

Lines changed: 162 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -526,4 +526,165 @@ Markdown 文档的初始版本,并通过 `upload_rmd()` 函数将其上传到
526526

527527
**workflowr** 的主要作者 John Blischak 也整理了一个与 R 项目工作流相关的 R 包和指南的非详尽列表,可见 GitHub 仓库: https://github.com/jdblischak/r-project-workflows。
528528

529-
### 使用 GitHub Actions 实现自动化部署{#github-actions}
529+
### 使用 GitHub Actions 实现自动化部署 {#github-actions}
530+
531+
使用 R Markdown 输出特定的文件格式后,一个自然的问题是如何与他人共享结果,一些使用场景包括在公司内部发布数据分析报告,发表 blogdown 博客,更新 bookdown 电子书籍等。最直接的做法是在本地执行编译,随后分享输出文件。例如执行在控制台 `rmarkdown::render()` 函数后,上传更新后的 HTML 文件到网络服务器上,或者在 Github 中上传 Markdown 文件。本地手动编译不仅需要重复的人力劳动,如果输出结果依赖于特定的系统环境,也难以保证结果的可重复性。为了使部署过程更高效可靠,包括 Github Actions, Gitlab Pipeline 等在内的持续集成 (continuous integration) 工具被广泛应用在 R Markdown 工作流的自动化部署中。本节以 Github 平台的 Github Actions 为例讲解 R Markdown 的自动化部署方法。
532+
533+
尽管 Github Actions 持续集成工具的用途非常广泛,具体在 R Markdown 的语境内,读者可以把它想象为一系列执行 R Markdown 编译的指令,可以是终端的 shell 命令,也可以调用 R 语言或任意工具的命令行接口。用户可以自行定义这些指令的触发条件,例如每周一上午十点运行一次,或每次 Github 仓库有新的代码提交时运行。当这些条件被触发时,Github 会创建一个专属的虚拟环境运行定义好的代码,其中便可以包括用于发布 R Markdown 输出文档的命令。
534+
535+
新建任意项目目录,在其中创建 `index.Rmd` 文件,包含如下内容:
536+
537+
````{r, echo = FALSE}
538+
import_example("rmd-ci.Rmd")
539+
````
540+
541+
本地编译结果如下 (需要安装 **prettydoc** [@R-prettydoc] 包):
542+
543+
```{r, echo = FALSE}
544+
import_example_result("examples/rmd-ci.Rmd")
545+
```
546+
547+
548+
本地验证代码运行无误后,开始设置自动化部署。首先在 Github 上新建对应的仓库,在本地目录下 `git init` 初始化 git 并 `git remote add origin <url>` 添加该仓库。希望实现的效果为,每次更新 main 分支后,Github Actions 自动编译 `index.Rmd` 并更新至仓库对应的 Github Pages 网页端。
549+
550+
Github Actions 使用 yaml 文件定义命令,在根目录下新建 `.github/workflows/deploy.yml` 文件。
551+
552+
此时文档结构为:
553+
554+
```markdown
555+
├── .github
556+
│   └── workflows
557+
│   └── deploy.yml
558+
└── index.Rmd
559+
```
560+
561+
其中,`.github/workflows/` 是固定的前缀路径,Github 在此路径下搜索 yaml 文件,每个文件称为一个 workflow,不同的 workflow 通常代表自动化部署的不同任务,例如有的负责获取数据,有的负责更新网页。`deploy.yml` 是本案例使用的唯一 workflow 文件,名称可以自定义,其中内容为:
562+
563+
```yaml
564+
on:
565+
push:
566+
branches: main
567+
568+
name: Render
569+
570+
jobs:
571+
render:
572+
name: Render index.Rmd
573+
runs-on: macOS-latest
574+
steps:
575+
- uses: actions/checkout@v2
576+
577+
- uses: r-lib/actions/setup-r@v2
578+
579+
- uses: r-lib/actions/setup-pandoc@v1
580+
581+
- name: Install rmarkdown
582+
run: Rscript -e 'install.packages(c("rmarkdown", "prettydoc"))'
583+
584+
- name: Render index.Rmd
585+
run: Rscript -e 'rmarkdown::render("index.Rmd")'
586+
587+
- name: Commit results
588+
run: |
589+
git add index.html
590+
git commit -m 'Re-build index.Rmd'
591+
git push origin
592+
```
593+
594+
`on` 定义了该 workflow 的触发条件,这里为 main 分支收到新提交 (push) 时触发。它的语法通常包括动作与分支,例如 "main 和 release 分支收到 pull request 时触发" 可以表示为
595+
596+
```markdown
597+
on:
598+
pull_request:
599+
# 可包含多个触发分支
600+
branches:
601+
- main
602+
- releases
603+
```
604+
605+
。`on` 还支持 cron 语法,例如
606+
607+
```markdown
608+
# 每天 5:30 and 17:30 UTC 触发 workflow
609+
on:
610+
schedule:
611+
- cron: '30 5,17 * * *'
612+
```
613+
614+
`name` 代表 Github 网页端显示的 workflow 名称。
615+
616+
`jobs` 是 workflow 中的核心内容,代表需要执行的一系列指令,可以分为不同的子任务,该文件中包含一个 `render` 子任务,指定运行环境为 `macOS-latest`,其他可选环境包括 windows,linux 等不同版本的机型。`render` 中的每一项代表一组独立的指令。由于每次 workflow 触发时均运行在全新的环境中,必须重新安装项目所需的依赖项,前三个 `uses` 指令是 Github 社区提供的模版,分别在环境中克隆所需的仓库,安装 R 和安装 pandoc。
617+
618+
```markdown
619+
# 预定义模版搜索 https://github.com/marketplace?type=actions
620+
- uses: actions/checkout@v2
621+
- uses: r-lib/actions/setup-r@v2
622+
- uses: r-lib/actions/setup-pandoc@v1
623+
```
624+
625+
626+
随后,两个自定义的终端命令为
627+
628+
```markdown
629+
- name: Install rmarkdown
630+
run: Rscript -e 'install.packages(c("rmarkdown", "prettydoc"))'
631+
632+
- name: Render index.Rmd
633+
run: Rscript -e 'rmarkdown::render("index.Rmd")'
634+
```
635+
636+
`name` 定义该步骤的 UI 名称, `run` 代表该步骤执行的 shell 命令,这两步安装了 rmarkdown 和 prettydoc 包,并编译 `index.Rmd`。`Rscript` 是 R 提供的命令行接口,另外一种写法是:
637+
638+
```markdown
639+
- name: Install rmarkdown
640+
shell: Rscript {0}
641+
run: |
642+
install.packages(c("rmarkdown", "prettydoc"))
643+
```
644+
645+
最后,workflow 需要把虚拟环境中生成的输出文件同步到主仓库中。这样,每次 main 分支收到更新,Github Actions 便会重新编译 `index.Rmd` 文档,同步输出文件 `index.html` 至仓库,实现自动化编译。
646+
647+
```markdown
648+
# 同步输出文件至仓库
649+
- name: Commit results
650+
run: |
651+
git add index.html
652+
git commit -m 'Re-build index.Rmd'
653+
git push origin
654+
```
655+
656+
案例的最后一步是启动 Github Pages 服务,该服务将自动识别仓库内的 `index.html` 文件,基于个人 Github 账号生成公开的网页地址。启动方法为点击仓库的 `settings -> pages` 并选择 Source 为 main 分支下的 root 目录。
657+
658+
```{r, echo = FALSE, fig.cap = "为仓库启用 Github Pages,生成地址见 https://qiushiyan.github.io/rmd-ci/"}
659+
knitr::include_graphics("images/rmd-ci-github-pages.png")
660+
```
661+
662+
真实生产环境中,应使主文档 `index.Rmd` 尽可能简洁抽象,可以运用 \@ref(child-document) 节学习的子文档知识,将业务逻辑封装为函数放入子文档 `functions.Rmd` 中,`functions.Rmd` 的内容为:
663+
664+
```{r, echo = FALSE}
665+
import_example("rmd-ci-functions.Rmd")
666+
```
667+
668+
669+
随后在主文档 `index.Rmd` 中引用子文档:
670+
671+
````{verbatim, lang="markdown"}
672+
```{r, child = "template.Rmd", include = FALSE}
673+
```
674+
675+
676+
```{r}
677+
sales_dat <- fetch_sales_data()
678+
knitr::kable(head(sales_dat, 20))
679+
```
680+
681+
682+
```{r}
683+
top_10_regions <- top_n_regions(sales_dat, 10)
684+
685+
barplot(sales ~ region, data = top_10_regions)
686+
```
687+
````
688+
689+
读者可以在 [Github Actions 文档](https://github.com/features/actions) 学习更多语法知识,此外 [rlib/actions](https://github.com/r-lib/actions) 仓库汇集了诸多 R 社区为各项自动化任务定制的 workflow 文件,大部分情况下可以直接复制使用,或仅需要修改少量配置。
690+

examples/rmd-ci-functions.Rmd

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
```{r, include=FALSE}
2+
fetch_sales_data <- function(date = Sys.Date()) {
3+
day <- as.integer(format(date, "%d"))
4+
sales_dat <- data.frame(
5+
region = rep(LETTERS, each = 10),
6+
sales = rpois(26 * 10, day)
7+
)
8+
sales_dat
9+
}
10+
11+
top_n_regions <- function(sales_dat, n) {
12+
sales_sum <- aggregate(sales ~ region, data = sales_dat, sum)
13+
head(sales_sum[order(-sales_sum$sales), ], n)
14+
}
15+
```

examples/rmd-ci.Rmd

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
---
2+
title: "用 Github Actions 实现自动化部署"
3+
author: "张三"
4+
date: "`r Sys.Date()`"
5+
output:
6+
prettydoc::html_pretty:
7+
theme: leonids
8+
highlight: github
9+
---
10+
11+
## 数据概览
12+
13+
`r Sys.Date()` 日各地区销售情况
14+
15+
```{r}
16+
day <- as.integer(format(Sys.Date(), "%d"))
17+
sales_dat <- data.frame(
18+
region = rep(LETTERS, each = 10),
19+
sales = rpois(26 * 10, day)
20+
)
21+
22+
knitr::kable(head(sales_dat, 20))
23+
```
24+
25+
## 描述性分析
26+
27+
本日销售量最多对前 10 个地区为:
28+
29+
```{r}
30+
sales_sum <- aggregate(sales ~ region, data = sales_dat, sum)
31+
32+
top_10_regions <- head(sales_sum[order(-sales_sum$sales), ], 10)
33+
34+
barplot(sales ~ region, data = top_10_regions)
35+
```
36+
37+
38+
## 线性模型
39+
40+
用简单线性模型探究地区对销售量对影响,公式为:
41+
42+
$$
43+
销售量 = \beta_o + \beta_1地区A + \beta_1地区B + \cdots + \beta_1地区Z
44+
$$
45+
46+
```{r}
47+
mod <- lm(sales ~ region, data = sales_dat)
48+
49+
region_coefs <- mod$coefficients[-1]
50+
max_idx <- which.max(region_coefs)
51+
```
52+
53+
所有 `r length(unique(sales_dat$region))` 个地区中,回归系数绝对值最大的是 `r LETTERS[max_idx]`,为 `r region_coefs[max_idx]`

examples/rmd-ci.png

169 KB
Loading

images/rmd-ci-github-pages.png

751 KB
Loading

0 commit comments

Comments
 (0)