第 7 章 R包开发与管理
7.1 开发R包
7.1.1 基本流程
7.1.1.2 完善meta信息
在DESCRIPTION
文件中,我们需要完善基本的包信息。
大量使用usethis
包和的相关函数:
# license
usethis::use_mit_license("hu huaping")
# authors
utils::person(
given = "Hu",
family = "Huaping",
email = "huhuaping01@nwsuaf.edu.cn",
role = c("aut", "cre"))
参考资源:
usethis
包 issuess 898: creates a “non-standard license specification” that is not “standardizable” by devtools::check()
7.1.1.4 创建数据集
# Proc 1: raw-data directory, and your raw R script and raw data
usethis::use_data_raw("scrape_proc")
# Proc 2: pgk data set construction
## step 1: write out data set, and can find it in "data/your_data.rda"
usethis::use_data(your_data, overwrite = TRUE)
## step 2: write r file, and can find it in "R/your_data.r".
## your should edited with oxygen syntactic
use_r("your_data")
## and your can use my helper function to help documenting
zoningr::document_dt(your_data)
# step 3: formally document, and can find it in "man/your_data.Rd".
document()
7.1.3 包周期管理
7.1.3.1 版本格式
(1)包版本格式:至少两个整数,并以.
或-
区隔。例如1.0
或者0.9.1-10
。tidyverse规范版本格式:<major>.<minor>.<patch>
。例如发布版本号1.9.2
,开发版本号1.9.2.9000
。
<major>.<minor>.<patch> # released version
<major>.<minor>.<patch>.<dev> # in-development version
(2)查看包版本:utils::package_version()
。
(3)编译包版本:使用函数usethis::use_version()
,根据情况做出版本号决定。它会自动填充DESCRIPTION
文件的版本号,同时自动添加行到NEWS.md
文件。
usethis::use_version()
#> Current version is 0.1.
#> What should the new version be? (0 to exit)
#>
#> 1: major --> 1.0
#> 2: minor --> 0.2
#> 3: patch --> 0.1.1
#> 4: dev --> 0.1.0.9000
#>
#> Selection:
7.1.4 碰到的坑
7.1.4.1 无法正常写入namespace
报错信息:document()
出现如下报错。
Warning: The existing 'NAMESPACE' file was not generated by roxygen2, and will not be overwritten
诊断检查:发现NAMESPACE
文件里面么有任何信息。而且document()
后也确实没有写入任何信息。
解决方法:直接在NAMESPACE
文件第一行添加如下信息,然后再document()
,一切恢复正常!(这种方法简直令人惊诧。因为前面都是按照官方引导来做的,却还是出现如此诡异一幕。)
# Generated by roxygen2: do not edit by hand
参考资源:
- devtools issues 1466:document() failing after upgrade
7.1.4.2 不能识别非ASCII字符
报错信息:check()
后出现如下报错。
Error: Portable packages must use only ASCII characters in their R code
诊断检查:
- R函数文件的代码行中出现有中文字符。
解决办法:
全局添加编码说明:在R安装路径
R/etc/Rprofile.site
文件中,加入代码行options(encoding = "UTF-8")
项目内添加编码说明:在project根目录
.Rprofile
文件中,加入代码行options(encoding = "UTF-8")
参考资源:
- 队长问答Is it possible to write package documentation using non-ASCII characters with roxygen2? 链接
7.1.4.3 不可见的全局变量
报错信息:check()
后出现如下报错。
checking R code for possible problems ... NOTE
get.tbl: no visible binding for global variable '.'
Undefined global functions or variables: .
诊断检查:
这里是因为R函数文件的代码行中用到了
mutate_at(., ~as.character)
,因此oxygen2
编译中会要求明确这个”.”是什么。当然,我们的很多R函数(如dplyr
、purrr
等包函数)都会出现这类完全正常的引用方式。当然,
oxygen2
编译中会要求明确这些”Undefined global variable”也是出于严谨性的目的,本身无可指责。
解决办法:
- 方法1:使用函数
utils::globalVariables()
来指明所有这些全局变量。当然,如果有很多这样的变量,那么此方法将难以为继。
参考资源:
7.3 代码调试
参看hadley “advanced R”, chapter 22 Debugging
7.3.1 tryCatch函数的正确使用
Basic Error Handing in R with tryCatch()
参看R-bloger post
7.3.2 R提醒程序执行完成
R提醒程序执行完成 参看
You just have to wrap it into a function like this:
options(error = function() {beep(9)})
最终方案:
beep_on_error(expr, sound = 1)
7.3.3 设定工作日志
可以使用R包logr
(见官网),来实现对工作流的日志记录。
7.3.4 强制结果不输出console中
有些函数会出现某些输出,如组标准误矫正回归的摘要函数summary(out.mice <- miceadds::lm.cluster())
等。
如若希望强制这些结果不输出到console中,则可以结合invisible(capture.output()))
进行设定(可参看队长问答)。
f1 <- function(n, ...){
print("Random print statement")
cat("Random cat statement\n")
rnorm(n = n, ...)
}
f1(2)
## [1] "Random print statement"
## Random cat statement
## [1] -0.5490191 -2.0346370
## [1] "Random print statement"
## Random cat statement
7.4 pkgdown发布
7.4.1 基本过程
参看
Hadley的在线书R Packages
pkgdown官方说明
# Run once to configure package to use pkgdown
usethis::use_pkgdown()
# write Non-vignette articles
usethis::use_article()
# Run to build the website
pkgdown::build_site()
#setting up GitHub actions to automatically build and publish your site
usethis::use_pkgdown_github_pages()
说明:
usethis::use_article()
的好处在于:其一可以编写更多的包文档,而不增加包的体积;其二可以使用更多的R包来编写更美观的说明文档,同时可以确保正在开发的R包yourpkg
不存在过多的、没必要的包依赖(避免”DESCRIPTION”设置文件里Suggests
或者Imports
里对依赖包的列出)。(1)对于前者,usethis::use_article()
会自动添加article文档到.gitignore列表及.buildignore列表里去。(2)对于后者,我们需要做出额外的设置:“DESCRIPTION”设置文件里增加参数域Config/Needs/website
,并列出article里(.Rmd)需要用到的额外R包。
7.4.4 github action报错未正确设定remote包
github action在执行r-lib/actions/setup-r-dependencies@v2
块时会给出如下报错:
Run r-lib/actions/setup-r-dependencies@v2
ℹ Creating lockfile '.github/pkg.lock'
✖ Creating lockfile '.github/pkg.lock' [9.2s]
Error:
! error in pak subprocess
Caused by error:
! Could not solve package dependencies:
* deps::.: Can't install dependency kwb.utils
* kwb.utils: Can't find package called kwb.utils.
* local::.: Can't install dependency kwb.utils
提示无法安装R包kwb.utils
。实际上这是一个在github上发布的包KWB-R/kwb.utils
。在自己的包开发描述文件中,并没有正确地指明这个包的来源(如下)。(但这并不影响包开发,因为在包检查流程中通过”Build” > “Check”检查,并不会报错。)
Imports:
dplyr,
fs,
magrittr,
kwb.utils,
rlang,
stringr,
tibble,
glue,
utils,
tidyselect,
officer,
rvg
Depends:
R (>= 2.10)
因此,正确的操作是需要通过Remotes: KWB-R/kwb.utils
来正确指定其安装来源:
Imports:
dplyr,
fs,
magrittr,
kwb.utils,
rlang,
stringr,
tibble,
glue,
utils,
tidyselect,
officer,
rvg
Depends:
R (>= 2.10)
Remotes: KWB-R/kwb.utils
7.4.5 github action安装依赖包时间过长或失败
使用pkgdown
包写说明文档(articles)时,可能会用到额外其他的R包。此时使用github action自动云端渲染更新说明文档,则意味着云端后台需要安装和准备这些额外的依赖R包。这直接会带来两个问题:一是过多的R依赖包意味着github action执行时间过长;二是某些特殊的R依赖包可能安装不成功,从而中止gihub action后续进程。
理论上,pkgdown
后台的github action可以通过设置并执行renv
环境来避免R依赖包的上述问题。但是目前没有看到现成的设置代码(.github/workflows/pkgdown.yaml
)。
目前的一个可行办法是设定包开发文件DESCRIPTION
,设定如下参数(可参看pkgdown
本身包开发的设定):
Config/testthat/edition: 3
Config/potools/style: explicit
Config/Needs/website: usethis, servr
using additional packages for pkgdown articles(参看问答)
7.5 R启动逻辑与环境配置
7.5.1 R session及临时文件路径
默认情况下,每次运行R project都会打开一次R session,并伴随产生一个本地临时文件夹,例如RtmpoLxPcG
或RtmpKecsRz
。正常情况下,如果关闭一次R session,这个临时文件夹就会自动删除。
有时候,因为R project的工程任务比较大,会伴随产生较多的临时文件,从而导致较大的本地硬盘空间占用。更加不幸的是,这些R session临时文件夹的默认路径往往是C:\Users\huhua\AppData\Local\Temp\RtmpoLxPcG
(windows用户)。这就意味着临时文件会占用系统盘(C盘)的硬盘空间。在硬盘空间严重不足的情况下,甚至会导致内存不足而无法跑project的情况。
再加上说不清的原因,有时候即使关闭了R session,其相应的临时文件夹却没有自动删除。残留的零时文件夹可能会不断积累而变得异常大(以我本人的情况,一次检查中竟然发现有接近30Gb的硬盘占用!!)。
鉴于此,可以在用户层面来设定R session临时文件夹的默认路径。例如设定在非系统盘如D:\appData\Rsession
。相关操作步骤如下:
步骤1:在非系统盘创建文件夹
D:\appData\Rsession
步骤2:创建并打开用户层面的
.Renviron
配置文件。可以采用R函数usethis::edit_r_environ(scope = "user")
,.Renviron
配置文件位于C:/Users/huhua/Documents/.Renviron
步骤3:在
.Renviron
配置文件中设定R session临时文件夹的默认路径,直接添加参数行TMP = "d:\appData\Rsession"
,保存,然后重新打开并运行R project。步骤4:查看路径设定结果。可以在打开的R session下运行R代码
tempdir()
进行查看。
7.6 Renv包报错及解决
7.6.1 为什么要用Renv?
考虑如下这些常见的R使用场景:
R软件版本由
R 3.6.0
升级到R 4.1.0
某个R包发生了版本变化,例如
rmarkdown v1.6
升级到rmarkdown v2.11
A从github上拉取了B的R项目,在B的工作环境下一切运行良好,但是A则出现各种报错。
上述场景是R用户经常会碰到的工作情形,而此时Renv
是用来进行R包管理的有效工具。
一方面,对于不同的R project,Renv
都会给出基于项目的独立的R包路径和R包存放。这样不同的R projects就可以各行其道,相互隔绝,互不冲突。
另一方面,Renv
可以把各类安装包及其历史版本(或不同来源)集中缓存在本地。如果某个R project需要安装“集中库”里已有的、正确版本的R包,R project只需要直接调用本地的缓存即可,从而避免再次远程在线下载,节约了安装时间。
最后,每当某个R project的R版本变化或R session改变时,可以直接调用缓存在R project下的历史版本包,从而实现有效的R包版本管理。
7.6.2 Renv环境设置
一般情况下,renv
已经很好地管理了本地的R包“集中库”,以及各个R project的R包“项目库”。用户不需要过多的操心它们的关系。
对于R project,用户一般不会创建在系统盘内(如放在D盘等),因此R project的R包“项目库”占用硬盘情况大可不用去担心。
对于电脑硬盘容量不足的少数用户,可能会面临因为renv
缓存本地的R包“集中库”占用磁盘过大,而出现系统盘(win 11 C盘)被过度占用的问题。
以我本人的电脑使用来看,缓存本地的R包“集中库”占用C盘将近6.5GB。对于win 11用户而言,本来就吃紧的C盘,直接就爆表不够用了。
这时就必须要对renv
的默认路劲参数进行修改,把R包“集中库”由默认的C盘路径修改到其他非系统盘符下。
需要提前理解如下的一些基本知识:
renv
官方的路径参数定制说明,具体见Path Customization。其中,最重要的是RENV_PATHS_ROOT
路径参数的设定。关于R启动(R startup)逻辑的相关知识。其中最重要的是区分不同层次、不同权限和不同语法的启动参数控制关系,重点包括如下一些文件的设置(参看):
repos.conf
、rsession.conf
、Renviron.site
、Rprofile.site
、.Renviron
、.Rprofile
。具体可以参看在线资源:a)“Resources for learning R” 的第11章 “Using .Rprofile and .Renviron”。b)“What They Forgot to Teach You About R”的“Chapter 7 R Startup”。
为了解决R包“集中库”由默认的C盘路径修改到其他非系统盘符下,重要的操作步骤如下:
步骤1:在非系统盘下创建文件夹:例如
d://appData//renv
步骤2:创建并修改R软件安装路径下的
Rprofile.site
,进行全局性设定。具体路径为:C:\Program Files\R\R-4.1.2\etc\Rprofile.site
。可以直接使用notepad等文本编辑软件进行修改设定。
注意:有人建议使用
usethis::edit_r_profile()
来创建和设定启动参数,但是经测试改函数仅仅基于用户或项目层面,例如:运行usethis::edit_r_profile(scope = "user")
只会创建或修改’C:/Users/huhua/Documents/.Rprofile’的启动参数;运行usethis::edit_r_profile(scope = "project")
只会创建或修改项目层面’D:/github/books-rworld/.Rprofile’的启动参数。这两者都达不到全局性(基于R软件版本)的启动设定要求。
- 步骤3:在
C:\Program Files\R\R-4.1.2\etc\Rprofile.site
下,添加R函数命令行Sys.setenv(RENV_PATHS_ROOT = "D:\\appData/renv")
。
注意:上述的
Rprofile.site
文件是与R软件安装版本相伴随的,因此如果R安装版本发生了改变,则以上的设定在新R版本下会失效,又得重新按上述步骤设置一遍。一个好的方案是,将自己的这些环境参数设定,以R project的形式进行git维护和管理,实现可重复使用。一些公开的环境参数维护项目可以参看“Example R profiles”。
7.6.3 Renv与R
首先,Renv
管理R
版本包库(library)是基于一位小数的R版本来设定本地R包的存放地址。例如”/renv/library/R-4.1”下会存放所有4.1.x
的R版本。因此,在进行一个大版的R
升级时(例如从4.1.2
升级到4.3.1
),工作项目的包库也会完全更新升级到新的目录下(“/renv/library/R-4.3”)。
此外,如果进行了大版本的R
升级时,windows系统下还要同步升级对应的Rtools
版本(下载地址)。例如RTools 4.3
对应于所有的R 4.3.x
。RTools
的安装对于renv::restore()
具有重大的影响,因为R包的编译和安装与之密切相关!否则会各种报错和无法安装R包。
7.6.6 更新R包记录
如果在另一个设备下,旧有的R包无法重载,也可以考虑更新这些包的renv记录(更新包版本,或更改安装来源),具体操作如下:
# use digest 0.6.22 from package repositories -- different ways
# of specifying the remote. use whichever is most natural
renv::record("digest@0.6.22")
renv::record(list(digest = "0.6.22"))
renv::record(list(digest = "digest@0.6.22"))
# alternatively, provide a full record as a list
digest_record <- list(
Package = "digest",
Version = "0.6.22",
Source = "Repository",
Repository = "CRAN"
)
renv::record(list(digest = digest_record))
7.6.7 R包依赖报错
操作情形:存档R包状态。renv::snapshot()
。
报错信息如下(可参看):
> x <- enc2utf8('á')
> parse(text = x, encoding = "UTF-8")
Error in parse(text = x, encoding = "UTF-8") :
<text>:1:1: unexpected input
1: á
^
Rstudio console窗口提示:
Error in !deps$Dev
解决办法:
设定renv参数为:renv::settings$snapshot.type("all")
,具体参考renv包关于”Capturing all dependencies”官方说明。
7.7 相关软件及环境变量
7.7.1 Java配置
很多R包都会调用系统软件Java,以实现相关函数功能。例如R包xlsx
就需要以来rJava
包,以调用系统的Java功能。
因此,本地需要安装Java的版本软件(又分为jre
版,及jdk
版,后者居于主流);同时需要配置OS系统的环境变量
。
经验法则:开始安装和配置前务必要保证Java版本与R GUI版本是同样的系统版本,例如都是64位的软件版本,或都是32位的软件版本。对于R GUI软件的windows版本,要注意其32bit和64bit都是绑在一块下载的。因此,如果电脑操作系统(win 11)是64bit的,那么在安装R GUI时务必要在安装引导界面下,去掉其32位的安装选项!!否则你自认为它会根据OS系统的情况自动选择安装64bit版本,而实际上安装的很可能是32bit的!!具体可以通过R函数
sessionInfo()
进行查看!!
下面是具体的操作步骤:
步骤1:确定OS操作系统的位数,例如win 11 64bit
步骤2:下载安装R GUI(下载地址见cran)。例如,只安装其windows版本下的64bit版本R,安装时去掉32bit的安装选项!具体缘由见上面!!
步骤3:下载并安装java软件。例如下载其windowns版本下64bit版本的
jdk
(下载地址见cran)。具体版本如jdk-17.0.2
(版本会迭代变化),然后进行本地安装后,其默认路径在C:\Program Files\Java\jdk-17.0.2
。步骤5:查看java是否配置正确。打开(windows终端)
cmd
,输入java -verion
。在Rstudio中输入library(rJava)
查看包安装情况是否成功。
7.8 编程思维
#see resource:
## template: https://www.nomnoml.com/
## github: https://github.com/rstudio/nomnoml
# renv::install("rstudio/nomnoml")
require(nomnoml)
## Loading required package: nomnoml
## Loading required package: webshot
7.8.1 人工编校与自动检查结合的迭代实现机制
情景说明:数据集中存在一些变量,需要进行人工处理。数据集是扩展性的,数据集容量会单调增加。新增加的数据可能也会存在一些变量,需要进行人工处理,进行编校整理。为了避免重复人工投入,早前完成的人工编校将作为历史版本进行保留备存。新增加的数据,会首先与这个保留备存文件进行比对。此时,仅需要对备存文件中未匹配到的数据行进行再次人工编校,然后再次保留备存。如此持续往复进行。
实现方法:把每次的原始文档(auto)和对应的编校文档(edited)存放在一个仓库目录下(hub/)。批量读取仓库目录(hub/)的历史存档,与新数据进行比对匹配,获得新的原始文档(auto)和对应的编校文档(edited),再次迭代保存。原理图如下:
其中get.hubs(dir)
为批量读取函数,具体函数为:
# match hub and edited by hand!!
## Helper function to read all hubs
get.hubs <- function(dir){
out <- tibble(
path = list.files(dir,full.names = T)
) %>%
mutate(
dt = map(.x = path,
.f = openxlsx::read.xlsx, sheet="edited")
) %>%
unnest(dt) %>%
select(-path)
}
dir_tar <- "data-sql/hub/"
dt_hubs <- get.hubs(dir = dir_tar)
匹配比对可以使用anti_join()
函数:
## match hubs,return all rows from x without a match in y
dt_newcome <- anti_join(
x = zone_clean40,
y = select(dt_hubs,unit),
by="unit" )
自动化写入存档可以通过openxlsx
包相关函数加以实现。其中auto
和edited
存放在同一个xlsx文档中,而后者需要进行人工编校:
## write newcomers
if (nrow(dt_newcome ) >0) {
## specify path
n_z4 <- nrow(dt_newcome)
today <- today()
out_path <- paste0(dir_tar,
today,
"_n", n_z4,
".xlsx")
## create workbook, write data, save workbook
wb <- openxlsx::createWorkbook("hub")
addWorksheet(wb, "auto")
addWorksheet(wb, "edited")
writeData(wb, sheet = "auto", x = dt_newcome)
writeData(wb, sheet = "edited", x = dt_newcome)
saveWorkbook(wb, file = out_path, overwrite = TRUE)
}
最后就是人工编校新存档,并再次读取全部历史存档: