R 包开发实战:从零构建、测试到发布
1. 引言
R 包是 R 语言生态中组织、复用和分发代码的标准方式。无论是个人项目中的工具函数,还是面向社区发布的开源库,将代码封装为 R 包都能显著提升可维护性和可分享性。本文将从零开始,带你完整走一遍 R 包的创建、编写、测试、文档化和发布流程,并给出大量可直接运行的代码示例。
2. 准备工作:安装必要工具
在开始之前,需要确保 R 环境中安装了以下关键包。它们分别负责构建、测试和文档生成。
# 安装核心工具链
install.packages(c("devtools", "roxygen2", "testthat", "usethis"))
验证安装
library(devtools)
library(roxygen2)
library(testthat)
library(usethis)
查看 devtools 版本
packageVersion("devtools")
其中,devtools 是开发流程的总入口,roxygen2 用于从注释生成文档,testthat 提供单元测试框架,usethis 则能自动化创建包的标准目录结构。
3. 创建包的基本结构
使用 usethis::create_package() 可以一键生成标准目录结构。下面以创建一个名为 mytools 的包为例。
# 创建包目录(请替换为你的实际路径)
usethis::create_package("~/R/mytools")
创建完成后,包目录下会生成以下关键文件:
DESCRIPTION:包的元数据文件,包含包名、版本、作者、依赖等信息。NAMESPACE:声明包的导入和导出规则。R/:存放 R 源代码文件的目录。man/:存放生成的帮助文档(通常由 roxygen2 自动生成)。tests/:存放单元测试代码。
4. 编写第一个 R 函数
在 R/ 目录下新建一个源文件,例如 R/hello.R,写入以下内容。这里我们定义一个简单的问候函数和一个计算均值的函数。
# R/hello.R
#' 向指定对象打招呼
#'
#' @param name 字符串,被问候者的名字
#' @return 返回一个问候语字符串
#' @export
#'
#' @examples
#' hello("World")
hello <- function(name = "World") {
paste0("Hello, ", name, "!")
}
#' 计算数值向量的均值(忽略缺失值)
#'
#' @param x 数值向量
#' @return 返回均值
#' @export
#'
#' @examples
#' my_mean(c(1, 2, 3, NA))
my_mean <- function(x) {
mean(x, na.rm = TRUE)
}
注意,函数上方的注释使用了 roxygen2 的语法,@param 描述参数,@return 描述返回值,@export 表示该函数需要被导出到 NAMESPACE,供用户直接调用。
5. 使用 roxygen2 生成文档
写好源码后,运行以下命令自动生成 man/ 目录下的帮助文档,并更新 NAMESPACE 文件。
# 在包根目录下执行
devtools::document()
执行后,man/hello.Rd 和 man/my_mean.Rd 会被自动创建。此时可以通过 ?hello 查看生成的帮助文档。
6. 编写单元测试
使用 usethis::use_testthat() 初始化测试框架,然后为每个函数编写测试用例。
# 初始化 testthat 框架
usethis::use_testthat()
为 hello 函数生成测试文件
usethis::use_test("hello")
生成的测试文件位于 tests/testthat/test-hello.R,编辑内容如下:
# tests/testthat/test-hello.R
test_that("hello 函数正常工作", {
expect_equal(hello("R"), "Hello, R!")
expect_equal(hello(), "Hello, World!")
expect_type(hello("Alice"), "character")
})
test_that("my_mean 函数忽略缺失值", {
expect_equal(my_mean(c(1, 2, 3, NA)), 2)
expect_equal(my_mean(c(10, 20)), 15)
expect_true(is.na(my_mean(c(NA, NA))))
})
运行全部测试:
devtools::test()
如果所有测试通过,控制台会输出绿色的通过信息;若有失败,会明确指出失败的断言和所在行号。
7. 添加数据与内部函数
包内可以附带示例数据集,也可以定义仅供内部使用的函数。下面演示如何添加一个内置数据集。
# 创建数据目录并写入示例数据
usethis::use_data_raw()
在 data-raw/ 目录下创建生成脚本
data-raw/generate_data.R
set.seed(42)
sample_data <- data.frame(
id = 1:100,
score = rnorm(100, mean = 75, sd = 10)
)
将数据保存到包内
usethis::use_data(sample_data, overwrite = TRUE)
对于内部辅助函数,只需在函数定义中不添加 @export 标签即可。这样的函数可以被包内其他函数调用,但不会暴露给用户。
# R/utils.R(内部函数,不导出)
标准化向量(内部使用)
z_score <- function(x) {
(x - mean(x, na.rm = TRUE)) / sd(x, na.rm = TRUE)
}
在导出的函数中调用内部函数
#' 返回标准化后的分数
#'
#' @param x 数值向量
#' @return 标准化后的向量
#' @export
standardize <- function(x) {
z_score(x)
}
8. 检查包的完整性
在发布前,务必运行 devtools::check() 对包进行全面检查,包括代码规范、文档完整性、测试通过情况等。
# 全面检查包
devtools::check()
检查结果会分为 ERROR、WARNING 和 NOTE 三个等级。理想情况下应做到 0 ERROR、0 WARNING,NOTE 越少越好。常见的 NOTE 包括未声明全局变量、文档示例运行时间过长等。
9. 安装与使用本地包
开发过程中,可以随时将包安装到本地 R 库中,方便在其它项目中调用。
# 安装到本地库
devtools::install()
加载并使用
library(mytools)
hello("R 社区")
[1] "Hello, R 社区!"
my_mean(c(5, 10, 15, NA))
[1] 10
10. 发布到 CRAN 或 GitHub
如果希望将包分享给更多人,可以选择发布到 CRAN 或 GitHub。发布到 CRAN 需要满足更严格的规范,而 GitHub 则更加灵活。
# 发布到 GitHub 前,先初始化 Git 仓库并提交
usethis::use_git()
创建 GitHub 远程仓库(需要提前配置 GitHub 令牌)
usethis::use_github()
提交并推送代码
之后在 GitHub 仓库页面创建 Release 即可
若准备提交 CRAN,先运行最终检查
devtools::check()
然后提交
devtools::release()
提交 CRAN 前,建议额外检查以下几点:
- DESCRIPTION 中的作者信息和许可证是否完整。
- 所有文档示例是否能在 5 秒内运行完毕。
- 代码中不包含绝对路径或网络下载操作。
11. 完整示例:一个实用的字符串工具包
下面整合以上知识点,构建一个完整的小型字符串处理包,包含多个函数、测试和文档。
# R/string_tools.R
#' 反转字符串
#'
#' @param s 字符串
#' @return 反转后的字符串
#' @export
str_reverse <- function(s) {
chars <- strsplit(s, "")[[1]]
paste(rev(chars), collapse = "")
}
#' 统计字符串中某个字符的出现次数
#'
#' @param s 字符串
#' @param char 要统计的字符
#' @return 出现次数
#' @export
str_count_char <- function(s, char) {
nchar(gsub(paste0("[^", char, "]"), "", s))
}
#' 将字符串转换为驼峰命名
#'
#' @param s 字符串,单词间用空格或下划线分隔
#' @return 驼峰命名字符串
#' @export
str_to_camel <- function(s) {
words <- strsplit(s, "[ _]")[[1]]
paste0(words[1], paste0(toupper(substring(words[-1], 1, 1)),
substring(words[-1], 2), collapse = ""))
}
对应的测试文件 tests/testthat/test-string_tools.R:
test_that("str_reverse 正确反转", {
expect_equal(str_reverse("abc"), "cba")
expect_equal(str_reverse("R 语言"), "言语 R")
})
test_that("str_count_char 正确计数", {
expect_equal(str_count_char("hello", "l"), 2)
expect_equal(str_count_char("banana", "a"), 3)
})
test_that("str_to_camel 正确转换", {
expect_equal(str_to_camel("hello world"), "helloWorld")
expect_equal(str_to_camel("foo_bar_baz"), "fooBarBaz")
})
运行测试并安装:
devtools::test()
devtools::install()
使用示例
library(stringtools)
str_reverse("hello") # "olleh"
str_count_char("banana", "a") # 3
str_to_camel("hello world") # "helloWorld"
12. 总结
本文从环境准备、目录创建、函数编写、文档生成、单元测试到发布流程,完整演示了 R 包的开发全流程。核心要点可以归纳为:
- 使用
usethis快速搭建标准目录结构。 - 用
roxygen2注释驱动文档和 NAMESPACE 的自动生成。 - 用
testthat为每个导出函数编写测试,保证代码质量。 - 发布前务必运行
devtools::check()消除错误和警告。
掌握这些技能后,你就可以将自己的 R 代码封装成规范、可复用的包,无论是个人使用还是开源分享都会更加高效。
更多推荐

所有评论(0)