第0章:工具汇总
0.1 devtools工具汇总
- devtools::has_devel() 检查开发环境是否完整;
- devtools::create(“path//to//package//pkgname”) 创建包;
- devtools::install() 从源码包到安装包;
- devtools::build() 从源码包到压缩包,同时生成使用指南;
- devtools::burld(binary=TRUE) 制作一个二进制包;
- devtools::install_github() 从github上下载源码包,编译,安装,生成安装包;
- devtools::load_all() 从源码包到内存中的附加包;
- devtools::use_build_ignore(“notes”) 忽略特定的文件或目录;
- devtools::use_package(“dplyr”) 添加dplyr到DESCRIPTION中的Imports字段,使用dplyr::fun()引用;
- devtools::use_package(“dplyr”,“Suggests”) 添加dplyr到Suggests字段,使用requirenamespace(“dplyr”,quietly=TRUE)来测试包是否安装,使用dplyr::fun()引用;
- devtools::document() 使用roxygen2创建对象文档;
- devtools::use_vignette(“my-vignette”) 创建使用指南;
- devtools::build_vignettes() 编译所有使用指南;
- devtools::use_testthat() 创建testthat测试相关文件夹和文件;
- devtools::test() 测试包;
- devtools::use_data() 使用导出数据;
- devtools::use_data_raw() 创建原始数据文件夹,用于存放原始数据和预处理数据的代码;
0.2 formatR工具
- formatR::tidy_dir(“R”)
第1章: 简介
1.1 为什么开发R包
- 共享R代码
- 组织自己的R代码,培养系统性思维
1.2 理念
任何可以自动化的都应该自动化
1.3 入门
install.packages(c("devtools","roxgen2","testthat","knitr"))
检查是否所有工具都安装了
devtools::has_devel()
## "C:/Program Files/R/R-34~1.4/bin/x64/R" --no-site-file --no-environ \
## --no-save --no-restore --quiet CMD SHLIB foo.c
##
## [1] TRUE
如果是TRUE,代表已经有了所有工具
第2章:包的结构
2.1 包的命名
- 选择独特的名字,容易被搜索的
- 避免同时使用大写和小写字母
- 找到一个和问题相关的词,并修改
- 使用缩略词
- 增加一个额外的r
2.2 创建一个包
- 使用Rstudio的GUI创建
- 命令
devtools::create("path/pkgname")
创建了一个最小可用的包,包含以下四个内容
- R/目录
- DESCRIPTION
- NAMESPACE
- pkgname.Rproj
.Rproj文件只是一个文本文件,不需要手动修改,可以通过Project Options来修改
2.3 包的生命周期
2.3.1 源码包
包含R/子目录,DESCRIPTION等组件的一个目录
2.3.2 压缩包
devtools::build()
很少需要压缩包
压缩后再解压,以下内容不再存在:
- src/目录下的临时文件
- .Rbuildignore 列出的任何文件
2.3.3 二进制包
二进制包是平台相关的;
2.3.4 已安装包
见0.1节devtools工具
2.3.5 内存中的包
- Build & Reload 从源码包到已安装包再到内存中的包;
- load_all() 直接到内存中的包;
2.4 库
库是一个包含已安装包的目录,使用.libPaths()来查看当前使用库。
.libPaths()
## [1] "C:/Program Files/R/R-3.4.4/library"
library()和require()的区别:
- library() 抛出错误;
- require() 返回FALSE;
在开发过程中,两个都不使用;
第3章:R代码
3.1 R代码的工作流
- 编辑一个R文件;
- 按Ctrl+Shift+L;
- 在控制台中浏览代码;
- 修改代码,重复上面过程;
3.2 组织函数
两种极端行为要避免:
- 把所有函数都放在一个文件中;
- 把每个函数放在独立的文件中;
文件名应该是有意义的,并且以.R结尾
不能在R/下使用子目录
快速跳转函数:
- 在代码中点击函数名并
- Ctrl+.
3.3 代码风格
install.packages("formatR")
formatR::tidy_dir("R")
3.4 顶层代码
在脚本中,代码在加载时运行。在包中,代码在编译时运行。
不应该在包的顶层运行代码,包的代码只能创建对象,主要是函数。
不改变R运行环境的通用做法:DESCRIPTION中列出需要的包,之后显式使用pkg::fun()
改变R运行环境的做法:
- library() 加载一个包;
- options() 修改全局设置;
- setwd() 修改工作目录;
- 其他函数的行为在运行函数前后发生改变;
第4章,包的元数据
DESCRIPTION的重要字段
Package: 包名
Title: 标题,包的一行描述,不加句点
Description: 一段,可多行,四个字符缩进
Version: 主版本号.次版本号.补丁版本[.开发版本]
Authors: person("First name","Second name",email="",role=c("aut","cre"))
Imports:
dplyr,
ggplot
Suggents:
Depends:
URL:
BugReports: https://github.com/ahorawzy/wzytry/issues
License:
LazyData: true
第一个开发版本号0.0.0.9000
第5章:对象文档
5.1 使用roxygen写对象文档的理由
- 代码和文档混合在一起,当你修改代码时,提醒你更新文档;
- roxygen2动态检查需要提供文档的对象,自动生成文档模板;
- 隐藏了不同类型对象文档之间的区别;
roxygen2的功能:
- 生成man/下的.Rd文件;
- 管理NAMESPACE和DESCRIPTION中的Collate字段;
5.2 文档工作流程
- 添加roxygen注释到.R文件;
- 运行devtools::document() 或者Ctrl+Shift+D 将roxygen注释转为.Rd文件;
- 使用?预览文档;
- 修改注释,重复,直到文档是需要的样子;
5.3 另一个文档工作流程
- 给.R文件添加roxygen注释;
- Build & Reload 重新编译整个包,包括所有文档,然后重启R并重新加载包; 3,用?预览文档;
- 修改文档;
5.4 roxygen注释
#' Add together two numbers. 标题,首字母大写,句点结束。
#'
#' This function add two numbers together, which written by wzy. 描述,简要说明函数的功能。
#'
#' 细节描述,较长,详细说明函数是如何工作的。可以使用@section标签在文档中添加任意块,块标题首字母大写,后面跟冒号。
#'
#' @section Waining:
#' Do not operate heavy machinery within 8 hours of using this function.
#‘
#' @seealso
#'
#' @family
#'
#' @param x A number. 大写字母开头,句点结尾,可跨越多行。
#' @param y A number.
#' @return The sum of \code{x} and \code{y}.
#' @examples
#' add(1, 1)
#' add(10, 1)
#' @export
wzyadd <- function(x, y) {
x + y
}
第6章:使用指南——长篇文档
6.1 元数据
---
title: "Guide for wzytry"
author: "ZhiYuan Wang"
date: "2018-09-23"
output: rmarkdown::html_vignette
vignette: >
%\VignetteIndexEntry{Guide for wzytry}
%\VignetteEngine{knitr::rmarkdown}
%\VignetteEncoding{UTF-8}
---
6.2 knitr的选项
knitr::opts_chunk$set(
opt1 = val1
opt2 = val2
)
- eval = FALSE 防止代码运行
a <- 1+1
print(a)
- echo = FALSE
## [1] 2
- results = “hide” 关闭代码输出的打印
- warning = FALSE和message = FALSE将不会显示警告和消息
- collapse = TRUE和comment = “#>” 显示代码输出
- results = “asis”
pander::pandoc.table(iris[1:3,1:4])
Sepal.Length | Sepal.Width | Petal.Length | Petal.Width |
---|---|---|---|
5.1 | 3.5 | 1.4 | 0.2 |
4.9 | 3 | 1.4 | 0.2 |
4.7 | 3.2 | 1.3 | 0.2 |
- fig.show = “hold” 保存所有的图,直到最后的代码快;
- fig.width = 5, fig.height = 5,设置图像高度和宽度(单位是英寸);
第7章:测试
问题不在于你没有测试代码,而在于没有自动化测试。
7.1 测试的工作流
devtools::use_testhat()
- 创建一个tests/testhat目录
- 在DESCRIPTION的Suggests字段中增加testthat;
- 创建一个文件test/testthat.R,在R CMD check运行时运行所有的测试。
工作流:
- 修改你的代码或测试;
- 用C+S+T或devtools::test()测试你的包;
- 重复,直到所有的测试通过。
7.2 测试结构
测试文件放在tests/testthat/下,名字必须以test开始。
测试是分层组织的:期望组成测试,测试组成文件。
- 期望是测试的原子单位,期望自动化了本来的视觉检查,期望是以expect_开始的一些函数;
- 测试由多个期望组成,用于测试简单函数的输出、复杂函数的某个参数的变化。它们测试一个功能单元,可以用test_that()创建一个测试;
- 文件由多个相关测试组成,可利用context()给文件赋予可读的名称。
context("String length")
library(stringr)
test_that("str_length is number of characters", {
expect_equal(str_length("a"), 1)
expect_equal(str_length("ab"), 2)
expect_equal(str_length("abc"), 3)
})
test_that("str_length of factor is length of level", {
expect_equal(str_length(factor("a")), 1)
expect_equal(str_length(factor("ab")), 2)
expect_equal(str_length(factor("abc")), 3)
})
test_that("str_length of missing is missing", {
expect_equal(str_length(NA), NA_integer_)
expect_equal(str_length(c(NA, 1)), c(NA, 1))
expect_equal(str_length("NA"), 2)
})
7.3 期望
7.3.1 测试相等
- expect_equal() 在误差内相等
- expect_identical() 精确相等
library(testthat)
expect_equal(10,10)
expect_equal(10,11)
7.3.2 测试字符匹配
- expect_match() 检查字符向量是否和一个正则表达式匹配
- expect_output() 检查打印输出
- expect_message() 检查消息
- expect_warning() 检查警告
- expect_error() 检查错误
7.3.3 测试对象
- expect_is() 检查对象是否从指定的类继承
7.3.4 测试逻辑
- expect_true()
- expect_false()
7.4 编写测试
与测试相关的消息应该具有提示作用,以便你快速定位问题的来源,避免把太多的期望放在一个测试中,最好是由很多小的测试,而不是几个大的测试。
每当你想利用打印语句时,请把它写成一个测试。
- 把重点放在测试功能的外部接口;
- 努力使用仅有的一个测试来测试每个行为;
- 避免测试对其有信心的简单代码;
- 当发现一个错误时,总是写成一个测试;
测试第一的哲学:从编写测试开始,然后写代码通过这些测试。这反映了一个解决问题的重要策略:从建立成功标准开始,不然你如何知道是否已经解决了这个问题。
测试的最高层结构是文件。每个文件应该包含一个单独的context()调用对它内容进行简要说明。
对每个复杂函数建立一个测试文件。
8. 命名空间
9. 外部数据
在包中包含数据主要有三种方法,取决于要做什么以及谁能够使用它;
- 如果想存储二进制数据,并把它提供给用户,就放在data/目录,比如放示例数据;
- 如果你想存储解析过的数据,但不提供给用户,就放在R/sysdata.rda。这是存放函数所需数据的最佳场所。
- 如果想存储原始数据,放在inst/extdata。
9.1 导出的数据
data/
利用save()创建的.RData文件,其中包含单个对象。
使用方法是devtools::use_data()
通常,data/所包含的数据是原始数据的干净版本。强烈建议花时间在源码包中包含处理原始数据的代码。这样很容易更新或复制数据版本。把这些代码放在data-raw/,不需要把它放在压缩包中,所以把它加入到.Rbuildignore。方法是:
devtools::use_data_raw()
9.2 内部数据
有些函数需要预先计算的数据表,如果放在data/目录,那么也将提供给用户,这是不恰当的。
应该放在R/sysdata/rda。
用devtools::use_data()加上参数internal = TRUE来创建。
其中的对象不会被导出,所以不需要文档,只是在你的包里可用。
9.3 原始数据
如果要展示原始数据,将原始文件放在inst/extdata。安装包时,inst/中的所有文件和文件夹都将会移动到顶层目录。
要引用inst/extdata中的文件,使用system.file()。
system.file("extdata","2012.csv",package="testdata")
如果文件不存在,会返回空值。
13. Git和Github
在github上安装包
install.packages("devtools")
devtools.install_github("username/packagename")
13.1 初始设置
- 安装Git;
- 告诉Git你的姓名和电子邮件
git config --global user.name "YOUR FULL NAME"
git config --global user.email "YOUR EMAIL ADDRESS"
可以运行git config –global –list来检查设置是否正确
- 在github上创建一个账户,使用相同电子邮件;
- 如果需要,生成一个SSH秘钥。
- 把SSH公钥给Github。
13.2 创建本地Git仓库
在project options中,把git/svn面板从“None”改为“Git”。
两处变化:
- 右上方出现Git窗格;
- 工具栏出现Git下拉菜单;
13.3 查看改变
- M,被修改的;
- ?,没有被跟踪的;
- D,删除的;
13.4 记录改变
- 保存更改;
- 通过点击Commit,或C+A+M打开提交窗格;
- 选择文件,点击Stage。
- A,添加一个没有跟踪的文件后,git知道你想把它添加到仓库;
- R,重命名一个文件,Git会看到一个删除和一个添加,如果选择了两个,会认为是重命名;
- 编写提交信息;
- 点击提交;
13.5 提交的最佳实践
- 最小的
- 完整的
提交信息:
- 简介,但具有提示性;
- 描述为什么,而不是是什么;
13.6 忽略文件
添加到.gitignore,右键点击该文件,选择Ignore。
13.7 与Github同步
git remote add origin git@github.com:username/packagename.git
git push -u origin master
只有在通过pull拉取最新版本后,git才会让你推送一个版本。
当你拉取的时候,Git首先下载所有的改变,然后把这些与你所做的合并。
当Git窗格出现U,表示合并冲突。