本工具使用的开源库

本工具代码中捆绑了 1 个开源库。

R 速查 — 简明参考

R 4.4 语法、数据结构与最常用 base R / tidyverse 速查手册,覆盖约 80% 日常场景。

R

R R 4.4

R (GNU R / 交互式解释器) · 函数式 · 向量化 · OO (S3 / S4 / R6) · 动态

学习路径

先学会 Rscript 运行与赋值(<-)→ 掌握向量、data.frame 等数据结构 → 用 if/for/apply 家族写流程 → 学会函数、闭包与管道 |> → 深入 apply 集合操作 → 理解 S3/R6 面向对象与错误处理 → 再按需学文件 I/O、并行、网络与正则。FAQ 节适合回头避坑;包管理与 renv 见构建章节。

1.Hello World 与运行环境

Rscript 运行、REPL 交互、打印输出与帮助文档。

最小程序

R 以表达式为单位求值。print 打印对象,cat 直接输出。脚本可用 Rscript 运行。

1
2
3
4
5
print('Hello, world!') # 打印到控制台
cat('Hello, world!\n') # 直接输出,无引号
# 保存为 hello.R 后运行:
# $ Rscript hello.R
# 或在 R / RStudio 里 source('hello.R')

赋值与输出

赋值用 <- 或 =。print 显示对象结构,cat 适合拼接输出。message 写 stderr。

1
2
3
4
5
x <- 42 # 赋值(推荐 <-)
y = 42 # = 也合法,风格上少用
print(x) # 显示 [1] 42
cat('x =', x, '\n') # 拼接输出:x = 42
message('这是提示') # 写往 stderr

Rscript 运行

Rscript 以非交互方式运行脚本,适合批处理与命令行。source 在会话内运行脚本文件。

1
2
3
4
5
6
7
8
9
10
# 脚本 hello.R:
# print('hello')
# 运行脚本:
# $ Rscript hello.R
# 运行并传参:
# $ Rscript hello.R a b
# 会话内运行文件:
source('hello.R')
# 直接执行字符串:
eval(parse(text = '1 + 1'))

命令行参数

commandArgs(trailingOnly = TRUE) 取脚本后的命令行参数。参数均为字符串。

1
2
3
4
5
6
7
8
9
# 运行:Rscript app.R a b c
args <- commandArgs(trailingOnly = TRUE)
print(args) # [1] "a" "b" "c"
# 是否含脚本名(FALSE 时 args[1] 是脚本路径):
commandArgs() # 含 Rscript 与脚本路径
# 用 args[1] 当输入路径:
if (length(args) > 0) {
input <- args[1]
}

打印输出

print 显示对象与结构,cat 无引号拼接,sprintf 格式化。invisible 抑制自动打印。

1
2
3
4
5
6
7
8
x <- c(1, 2, 3)
print(x) # [1] 1 2 3
cat(x, sep = ', ', '\n') # 1, 2, 3
sprintf('Pi = %.2f', pi) # "Pi = 3.14"
# 表达式自动打印(顶层才有):
1 + 1 # [1] 2
# 函数内最后表达式自动返回并打印:
invisible(1) # 不打印

帮助文档

? 打开帮助,?? 全文搜索。args 看函数形参,example 运行示例。

1
2
3
4
5
6
7
8
?mean # mean 的帮助页
??regression # 全文搜索
args(mean) # 形参列表
example(mean) # 运行内置示例
apropos('dist') # 搜索含 dist 的对象
help.search('聚类') # 按主题搜索
# 查看源码:
mean # 打印函数体

加载包

library 加载包到搜索路径,require 返回逻辑值。:: 显式取包内函数。

1
2
3
4
5
6
7
8
9
10
11
library(dplyr) # 加载包
library('ggplot2') # 字符串形式亦可
if (!requireNamespace('pkg', quietly = TRUE)) {
install.packages('pkg')
}
# 显式命名空间调用:
dplyr::select(df, x)
# 查看已加载:
search()
# 已安装包列表:
rownames(installed.packages())

运行脚本

source 在当前会话执行脚本文件,可定义函数与变量。echo 显示执行过程。

1
2
3
4
5
6
7
source('utils.R') # 运行 utils.R
source('utils.R', encoding = 'UTF-8')
# 显示每行与结果:
source('demo.R', echo = TRUE)
# 常用:加载自建函数集:
# utils.R 里定义 f <- function(x) x * 2
f(5) # 10

2.变量与常量

赋值、基本类型、NA/NULL 与类型转换。

赋值符号

<- 是惯用赋值;= 也可。<<- 在函数内写外层变量。全局赋值用 assign。

1
2
3
4
5
6
7
8
9
10
11
x <- 5 # 推荐
y = 5 # 也合法
x <- y <- z <- 1 # 链式赋值,三者皆 1
# 函数内改外层变量:
f <- function() {
g <<- 99 # 写到全局环境
}
# 按名字赋值:
assign('name', 'Rex')
name # "Rex"
# 与 = 的区别:= 不进入嵌套函数环境

基本类型

原子向量有 numeric、integer、character、logical。整数用 L 后缀。typeof 看存储类型。

1
2
3
4
5
6
7
8
9
10
x <- 3.14 # numeric (double)
i <- 1L # integer(L 后缀)
s <- 'hi' # character
b <- TRUE # logical
typeof(x) # "double"
typeof(i) # "integer"
# 长度:所有标量都是长度为 1 的向量:
length(s) # 1
# 复合类型:
typeof(1:5) # "integer"

NA 与 NULL

NA 表示缺失值,NULL 表示空对象。is.na / is.null 判断。NaN 是非数值,Inf 无穷。

1
2
3
4
5
6
7
8
9
10
11
12
13
v <- c(1, NA, 3)
is.na(v) # FALSE TRUE FALSE
any(is.na(v)) # TRUE
# NULL 表示对象不存在:
x <- NULL
is.null(x) # TRUE
# 数值上的特殊值:
0 / 0 # NaN
1 / 0 # Inf
is.nan(NaN) # TRUE
is.finite(Inf) # FALSE
# 统计时忽略 NA:
mean(c(1, NA), na.rm = TRUE) # 1

类型转换

as.* 显式转换。c() 拼接不同类时自动向最通用类型转换。判断用 is.*。

1
2
3
4
5
6
7
8
9
10
11
12
as.numeric('42') # 42
as.character(42) # "42"
as.logical(1) # TRUE
as.factor(c('a', 'b')) # 因子
# 混合拼接自动转换:
c(1, 'a') # "1" "a"(变 character)
c(1, TRUE) # 1 1(变 numeric)
# 转换失败产生 NA:
as.numeric('abc') # NA(有警告)
# 安全转换 + 检查:
x <- suppressWarnings(as.numeric('abc'))
is.na(x) # TRUE

创建向量

c 拼接、冒号生成序列、seq 定制步长、rep 重复。names 给元素命名。

1
2
3
4
5
6
7
8
9
10
c(1, 2, 3) # 拼接
1:10 # 1 2 ... 10
seq(0, 1, by = 0.25) # 0 0.25 ... 1
seq(1, 10, length.out = 5) # 5 个等距点
rep(1, 3) # 1 1 1
rep(c('a', 'b'), each = 2) # a a b b
# 命名元素:
x <- c(a = 1, b = 2)
names(x) # "a" "b"
x['a'] # 1

命名约定

变量名允许字母、数字、点、下划线,不能以数字开头。点号在 R 中不是运算符。

1
2
3
4
5
6
7
8
9
10
11
12
my_var <- 1 # 下划线
my.var <- 2 # 点号合法
.var <- 3 # 以点开头
# 不能以数字开头:
# 1var <- 1 # 语法错误
# 保留字不可作变量名:
# if <- 1 # 错误
# 驼峰或蛇形均可,保持一致:
totalCount <- 10
total_count <- 20
# 点号在 R 里是普通字符:
a.b <- 1 # 合法

变量管理

ls 列出变量,rm 删除。exists 判断存在。rm(list = ls()) 清空环境。

1
2
3
4
5
6
7
8
9
10
11
x <- 1; y <- 2
ls() # "x" "y"
exists('x') # TRUE
rm(x) # 删除 x
rm(list = ls()) # 清空当前环境
# 只删特定模式:
rm(list = ls(pattern = '^tmp'))
# 查看对象结构:
str(y) # num 2
# 环境信息:
ls(all.names = TRUE) # 含隐藏对象

内置常量

R 内置常用常量:pi、letters、month.name,以及 .Machine 平台细节。

1
2
3
4
5
6
7
8
9
10
11
pi # 3.141593
letters # a b ... z
LETTERS # A B ... Z
letters[1:3] # "a" "b" "c"
month.name # January ...
month.abb # Jan Feb ...
# 机器精度等:
.Machine$double.eps # 2.22e-16
.Machine$integer.max # 2147483647
# 其他内置:
date() # 当前日期时间字符串

作用域基础

R 词法作用域:函数内找变量逐级向外。全局与局部同名会遮蔽。get 指定环境取值。

1
2
3
4
5
6
7
8
9
10
11
12
13
x <- 'global'
f <- function() {
x <- 'local' # 遮蔽全局
x
}
f() # "local"
# 不赋值则取全局:
g <- function() x
g() # "global"
# 显式环境取值:
get('x', envir = .GlobalEnv)
# 环境链:
parent.env(globalenv())

3.数据类型与结构

向量、因子、矩阵、列表、数据框与 tibble,R 的数据结构体系。

向量

原子向量同质且一维,是 R 最基础结构。c() 创建,索引从 1 开始。

1
2
3
4
5
6
7
8
9
10
11
12
v <- c(1, 2, 3)
v[1] # 1(索引从 1 起)
v[c(1, 3)] # 1 3
v[-1] # 去掉第 1 个
v > 1 # FALSE TRUE TRUE
v[v > 1] # 逻辑筛选:2 3
length(v) # 3
# 整数序列:
1:5
# 命名访问:
x <- c(a = 1, b = 2)
x['a']

因子

因子存分类变量,levels 固定类别。有序因子表示顺序。as.factor / factor 创建。

1
2
3
4
5
6
7
8
9
10
11
f <- factor(c('低', '中', '高'))
levels(f) # "低" "中" "高"
table(f) # 计数
# 固定类别顺序:
f2 <- factor(c('低', '高'),
levels = c('低', '中', '高'))
# 有序因子:
of <- ordered(c('低', '高'),
levels = c('低', '中', '高'))
of[2] > of[1] # TRUE
# 数值转因子再取值要小心(见 FAQ):

矩阵

matrix 二维同质。byrow 控制填充方向。行名/列名与维度名 dimnames。

1
2
3
4
5
6
7
8
9
10
11
12
m <- matrix(1:6, nrow = 2, ncol = 3)
m # 2 行 3 列
m[1, 2] # 第 1 行第 2 列
m[1, ] # 第 1 行
# 按行填充:
matrix(1:6, nrow = 2, byrow = TRUE)
# 行/列运算:
rowSums(m); colMeans(m)
# 维度:
dim(m) # 2 3
# 命名:
dimnames(m) <- list(c('r1', 'r2'), c('a', 'b', 'c'))

数组

array 任意维同质结构。dim 指定各维长度。适用多维张量。

1
2
3
4
5
6
7
8
9
10
11
a <- array(1:24, dim = c(2, 3, 4))
dim(a) # 2 3 4
a[1, 2, 3] # 切片取值
# 索引后变低维:
a[1, , ] # 3x4 矩阵
# 沿维度求和:
apply(a, 3, sum) # 每个第三维切片求和
# 变维:
dim(a) <- c(6, 4) # 重塑为 6x4
# 与 matrix 的关系:
is.matrix(a) # TRUE(当 dim 为 2 时)

列表

list 异构可嵌套。$ 与 [[ 取元素,[ 返回子列表。length 统计元素数。

1
2
3
4
5
6
7
8
9
10
11
l <- list(name = 'Rex', age = 5, tags = c('a', 'b'))
l$name # "Rex"
l[[2]] # 5
l[1] # 子列表(含 name)
length(l) # 3
names(l) # "name" "age" "tags"
# 嵌套列表:
l2 <- list(a = list(x = 1), b = 2)
l2$a$x # 1
# 递归遍历:
unlist(l2) # 拍平为向量

数据框

data.frame 表格型数据,列可异质。默认 stringsAsFactors=FALSE(R 4.0+)。str/head 查看。

1
2
3
4
5
6
7
8
9
10
11
12
13
df <- data.frame(
name = c('alice', 'bob'),
age = c(30, 25),
admin = c(TRUE, FALSE)
)
df$age # 取列(向量)
df[2, ] # 第 2 行
df[['name']] # 取列(精确名)
nrow(df); ncol(df) # 2 3
str(df) # 结构概览
head(df, 2) # 前 2 行
# 增列:
df$score <- c(88, 92)

tibble

tibble 是 tidyverse 的现代数据框:惰性计算列、打印友好、不转字符串为因子。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# library(tibble)
tb <- tibble(
name = c('a', 'b'),
value = c(1, 2)
)
tb # 打印友好,显示类型
# 列可引用前面列:
tb2 <- tibble(x = 1:3, y = x * 2)
# 不转因子:
class(tb$name) # character
# 与 data.frame 互转:
df <- as.data.frame(tb)
tb3 <- as_tibble(df)
# 提取:
tb$value; tb[['value']]

属性

attributes 携带元信息:names、dim、class 等。attr 单独读取/设置。structure 一步构造。

1
2
3
4
5
6
7
8
9
10
11
12
x <- 1:3
attr(x, 'unit') <- 'cm' # 自定义属性
attributes(x) # 列出全部
attr(x, 'unit') # "cm"
# structure 一步构造:
y <- structure(1:3, unit = 'cm')
# 常用内置属性:
dim(m); names(df); class(x)
# 移除属性:
attributes(x) <- NULL
# 查看对象类:
class(c(1, 2)) # "numeric"

类型判断

is.* 判断类型,class/typeof/mode 看类型。as.* 转换。is.data.frame 等用于分支。

1
2
3
4
5
6
7
8
9
10
11
12
is.numeric(1) # TRUE
is.integer(1L) # TRUE
is.character('a') # TRUE
is.logical(TRUE) # TRUE
is.list(list()) # TRUE
is.data.frame(df) # TRUE
is.matrix(m) # TRUE
# class vs typeof:
class(data.frame()) # "data.frame"
typeof(list()) # "list"
# 判断 null / na:
is.null(NULL); is.na(NA)

4.引用与对象语义

R 无裸指针:copy-on-modify 值语义与 environment 引用语义。

写时复制

R 采用复制时修改(copy-on-modify):赋值共享数据,仅在被修改时才真正复制。大对象赋别名几乎零成本。

1
2
3
4
5
6
7
8
9
10
11
x <- 1:1e6
y <- x # 不复制,共享数据
# 修改 y 才触发复制:
y[1] <- 99
# 此时 x 不受影响:
x[1] # 1
# tracemem 可观察复制时机:
tracemem(x)
y2 <- x # 共享,不复制
y2[1] <- 1 # 此处发生复制
untracemem(x)

tracemem 追踪拷贝

tracemem 标记对象,R 在真正复制该对象时打印地址变化。用来诊断意外拷贝与内存开销。

1
2
3
4
5
6
7
8
9
10
x <- 1:1e6
tracemem(x) # 开始追踪
y <- x # 无输出:未复制
y[1] <- 0 # 输出 tracemem: 复制
untracemem(x) # 停止追踪
# 大数据框修改整列会复制:
df <- data.frame(a = 1:1e5, b = rnorm(1e5))
tracemem(df)
df$a <- df$a + 1 # 整框被复制
untracemem(df)

环境引用

environment 是 R 中的引用对象:传参给函数不会复制,函数内修改直接生效。相当于 R 的「指针」。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
e <- new.env() # 新建环境
e$x <- 1
e[['y']] <- 2
# 函数内修改环境(无需 <<-):
inc <- function(env) {
env$x <- env$x + 1
}
inc(e)
e$x # 2(引用语义生效)
# 读取:
get('x', envir = e)
ls(e) # "x" "y"
# 环境也是哈希表:
e$name <- 'Rex'

可变状态容器

环境作可变状态容器:计数器、缓存、累加器。避免全局变量污染,状态显式传递。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
counter <- function() {
e <- new.env()
e$n <- 0
function() {
e$n <- e$n + 1 # 修改被闭包捕获的环境
e$n
}
}
next_num <- counter()
next_num() # 1
next_num() # 2
# 缓存:按 key 存结果
cache <- new.env(hash = TRUE)
cache$key <- list(result = 42)

浅拷贝与深拷贝

列表赋值是浅拷贝:共享子对象,改嵌套元素会复制该层。深拷贝需显式实现。

1
2
3
4
5
6
7
8
9
10
11
12
l1 <- list(x = 1:3, y = list(a = 1))
l2 <- l1 # 浅拷贝,共享子对象
# 修改 l1 顶层触发该层复制:
l1$x[1] <- 99 # x 被复制,y 仍共享
# 深拷贝示例(递归复制):
deep_copy <- function(obj) {
if (is.list(obj)) lapply(obj, deep_copy)
else obj
}
l3 <- deep_copy(l1)
# data.table 有显式 copy():
# dt2 <- copy(dt)

引用类 R6

R6 是引用语义的类:对象按引用传递,方法修改原对象而非副本。适合状态管理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# library(R6)
Counter <- R6Class('Counter',
public = list(
n = 0,
increment = function() self$n <- self$n + 1
)
)
c1 <- Counter$new()
c2 <- c1 # 引用,指向同一对象
c2$increment()
c1$n # 1(c1 也被改了)
# R5 / setRefClass 类似引用语义:
# setRefClass 定义 field + methods
# 相比 S3/S4,R6 修改无 copy-on-modify

data.table 引用

data.table 用 := 原地修改(引用语义),data.frame 是复制语义。setDT 原地转 data.table。

1
2
3
4
5
6
7
8
9
10
11
12
# library(data.table)
dt <- data.table(a = 1:3, b = 4:6)
dt[, c := a + b] # 原地新增列 c,不复制
dt
# data.frame 修改是复制:
df <- as.data.frame(dt)
df$d <- 1 # 产生副本
# 原地设置/删除:
dt[, d := NULL] # 删列
setDT(df) # 原地转 data.table
# 显式复制:
dt2 <- copy(dt) # data.table::copy

对象大小

object.size 看单个对象占用,gc 看整体内存。lobstr::obj_size 能算共享量。

1
2
3
4
5
6
7
8
9
object.size(1:1e6) # ~8 MB
format(object.size(1:1e6), units = 'MB')
# 整体内存:
gc() # 使用/峰值(MB)
memory.size() # Windows 专用
# 共享对象实际占用:
# lobstr::obj_size(x, y) # 重复部分只算一次
# 大数据集:
# 可用 data.table 减少复制

显式复制

需要独立副本时显式复制:列表用 lapply 递归复制,data.table 用 copy,向量用 []。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 列表深复制:
l <- list(a = 1:3, b = list(x = 1))
l_copy <- lapply(l, function(x) {
if (is.list(x)) lapply(x, function(y) y)
else x
})
# data.table 副本:
# dt2 <- copy(dt)
# 向量复制:
v <- c(1, 2, 3)
v2 <- v[] # 显式复制
# 修改 v2 不影响 v:
v2[1] <- 99
v[1] # 1
# 检查是否共享:
# lobstr::ref(v, v2)

5.流程控制

if / else、向量化 ifelse、for / while、switch 与逻辑运算。

if / else

if 条件必须是长度 1。非 1 时取首元素并告警。else 与 if 同行结尾括号一致。

1
2
3
4
5
6
7
8
9
10
11
12
x <- 5
if (x > 0) {
print('正数')
} else if (x == 0) {
print('零')
} else {
print('负数')
}
# 条件长度为 1 才安全:
if (c(TRUE, FALSE)) print('x') # 警告并取首元素
# 赋值表达式:
y <- if (x > 0) 'pos' else 'neg'

ifelse 向量化

ifelse 对向量逐元素条件判断,返回与条件同长的结果。适合批量替换。

1
2
3
4
5
6
7
8
9
10
11
x <- c(-1, 0, 1, 2)
ifelse(x > 0, 'pos', 'non-pos')
# 嵌套:
ifelse(x > 0, 'pos',
ifelse(x == 0, 'zero', 'neg'))
# 注意 ifelse 会强制返回类型:
ifelse(x > 0, 1L, 0) # 都转 double
# 保留 NA:
ifelse(x > 0, 'pos', NA_character_)
# tidyverse 替代:
# dplyr::case_when(x > 0 ~ 'pos', TRUE ~ 'neg')

for 循环

for 遍历向量或序列。seq_along 按索引遍历,避免用 1:length 的空序列陷阱。

1
2
3
4
5
6
7
8
9
10
11
for (i in 1:5) print(i)
# 遍历向量元素:
for (ch in c('a', 'b')) print(ch)
# 按索引遍历:
x <- c(10, 20, 30)
for (i in seq_along(x)) {
x[i] <- x[i] * 2
}
x # 20 40 60
# 避免 1:length(x) 当 x 为空时出错
# 尽量向量化替代循环

while 与 repeat

while 先判条件再执行;repeat 无条件循环,靠 break 退出。二者都注意防止死循环。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
n <- 0
while (n < 3) {
n <- n + 1
}
n # 3
# repeat 至少执行一次:
total <- 0
repeat {
total <- total + 1
if (total >= 5) break
}
total # 5
# 死循环保护:设最大次数
# 条件永不满足时用 break/return

next 与 break

next 跳到下一轮,break 退出循环。适合跳过与提前终止。

1
2
3
4
5
6
7
8
9
10
11
12
for (i in 1:10) {
if (i %% 2 == 0) next # 跳过偶数
if (i > 7) break # 大于 7 终止
print(i) # 1 3 5 7
}
# 嵌套循环中 break 只退内层:
for (i in 1:3) {
for (j in 1:3) {
if (j == 2) break
cat(i, j, '\n')
}
}

switch

switch 按位置或名字分发。数字取第 n 个,字符匹配命名分支,未匹配返回 NULL。

1
2
3
4
5
6
7
8
9
10
11
12
13
switch(2, 'a', 'b', 'c') # "b"(位置)
op <- 'add'
switch(op,
add = 1 + 1, # 2
mul = 2 * 3,
'未知'
)
# 无默认时未匹配返回 NULL:
switch('nope', add = 1)
# 数字 + 缺失返回 NULL:
switch(5, 'a', 'b')
# match.arg 校验参数:
# match.arg(choice, c('a', 'b'))

向量化优于循环

R 用向量化替代显式循环:整向量一次运算,更快更简洁。循环留给不可向量化场景。

1
2
3
4
5
6
7
8
9
10
11
x <- 1:1e6
y <- x * 2 # 整向量乘,快
z <- x^2 + sqrt(x)
# 等价循环(慢,不推荐):
for (i in seq_along(x)) x[i] * 2
# 常用向量化函数:
sum(x); mean(x); cumsum(x)
which(x > 5) # 满足条件的索引
x[x > 5] # 筛选值
# 比较向量化:
any(x > 5); all(x > 0)

逻辑运算

& | 是向量化逻辑运算,&& || 只评估第一个元素(短路)。any/all 汇总。

1
2
3
4
5
6
7
8
9
10
11
12
13
x <- c(TRUE, FALSE, TRUE)
y <- c(FALSE, TRUE, TRUE)
x & y # FALSE FALSE TRUE
x | y # TRUE TRUE TRUE
!x # 取反
# 短路版本(单值):
TRUE && FALSE # FALSE
TRUE || stop('不执行') # 短路,不报错
# 汇总:
any(x); all(x)
# 比较链需要逐段写:
# x > 1 && x < 5
# 注意 && || 只取第一个元素

索引遍历

seq_along / seq_len 生成索引,rev 逆序。which 取位置。split 分组循环。

1
2
3
4
5
6
7
8
9
10
11
12
13
x <- c(3, 1, 4, 1, 5)
seq_along(x) # 1 2 3 4 5
seq_len(3) # 1 2 3
# 逆序遍历:
for (i in rev(seq_along(x))) print(x[i])
# which:
which(x == 1) # 2 4
which.max(x) # 5
# 按组循环:
grp <- split(x, x > 2)
lapply(grp, sum)
# 逐元素输出:
# sapply(x, function(v) ...)

6.函数与闭包

函数定义、参数、惰性求值、可变参数、闭包与管道。

函数定义

function 定义函数,函数体最后表达式为返回值。匿名函数直接用于参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
square <- function(x) {
x * x
}
square(5) # 25
# 单表达式可简写:
add1 <- function(x) x + 1
add1(2) # 3
# 匿名函数:
(function(x) x * 2)(10) # 20
# 函数即对象,可赋值:
f <- add1
f(1) # 2
# 查看函数体:
body(square)

参数与默认值

函数参数可设默认值。调用可省略参数名按位置传,也可命名传参。missing 判断是否提供。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
greet <- function(name, greeting = '你好') {
paste(greeting, name)
}
greet('张三') # 你好 张三
greet('张三', '早上好')
greet(greeting = '嗨', name = '李四')
# 检测参数是否缺省:
show <- function(x) {
if (missing(x)) return('未提供')
x
}
show() # 未提供
# ... 传透参数:
f <- function(x, ...) paste(x, ...)
f('a', 'b', 'c')

惰性求值

R 参数惰性求值:用到才求值,未用不报错。force 强制提前求值。这在闭包中很关键。

1
2
3
4
5
6
7
8
9
10
11
f <- function(a, b) a
f(1, stop('这行不执行')) # 1,b 未用
# 强制求值:
g <- function(a, b) {
force(b) # 立刻求值 b
a
}
g(1, stop('会报错')) # 抛错
# 惰性求值配合默认参数引用前面参数:
h <- function(x, y = x * 2) y
h(5) # 10

可变参数

... 捕获任意多参数。list(...) 收集,do.call 用列表动态调用函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
sum_all <- function(...) sum(...)
sum_all(1, 2, 3) # 6
# 收集为列表:
capture <- function(...) list(...)
capture(1, 'a', TRUE)
# do.call:动态传参
args <- list(x = 1:3, y = 1:3)
do.call(pmax, args)
# 向量元素拆开传入:
vals <- c(1, 2, 3)
do.call(sum, as.list(vals)) # 6
# ... 转发给其它函数:
wrap <- function(...) plot(...)

返回值

函数返回最后表达式。return 提前返回。invisible 返回值不自动打印。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
f <- function(x) x * 2 # 最后表达式即返回
f(3) # 6
# return 提前退出:
g <- function(x) {
if (x < 0) return('负数')
x * 2
}
g(-1) # 负数
# 多值用列表返回:
h <- function(x) {
list(sq = x^2, rt = sqrt(x))
}
h(4) # 列表含 sq rt
# 不打印的返回值:
invisible(42)

闭包

函数捕获创建它的环境,可携带私有状态。工厂函数生成带状态的新函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
make_adder <- function(n) {
function(x) x + n # 闭包捕获 n
}
add5 <- make_adder(5)
add5(3) # 8
# 计数器闭包:
counter <- local({
n <- 0
function() {
n <<- n + 1 # 修改环境中的 n
n
}
})
counter(); counter() # 1 2
# 环境是闭包的载体:
environment(add5)

匿名函数

匿名函数即函数字面量,常用于 apply 家族与 purrr。R 4.1+ 支持 \\(x) 简写。

1
2
3
4
5
6
7
8
9
10
11
lapply(1:3, function(x) x^2) # 1 4 9
sapply(1:3, function(x) x * 2)
# \\(x) 简写(R >= 4.1):
lapply(1:3, \(x) x^2)
# 直接调用:
(function(x) x + 1)(5) # 6
# purrr 管道风格:
# purrr::map(1:3, ~ .x * 2)
# 匿名函数在排序中的用法:
sort(c(3, 1, 2))
sort(c('b', 'a'), decreasing = TRUE)

高阶函数

函数可作为参数传给函数,也可返回函数。Map / Reduce / Filter 等内建高阶函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
Filter(is.numeric, list(1, 'a', 2))
Reduce(`+`, 1:5) # 15
Map(function(x, y) x + y, 1:3, 4:6)
# 返回函数的函数(柯里化):
curry_add <- function(a) {
function(b) a + b
}
curry_add(10)(5) # 15
# 常见模式:给数据框的每列应用函数
sapply(df, mean, na.rm = TRUE)
# 自定义高阶:
apply_twice <- function(f, x) f(f(x))
apply_twice(function(x) x + 1, 5) # 7

管道

|> 原生管道把左侧结果传给右侧函数首参,让嵌套调用变线性。%>% 是 magrittr 版本。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
x <- 1:10
x |> sum() # 55
# 链式管道:
1:10 |> mean() |> round(2)
# 传至非首参用占位符:
1:10 |> paste(collapse = '-') # 错误演示
# 原生管道占位符:
1:10 |> paste(_, collapse = '-')
# tidyverse 风格:
df |> dplyr::filter(age > 18) |>
dplyr::select(name) |>
dplyr::arrange(name)
# magrittr 管道 . 占位:
library(magrittr)
1:10 %>% sum()

7.字符串

字符向量、拼接、截取、分割、正则与格式化。

字符串基础

字符向量用引号,双引号与单引号等价。nchar 长度,[] 取字符。反斜杠需转义。

1
2
3
4
5
6
7
8
9
10
11
12
s <- 'hello'
"hi" # 等价
toupper('abc') # "ABC"
tolower('ABC') # "abc"
nchar('中文') # 2(字符数)
# 反斜杠转义:
cat('a\tb') # a b
# 原始字符串:
raw_s <- r"(C:\path\file)"
# 拼接:
paste('a', 'b') # "a b"
paste0('a', 'b') # "ab"

拼接

paste 默认空格分隔,paste0 无分隔。collapse 把整个向量合成一个字符串。向量化拼接。

1
2
3
4
5
6
7
8
9
10
11
12
paste('a', 'b', 'c') # "a b c"
paste0('a', 'b') # "ab"
paste('a', 'b', sep = '-') # "a-b"
# collapse:向量合成单串
paste(1:3, collapse = ',') # "1,2,3"
# 向量化组合:
paste(letters[1:3], 1:3) # "a 1" "b 2" "c 3"
# 循环回收组合:
paste(letters[1:2], 1:3) # 长度补成 3
# 字符数与字节:
nchar('你好') # 2
nchar('你好', type = 'bytes') # 6

截取

substr / substring 按位置截取,[] 中括号取单个字符。strsplit 分割。

1
2
3
4
5
6
7
8
9
10
s <- 'hello world'
substr(s, 1, 5) # "hello"
substring(s, 7) # "world"(到末尾)
# 取单个字符:
strsplit(s, '')[[1]] # 逐字符
# 替换子串:
substr(s, 7, 11) <- 'R!'
s # "hello R!"
# 按位置取多段:
substr('abcdef', c(1, 4), c(2, 6))

分割

strsplit 按分隔符拆成列表。unlist 拍平。正则也可作分隔。

1
2
3
4
5
6
7
8
9
10
11
s <- 'a,b,c'
strsplit(s, ',') # 列表 ["a" "b" "c"]
unlist(strsplit(s, ',')) # 向量
# 向量化分割:
strsplit(c('a-b', 'c-d'), '-')
# 正则分隔:
strsplit('a1b22c', '[0-9]+')
# 固定字符串匹配(非正则):
strsplit('a.b.c', '.', fixed = TRUE)
# 合并回来:
paste(unlist(strsplit(s, ',')), collapse = ';')

正则函数

grep / grepl 匹配,gsub / sub 替换。vectorized 全部按元素。详细见正则章节。

1
2
3
4
5
6
7
8
9
10
x <- c('apple', 'banana', 'cherry')
grepl('^a', x) # TRUE FALSE FALSE
grep('a', x) # 索引 1 2
# 替换:
gsub('a', 'o', x) # opple bonono cherory
sub('a', 'o', x) # 只换第一个
# 提取匹配:
regmatches(x, gregexpr('a', x))
# 大小写:
grepl('APPLE', x, ignore.case = TRUE)

格式化

sprintf 仿 C printf 格式化。%s 字符串,%d 整数,%f 浮点。formatC / round 控制数字。

1
2
3
4
5
6
7
8
9
10
11
sprintf('%s is %d', 'R', 4) # "R is 4"
sprintf('%.2f', pi) # "3.14"
sprintf('%5.1f', pi) # " 3.1"
sprintf('%05d', 42) # "00042"
# 百分号转义:
sprintf('100%%')
# 向量化:
sprintf('x%d', 1:3) # "x1" "x2" "x3"
# 数字格式:
format(pi, digits = 3)
formatC(12345, big.mark = ',')

大小写与修剪

toupper / tolower 转换大小写,trimws 去空白。chartr 字符替换。工具名处理常用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
toupper('hello') # "HELLO"
tolower('HELLO') # "hello"
# 首字母大写:
to_title <- function(s) {
paste0(toupper(substr(s, 1, 1)), substr(s, 2, nchar(s)))
}
to_title('hello') # "Hello"
# 去空白:
trimws(' hi ') # "hi"
trimws('\nhi\t', which = 'both')
# 字符级替换:
chartr('a-c', 'A-C', 'abc')
# 包名转下划线:
# gsub('([A-Z])', '_\\L\\1', s, perl = TRUE)

编码处理

R 字符串为 UTF-8 为主。Encoding 查看编码,enc2utf8 转换。iconv 转码。

1
2
3
4
5
6
7
8
9
10
11
12
13
s <- '你好'
Encoding(s) # "UTF-8"
enc2utf8(s) # 转 UTF-8
enc2native(s) # 转本机编码
# 编码转换:
iconv(s, from = 'UTF-8', to = 'GB18030')
# 字节视图:
charToRaw('A') # 41
rawToChar(as.raw(0x41)) # "A"
# 十六进制串:
paste(as.hexmode(charToRaw('R')), collapse = ' ')
# 读取文件指定编码:
# readLines('f.txt', encoding = 'UTF-8')

stringr

tidyverse 字符串处理包。str_ 前缀函数统一命名,str_detect / str_replace / str_extract。

1
2
3
4
5
6
7
8
9
10
11
12
# library(stringr)
str_detect(c('a1', 'b2'), '[0-9]')
str_replace('ab-cd', '-', '_')
str_replace_all('a-b-c', '-', '')
str_extract('price 99', '[0-9]+')
str_extract_all('a1 b2', '[0-9]')
str_remove('xx-abc', 'xx-')
str_split('a,b', ',')
str_pad('5', 3, pad = '0') # "005"
str_trim(' hi ')
str_to_title('hello world')
str_length('你好') # 2

8.集合与 apply 家族

apply / lapply / sapply 批量遍历,排序、去重、集合运算与 dplyr 数据操作。

lapply 与 sapply

lapply 对列表/向量逐元素应用函数并返回列表。sapply 尝试简化成向量或矩阵。

1
2
3
4
5
6
7
8
9
10
l <- list(a = 1:3, b = 4:5)
lapply(l, sum) # 列表,含 $a $b
lapply(l, function(x) x * 2)
# sapply 简化:
sapply(l, sum) # a=6 b=9 具名向量
sapply(1:4, sqrt)
# vapply 指定返回类型(更稳):
vapply(1:4, sqrt, numeric(1))
# unlist 拍平列表结果:
unlist(lapply(1:3, function(x) x^2))

apply

apply 沿矩阵/数组维度批量运算。MARGIN=1 按行、2 按列。返回值按维度拼。

1
2
3
4
5
6
7
8
9
10
m <- matrix(1:9, nrow = 3)
apply(m, 1, sum) # 每行和
apply(m, 2, sum) # 每列和
apply(m, 1, mean)
apply(m, 2, function(x) x / max(x))
# 3 维数组沿第三维:
a <- array(1:24, c(2, 3, 4))
apply(a, 3, sum)
# 附带索引的分组:
apply(m, 1, which.max)

mapply 多参

mapply 并行地对多个参数应用函数,对应 Map。短参数循环补齐。SIMPLIFY 控制简化。

1
2
3
4
5
6
7
8
9
mapply(paste, c('a', 'b'), c(1, 2))
# 对应 pmax 等:
mapply(max, c(1, 5), c(3, 2)) # 3 5
# Map 不简化,永远列表:
Map(paste, c('a', 'b'), c(1, 2))
# 多参数向量化:
mapply(function(x, y) x^y, 1:3, 1:3)
# 简化成矩阵:
mapply(function(x, y) c(x, y), 1:2, 3:4, SIMPLIFY = TRUE)

split 分组

split 按因子把向量拆成组列表。配合 lapply 做分组统计,等价 group-by。

1
2
3
4
5
6
7
8
9
10
11
12
13
x <- c(1, 2, 3, 4, 5)
g <- c('a', 'a', 'b', 'b', 'b')
split(x, g) # 列表 $a $b
# 分组求均值:
lapply(split(x, g), mean)
# 数据框按列分组:
df <- data.frame(grp = c('a', 'a', 'b'), val = 1:3)
split(df, df$grp)
# 统计概要:
sapply(split(df$val, df$grp), summary)
# tapply:一步到位
tapply(df$val, df$grp, mean)
# 对应 dplyr group_by + summarise

排序与排名

sort 排向量,order 返回排序索引,rank 排名。decreasing 控制方向。数据框按列排序。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
x <- c(3, 1, 2)
sort(x) # 1 2 3
order(x) # 2 3 1(索引)
x[order(x)] # 排序后
rev(sort(x)) # 降序
rank(c(3, 1, 2)) # 3 1 2
# 数据框排序:
df <- data.frame(age = c(30, 20), name = c('b', 'a'))
df[order(df$age), ]
# 多列排序:
df2 <- data.frame(a = c(1, 1, 2), b = c(2, 1, 3))
df2[order(df2$a, df2$b), ]
# dplyr:
# dplyr::arrange(df, desc(age))

去重

unique 去重,duplicated 标记重复项。任何重复行取第一个。all.equal 比较向量。

1
2
3
4
5
6
7
8
9
10
11
12
x <- c(1, 2, 1, 3, 2)
unique(x) # 1 2 3
duplicated(x) # FALSE FALSE TRUE FALSE TRUE
!duplicated(x) # 保留首现
x[!duplicated(x)]
# 数据框去重行:
df <- data.frame(a = c(1, 1, 2), b = c(1, 1, 3))
unique(df)
# 去重计数:
table(x)
# dplyr:
# dplyr::distinct(df, a, .keep_all = TRUE)

集合运算

union / intersect / setdiff 集合运算。%in% 判断元素存在。unique 后取交并差。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
a <- c(1, 2, 3); b <- c(2, 3, 4)
union(a, b) # 1 2 3 4
intersect(a, b) # 2 3
setdiff(a, b) # 1
setdiff(b, a) # 4
# 成员判断:
2 %in% a # TRUE
# 保留重复的并集:
c(a, b)
# 相等比较:
identical(a, b)
setequal(a, c(3, 2, 1)) # TRUE(忽略顺序)
# 用 %in% 过滤:
a[a %in% b] # 2 3

列表操作

组合列表、展平、批量改名。unlist 递归拍平,do.call 拼接为数组。purrr 类型化操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
l1 <- list(a = 1); l2 <- list(b = 2)
c(l1, l2) # 组合
unlist(l1) # 具名向量
# 列表每个元素加函数:
lapply(l1, function(x) x + 1)
# 改列表名:
names(l) <- c('x', 'y')
# 按条件筛元素:
l <- list(1, 'a', TRUE)
Filter(is.numeric, l)
# 拍平一层:
list_of_lists <- list(list(1, 2), list(3))
unlist(list_of_lists, recursive = TRUE)
# purrr:
# purrr::map(l, ~ .x * 2)
# purrr::keep(l, is.numeric)

dplyr 数据操作

select/filter/mutate/arrange/summarise 管道式操作数据框,group_by 分组。核心 tidyverse。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# library(dplyr)
df <- data.frame(
name = c('a', 'b', 'c'),
age = c(25, 35, 40),
salary = c(50, 60, 80)
)
df |> filter(age > 30)
df |> select(name, salary)
df |> mutate(salary2 = salary * 2)
df |> arrange(desc(age))
# 分组汇总:
df |> group_by(age > 30) |>
summarise(avg = mean(salary))
# 去重:
df |> distinct(age)
# 新增计算列并筛选:
df |> mutate(ratio = salary / age) |>
filter(ratio > 1.5)

9.内存管理

垃圾回收、对象大小、拷贝成本、预分配与性能分析。

垃圾回收

R 自动引用计数 + 周期 GC。gc 手动回收并报告。大数据循环后可调用。

1
2
3
4
5
6
7
8
9
10
11
gc() # 强制回收,返回统计
# gc 返回 Ncells/Vcells 使用与峰值:
gc(reset = TRUE) # 重置峰值统计
# 大对象不再用时置 NULL:
big <- 1:1e8
big <- NULL # 可回收
gc()
# 查看 gc 触发阈值:
gc(verbose = TRUE) # 打印回收明细
# 内存占用函数:
ls() # 查看当前对象

对象大小

object.size 看单个对象字节数。format 转可读单位。大数据集需预估内存。

1
2
3
4
5
6
7
8
9
10
11
object.size(1:1e6)
format(object.size(1:1e6), units = 'MB')
# 列表大小:
object.size(list(1:1e5, 1:1e5))
# 数据框:
df <- data.frame(x = 1:1e5, y = rnorm(1e5))
format(object.size(df), units = 'MB')
# 共享对象实际占用:
# lobstr::obj_size(x, y)
# 大数据集预估:
# 1e7 行数据框约几百 MB

拷贝成本

R 值语义在修改时复制整个对象,大对象修改代价高。避免频繁修改大向量元素。

1
2
3
4
5
6
7
8
9
10
11
12
13
x <- 1:1e7
# 整对象运算快(向量化):
y <- x * 2
# 循环内逐元素改:
for (i in 1:1000) x[i] <- x[i] + 1 # 触发大量复制
# 预分配替代:
out <- numeric(1e5) # 先分配
for (i in seq_along(out)) out[i] <- i
# 避免 c() 在循环里增长:
# res <- c() # 慢
# 用向量化或预分配
# 查看是否复制:
# tracemem(x)

预分配

先分配好长度的结果向量,再循环填充,避免反复拼接复制。numeric/character 预分配。

1
2
3
4
5
6
7
8
9
10
11
12
13
n <- 1e4
# 预分配 numeric 向量:
out <- numeric(n)
for (i in 1:n) out[i] <- i^2
# 预分配字符:
res <- character(n)
# 预分配列表:
results <- vector('list', n)
for (i in seq_len(n)) results[[i]] <- i * 2
# 避免方式(慢):
# res <- c(); for (i in 1:n) res <- c(res, i)
# 理想:完全向量化
sq <- (1:n)^2

向量化与性能

尽量整向量运算。行绑定 rbind 慢,用 do.call(rbind, list) 或 data.table。微基准测试。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
x <- 1:1e6
# 快:整向量
system.time(y <- x * 2 + sin(x))
# 慢:循环
# for (i in seq_along(x)) y[i] <- x[i] * 2
# 行绑定累积慢:
# for (...) df <- rbind(df, row) # 差
# 正确做法:收集列表再 do.call
rows <- lapply(1:100, function(i) data.frame(i = i))
big <- do.call(rbind, rows)
# 计时:
system.time(mean(x))
# 微基准:
# microbenchmark::microbenchmark(a, b)

内存上限

memory.limit(Windows)查看/调整上限。object.size 检查。ulimit 影响会话。

1
2
3
4
5
6
7
8
9
10
11
memory.limit() # Windows 内存上限 MB
# 可能需要:
# memory.limit(size = 8192)
# 当前已用:
memory.size()
# 其它平台无 memory.limit:
# 用系统资源工具监控
# 检查大对象:
ls() |> sapply(function(x) object.size(get(x)))
# 排序看最大:
sort(sapply(ls(), function(x) object.size(get(x))), decreasing = TRUE)[1:5]

性能分析

Rprof 记录函数调用耗时。summaryRprof 汇总。system.time 粗略计时。profvis 可视化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Rprof('prof.out')
slow <- function() {
for (i in 1:1e5) sqrt(i)
}
slow()
Rprof(NULL)
summaryRprof('prof.out')
# 简单计时:
system.time(slow())
# 精细计时:
# tictoc::tic(); slow(); tictoc::toc()
# 可视化:
# profvis::profvis(slow())
# 基准比较:
# bench::mark(a, b)

data.table 内存

data.table 用引用语义减少复制::= 原地改、setDT 转换、copy 显式复制。大数据集更省内存。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# library(data.table)
dt <- data.table(a = 1:1e5, b = rnorm(1e5))
# 原地新增/修改列(不复制):
dt[, c := a * 2]
# set 系列:
setkey(dt, a) # 排序键
setorder(dt, a) # 原地排序
# 取子集时避免复制:
sub <- dt[a > 100] # 复制子集
# 显式复制:
copy(dt)
# 共享列引用:
# set(dt, j = 'd', value = dt$a)
# 内存对比:
# df 修改整列会复制整框,dt 不会

10.面向对象 (S3 / S4 / R6)

S3 简易泛型、S4 形式化类、R6 引用类,三种 OO 系统各司其职。

S3 类基础

S3 是 R 的简易 OO:class 属性 + 泛型函数。unclass 查看底层。最常用、最轻量。

1
2
3
4
5
6
7
8
9
10
11
12
x <- 1:3
class(x) # "integer"
# 自定义类:
obj <- structure(list(a = 1), class = 'myclass')
class(obj) # "myclass"
# 泛型分派:
print(obj) # 调用 print.myclass
# 通用函数列表:
methods('print')
# 查看分派目标:
class(1:3)
unclass(1:3) # 去掉类属性

S3 方法

定义 class.method 函数即可实现泛型。UseMethod 分派。NextMethod 调父方法。

1
2
3
4
5
6
7
8
9
10
11
12
shape <- function(x) UseMethod('shape')
shape.default <- function(x) paste('default:', class(x)[1])
shape.circle <- function(x) paste('圆,半径', x$r)
# 构造对象:
c1 <- structure(list(r = 3), class = 'circle')
shape(c1) # 圆,半径 3
shape(1:3) # default
# 覆盖 print:
print.circle <- function(x) cat('Circle r =', x$r, '\n')
print(c1)
# 查看所有方法:
methods('shape')

S3 构造

自定义类需提供构造器与校验。structure 一步设类。print / summary 自定义输出。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
new_point <- function(x, y) {
stopifnot(is.numeric(x), is.numeric(y))
structure(list(x = x, y = y), class = 'point')
}
p <- new_point(1, 2)
# 泛型 print:
print.point <- function(obj, ...) {
cat('point(', obj$x, ',', obj$y, ')\n')
}
print(p)
# 泛型 summary:
summary.point <- function(obj, ...) {
cat('均值:', mean(c(obj$x, obj$y)), '\n')
}
summary(p)
# 泛型 + 校验:
# new_ 前缀是 tidyverse 习惯

S4 类

S4 是形式化 OO:setClass 定义槽,validity 校验,setGeneric / setMethod 泛型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
setClass('Person',
slots = c(name = 'character', age = 'numeric'),
validity = function(obj) {
if (obj@age < 0) 'age 不能为负' else TRUE
}
)
p <- new('Person', name = 'Rex', age = 5)
p@age # 5
slot(p, 'name') # "Rex"
# 泛型:
setGeneric('describe', function(x) standardGeneric('describe'))
setMethod('describe', 'Person', function(x) paste(x@name, x@age))
describe(p)
# 检查:
isS4(p)
# 继承:
# setClass('Student', contains = 'Person')

R6 类

R6 是引用语义封装类,方法里用 self$ 访问。面向对象编程更贴近其它语言。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# library(R6)
Animal <- R6Class('Animal',
public = list(
name = NULL,
initialize = function(name) self$name <- name,
speak = function() paste(self$name, '叫')
)
)
a <- Animal$new('狗')
a$speak() # "狗 叫"
# 私有成员:
Class <- R6Class('Class',
public = list(init = NULL),
private = list(hidden = 42)
)
# 继承:
Dog <- R6Class('Dog',
inherit = Animal,
public = list(speak = function() paste(self$name, '汪汪'))
)
d <- Dog$new('旺财')
d$speak()

泛型分派

分派基于对象的 class 属性,UseMethod 按 class 找对应方法。旧类(S3)层层尝试。

1
2
3
4
5
6
7
8
9
10
11
12
13
f <- function(x) UseMethod('f')
f.default <- function(x) '默认'
f.numeric <- function(x) '数字'
f.factor <- function(x) '因子'
f(1L) # 数字
f(factor('a')) # 因子
f('a') # 默认
# 多参数分派(S4):
# setMethod(..., signature = c('x', 'y'))
# 查看分派方法表:
methods('mean')
# class 可以是向量:
class(x) <- c('myclass', 'numeric')

继承

S3 用 class 属性继承(NextMethod 链),S4 用 contains,R6 用 inherit。

1
2
3
4
5
6
7
8
9
10
11
12
13
# S3 继承:
structure(list(a = 1), class = c('sub', 'base'))
# NextMethod 调父类方法:
f.base <- function(x) 'base 实现'
f.sub <- function(x) paste('sub:', NextMethod())
f(structure(list(), class = c('sub', 'base')))
# R6 继承已见 r6 主题:
# 用 super$ 访问父类方法
# S4 继承:
# setClass('Base')
# setClass('Derived', contains = 'Base')
# 检查继承:
# inherits(obj, 'base')

三套系统对比

S3 轻量用于大部分包;S4 用于严谨定义;R6 引用语义适合状态对象。实际多混用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# S3:类属性 + 泛型,无需声明
# 快速、无强制、多数包用
# S4:setClass 严格槽与校验
# 适合正式 API 与大型框架
# R6:引用语义、方法封装
# 适合可变状态、建模对象
# 混用示例:
# ggplot2 用 S3;
# Bioconductor 用 S4;
# 许多新包用 R6
# 选择建议:
# 简单数据对象用 S3,
# 复杂正式接口用 S4,
# 需要修改原对象用 R6

class 与属性

class 是特殊属性,泛型按它分派。attr 可加自定义属性,不影响分派。class<- 直接设类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
x <- 1:3
class(x) # "integer"
class(x) <- 'myclass' # 直接改类
x
# 查看全部属性:
attributes(x)
# 自定义属性:
attr(x, 'note') <- '说明'
attr(x, 'note')
# 判断类:
inherits(x, 'myclass') # TRUE
is.numeric(1:3)
# 泛型寻找 class 顺序:
# 先找 class[1],再 class[2]...,最后 default
# 移除类:
unclass(x)

11.错误处理

stop 报错、warning 警告、tryCatch 捕获与条件系统。

stop 报错

stop 抛出错误终止执行。stopifnot 快速校验条件。错误消息建议写清原因。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
divide <- function(a, b) {
if (b == 0) stop('除数不能为 0')
a / b
}
divide(1, 0) # 抛错
# stopifnot:
check <- function(x) {
stopifnot(is.numeric(x), length(x) > 0)
x
}
check('a') # 抛错
# 自定义错误类:
stop('自定义错误', call. = FALSE)
# 附条件对象:
# stop(simpleError('msg'))

warning 警告

warning 给出非致命提示,不中断。suppressWarnings 静默。options(warn=2) 转错误。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
warn_if <- function(x) {
if (any(is.na(x))) warning('存在 NA')
x
}
warn_if(c(1, NA))
# 静默:
suppressWarnings(warn_if(c(NA)))
# 把所有警告变错误:
options(warn = 2)
# 还原:
options(warn = 0)
# 一次性抓取警告:
withCallingHandlers(
expr,
warning = function(w) print('有警告')
)

tryCatch

tryCatch 捕获错误/警告并返回处理结果。错误、警告、finally 三部分。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
safe_div <- function(a, b) {
tryCatch(
a / b,
error = function(e) paste('错误:', conditionMessage(e)),
warning = function(w) paste('警告:', conditionMessage(w)),
finally = cat('完成\n')
)
}
safe_div(1, 0) # 错误: 除数不能为 0
safe_div(1, 2) # 0.5
# 只看错误:
tryCatch(stop('boom'), error = function(e) '捕获')
# 把结果与错误都保留:
tryCatch(list(ok = TRUE, val = 1), error = function(e) list(ok = FALSE))

try 容错

try 返回表达式结果,出错时返回 try-error 类对象,不中断整体。批量处理常用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
out <- try(log('a'), silent = TRUE)
class(out) # "try-error"
inherits(out, 'try-error') # TRUE
# 容错循环:
res <- vector('list', 3)
for (i in 1:3) {
res[[i]] <- try(log(i - 2), silent = TRUE)
}
# 过滤失败项:
failed <- sapply(res, inherits, 'try-error')
# 成功项:
res[!failed]
# 出错时给默认值:
out2 <- tryCatch(log(-1), error = function(e) NA)
out2

条件系统

R 错误/警告/消息都是条件对象。signalCondition 触发,withCallingHandlers 捕获并继续。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
signalCondition(simpleError('出错了'))
# 条件类:
stopifnot(identical(conditionMessage(simpleError('x')), 'x'))
# 消息:
message('普通提示')
# 捕获消息:
withCallingHandlers(
message('hi'),
message = function(m) cat('捕获:', conditionMessage(m), '\n')
)
# 条件包含调用栈:
f <- function() stop('deep')
# tryCatch 捕获:
tryCatch(f(), error = function(e) conditionCall(e))
# 条件对象可携带自定义字段:
# simpleError('msg', call = sys.call())

警告拦截

withCallingHandlers 捕获警告但不停止执行,可记录并继续。配合 invokeRestart。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
log_warnings <- function(expr) {
ws <- list()
res <- withCallingHandlers(
expr,
warning = function(w) {
ws[[length(ws) + 1]] <<- conditionMessage(w)
invokeRestart('muffleWarning')
}
)
list(result = res, warnings = ws)
}
out <- log_warnings({ warning('w1'); 42 })
out$result # 42
out$warnings # "w1"
# 与 tryCatch 区别:
# withCallingHandlers 捕获后可继续执行原表达式

restarts 恢复点

信号处理的高级机制:调用者可提供恢复动作。invokeRestart 在捕获端调用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 定义恢复点:
withRestarts(
{ signalCondition(simpleError('x')); '继续' },
my_restart = function() '恢复动作'
)
# 捕获并恢复:
withCallingHandlers(
withRestarts(stop('err'), abort = function() 'aborted'),
error = function(e) invokeRestart('abort')
)
# 常见恢复点:
# muffleWarning、muffleMessage
# 自定义:用户可在 error handler 里选择
# 适合交互式重试逻辑

循环内错误

批量处理时单个失败不应中断全部。tryCatch 逐项容错并保留结果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
items <- list(1, 'a', 3, -1)
process <- function(x) if (x < 0) stop('负数') else x * 2
# 安全处理:
out <- lapply(items, function(x) {
tryCatch(process(x), error = function(e) NA)
})
unlist(out) # 2 NA 6 NA
# 记录失败原因:
out2 <- lapply(items, function(x) {
tryCatch(list(ok = TRUE, v = process(x)),
error = function(e) list(ok = FALSE, msg = conditionMessage(e)))
})
# 继续处理:
# 可用 purrr::possibly / safely

自定义错误

自定义错误类携带额外字段。simpleError / structure 构造,class 标识类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
my_error <- function(msg, code = 500) {
structure(
list(message = msg, call = NULL, code = code),
class = c('my_error', 'error', 'condition')
)
}
stop(my_error('余额不足', code = 400))
# 捕获并读取字段:
fail <- function() stop(my_error('boom'))
tryCatch(
fail(),
my_error = function(e) paste('自定义:', e$code),
error = function(e) '其他错误'
)
# 创建条件类:
# condition <- structure(list(), class = c('x', 'condition'))

12.文件与数据 I/O

CSV、readr、逐行读取、RDS、JSON、连接对象与二进制。

CSV 读写

read.csv / write.csv 读写 CSV。stringsAsFactors=FALSE 防止转因子。check.names 处理列名。

1
2
3
4
5
6
7
8
9
10
11
12
13
df <- data.frame(a = 1:3, b = c('x', 'y', 'z'))
write.csv(df, 'out.csv', row.names = FALSE)
# 读回:
d <- read.csv('out.csv')
# 避免因子:
read.csv('out.csv', stringsAsFactors = FALSE)
# 自定义分隔:
write.table(df, 'out.tsv', sep = '\t', row.names = FALSE)
read.delim('out.tsv')
# 无表头:
read.csv('f.csv', header = FALSE)
# 指定编码:
# read.csv('f.csv', fileEncoding = 'UTF-8')

readr 读写

readr 是 tidyverse 的快速 CSV 读取。read_csv 自动推断类型,write_csv 快速写出。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# library(readr)
write_csv(df, 'out.csv')
read_csv('out.csv')
# 类型自动推断,更快:
read_csv('out.csv', col_types = 'di') # d=double i=integer
# 大数据集:
read_csv('big.csv', show_col_types = FALSE)
# 指定列名:
read_csv('f.csv', col_names = c('x', 'y'))
# 写回:
write_csv(df, 'out2.csv')
# 其它:
# read_tsv / write_tsv 制表符
# read_delim(file, delim = '|')
# 进度与类型见 README

逐行读取

readLines 按行读成字符向量。writeLines 写出。处理大文件时逐块读。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
writeLines(c('第一行', '第二行'), 'f.txt')
lines <- readLines('f.txt')
lines # "第一行" "第二行"
# 指定编码:
readLines('f.txt', encoding = 'UTF-8')
# 只读前几行:
readLines('f.txt', n = 1)
# 大文件分块:
con <- file('f.txt', 'r')
while (length(chunk <- readLines(con, n = 10)) > 0) {
cat('块大小', length(chunk), '\n')
}
close(con)
# 写出带编码:
writeLines(lines, 'out.txt', useBytes = FALSE)

read.table 读表

read.table 通用表格读取,read.csv 是其变体。header / sep / na.strings 常用参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 制表符分隔:
read.table('data.tsv', header = TRUE, sep = '\t')
# 无表头:
read.table('f.txt', sep = ',')
# 自定义 NA 值:
read.table('f.csv', header = TRUE, sep = ',', na.strings = c('NA', ''))
# 跳过行:
read.table('f.csv', skip = 2)
# 指定列类型:
read.table('f.csv', colClasses = c('numeric', 'character'))
# 行名:
read.table('f.csv', row.names = 1)
# 数量限制:
read.table('f.csv', nrows = 100)

RDS 存取

saveRDS 存单个 R 对象,readRDS 读回。保留类型与属性,比 CSV 完整。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
saveRDS(df, 'df.rds')
d2 <- readRDS('df.rds')
identical(df, d2) # TRUE
# 存多个对象:
save(df, x, file = 'all.RData')
load('all.RData') # 恢复 df 和 x
# 压缩:
saveRDS(df, 'df.rds', compress = TRUE)
# 读其它工具:
# readRDS 支持读取之前版本
# 大数据建议:
# data.table::fread / fwrite 更高效
# 检查文件:
# file.info('df.rds')

JSON 读写

jsonlite 是主流 JSON 包。fromJSON 解析,toJSON 序列化。行式 JSON 处理 API 返回。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# library(jsonlite)
json <- '{"name":"R","ver":4.4}'
fromJSON(json) # 具名列表
# 数据框转 JSON:
df <- data.frame(a = 1:2, b = c('x', 'y'))
toJSON(df)
# 嵌套结构:
fromJSON('{"x":[1,2],"y":{"z":true}}')
# 行式 JSON(每行一个对象):
stream_in(file('rows.jsonl'))
# 数组:
fromJSON('[1,2,3]')
# 写文件:
write(toJSON(df), 'out.json')
# 复杂结构转数据框:
# fromJSON('...', flatten = TRUE)

连接对象

连接是文件/网络等数据源抽象。file 打开、readLines / writeLines 读写、close 关闭。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
con <- file('f.txt', 'w')
writeLines('一行', con)
close(con)
# 读连接:
con <- file('f.txt', 'r')
content <- readLines(con)
close(con)
# 逐行处理:
con <- file('big.txt', 'r')
while (length(line <- readLines(con, n = 1)) > 0) {
# 处理 line
}
close(con)
# 文本/二进制模式:
file('f.bin', 'rb')
# url 连接:
url('https://example.com')
# 自动关闭:
# on.exit(close(con))

二进制读写

readBin / writeBin 读写二进制。raw 类型存字节。大文件与图像数据用二进制。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
x <- as.raw(c(0x48, 0x49)) # "H" "I"
writeBin(x, 'f.bin')
back <- readBin('f.bin', 'raw', n = 2)
# 读整数:
writeBin(1:3, 'ints.bin')
readBin('ints.bin', 'integer', n = 3)
# 字节长度:
length(x)
# 文件大小:
file.info('f.bin')$size
# 图像:
# png::readPNG / jpeg::readJPEG
# 读写大数据:
# 用 readBin 分块
# 检查 raw 视图:
charToRaw('A') # 41

其它格式

readxl 读 Excel,haven 读 SPSS/Stata,feather/parquet 列式格式。按需加载。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# Excel:
# library(readxl)
# read_excel('book.xlsx', sheet = 1)
# readxl::excel_sheets('book.xlsx')
# SPSS / Stata:
# library(haven)
# read_sav('data.sav'); read_dta('data.dta')
# 列式存储:
# library(arrow)
# write_parquet(df, 'df.parquet')
# read_parquet('df.parquet')
# feather:
# arrow::write_feather(df, 'df.feather')
# 数据库:
# library(DBI); dbConnect(RSQLite::SQLite())
# dbReadTable(con, 'tbl')

13.常见坑

R 日常最常踩的坑与正确写法。

条件长度不为 1

if 需要长度为 1 的条件,向量条件只判第一个并告警。用 any/all 汇总或 ifelse。

1
2
3
4
5
6
7
8
x <- c(1, 2, 3)
// BAD if 只判第一个元素,且有警告
if (x == 2) print('找到')
// GOOD 用 any / all 判断整个向量
if (any(x == 2)) print('找到')
if (all(x > 0)) print('全正')
// GOOD 逐元素条件用 ifelse
ifelse(x == 2, '命中', '未命中')

因子转数值

因子转数值直接 as.numeric 得到内部编码。先转字符再转数值才是真实值。

1
2
3
4
5
6
7
8
9
f <- factor(c('10', '20', '30'))
// BAD 得到内部编码 1 2 3
as.numeric(f)
// GOOD 先转字符再转数值
as.numeric(as.character(f))
// GOOD 或先 levels 索引
as.numeric(levels(f))[f]
# 读取 CSV 时可 stringsAsFactors = FALSE 预防
read.csv('f.csv', stringsAsFactors = FALSE)

维度丢失

矩阵取单行/单列默认降维成向量。drop = FALSE 保持维度。

1
2
3
4
5
6
7
8
9
10
11
12
m <- matrix(1:9, nrow = 3)
m[1, ] # 向量 1 4 7
// BAD 想按行拼接时维度丢
rbind(m[1, ], m[2, ])
// GOOD 保留行维度
rbind(m[1, , drop = FALSE], m[2, , drop = FALSE])
# 子集后想保留矩阵:
m[1:2, , drop = FALSE]
# 数据框取单列返回向量:
df <- data.frame(a = 1:3, b = 4:6)
df[1] # 仍是 data.frame
df[[1]] # 向量

循环增长向量

循环里 c() 拼接每次复制整个向量,极慢。先预分配或向量化。

1
2
3
4
5
6
7
8
9
10
11
n <- 5000
// BAD 每次 c() 都复制,O(n^2)
res <- numeric()
for (i in 1:n) res <- c(res, i)
// GOOD 预分配长度
res <- numeric(n)
for (i in 1:n) res[i] <- i
// GOOD 直接向量化(最快)
res <- 1:n
# 收集列表再拼接:
# do.call(c, lapply(1:n, function(i) i))

NA 与比较

NA 参与比较得到 NA,不会产生 FALSE。排除 NA 用 is.na 判断。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
x <- c(1, NA, 3)
x == NA # NA NA NA(不要这样)
// BAD 过滤 NA 后比较
x[x == 1] # NA 会漏进
// GOOD 先排除 NA
x[!is.na(x) & x == 1]
// GOOD 用 which 可忽略 NA
which(x == 1)
# 判断存在 NA:
any(is.na(x))
# NA 与 NaN 不同:
is.nan(NaN); is.na(NaN) # TRUE TRUE
# 统计时跳过:
sum(x, na.rm = TRUE)

部分匹配

$ 与 [[ 会做唯一前缀匹配。列名缩写的静默行为易藏 bug,用完整名或 dplyr。

1
2
3
4
5
6
7
8
9
10
11
12
13
df <- data.frame(age_group = 1:3, score = 4:6)
// BAD 静默匹配到 age_group
df$age_g
// GOOD 用完整列名
df$age_group
// GOOD 精确匹配 [[
df[['age_group']]
# 关闭部分匹配:
df[['age_g', exact = FALSE]] # 仍会模糊匹配
# dplyr 用 tidyselect,不模糊:
# dplyr::select(df, age_g) # 会报错提示
# 查看列名:
names(df)

循环回收

短向量参与运算会循环补齐到长向量长度。长度不成倍数时只有警告,易出隐蔽 bug。

1
2
3
4
5
6
7
8
9
10
11
x <- c(1, 2, 3, 4)
y <- c(10, 20)
// BAD 回收:y 补成 c(10, 20, 10, 20)
x + y # 11 22 13 24
// 不成倍数才告警:
x + c(1, 2) # 长度 4 vs 2,无警告
x + c(1, 2, 3) # 警告:4 不整除 3
// GOOD 显式确认长度一致
stopifnot(length(x) == length(y))
# 比较时循环回收同样隐蔽:
# x > c(1, 100)

列表扁平化

c() 对列表会尝试拍平顶层;把列表元素放列表里用 list()。unlist 递归拍平有隐忧。

1
2
3
4
5
6
7
8
9
10
11
12
13
l <- list(a = list(x = 1), b = list(y = 2))
// BAD c() 拍平了顶层列表
c(l$a, l$b)
// GOOD 保持嵌套
list(l$a, l$b)
// GOOD 需要拍平时明确 intent
unlist(l, recursive = FALSE)
# 拼接两个列表:
c(list(a = 1), list(b = 2))
# 查看结构:
str(c(l$a, l$b))
# 向量化元素收集:
# do.call(list, l)

包遮蔽

library 加载的包会遮蔽同名函数,search 顺序后面的先隐藏。用 :: 消除歧义。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 两个包都导出 filter:
# library(stats)
# library(dplyr) # dplyr 遮蔽 stats::filter
// BAD 遮蔽后 filter 含义随加载顺序漂移
filter(x, rep(1, 3)) # 可能是 dplyr::filter
// GOOD 显式命名空间调用
stats::filter(x, rep(1, 3))
dplyr::filter(df, a > 1)
# 查看遮蔽:
# find('filter')
# 卸载包:
# detach('package:dplyr')
# 只取特定函数:
# dplyr::select
# 用 conflicted 包检测:
# library(conflicted); conflict_prefer('filter', 'dplyr')

14.并行与并发

parallel、mclapply、foreach、future:R 的多进程并行。

parallel 概览

R 并行基于多进程(fork)或套接字。parallel 包内置 mcapply 与集群。并行有启动开销,任务要够大才划算。

1
2
3
4
5
6
7
8
9
10
library(parallel)
detectCores() # CPU 核数
detectCores(logical = FALSE) # 物理核
# 比较串行与并行耗时:
f <- function(i) sqrt(i)
system.time(lapply(1:1e5, f))
# 并行版本:
# system.time(mclapply(1:1e5, f))
# 小任务并行反而更慢(进程启动开销)
# 合适场景:耗时函数、大数据批处理

mclapply 并行

mclapply 是 parallel 的并行 lapply,fork 实现。仅 Unix 可用(Windows 需集群)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
library(parallel)
# Unix/macOS:
f <- function(i) {
Sys.sleep(0.01); i^2
}
# res <- mclapply(1:8, f, mc.cores = 4)
# Windows 用 mc.cores = 1 或集群
res <- lapply(1:8, f) # 串行兜底
# mc.preschedule:批量调度
# mclapply(x, f, mc.cores = 2, mc.preschedule = TRUE)
# 返回类型同 lapply:
unlist(res)
# 出错检查:
# is.atomic(res)

集群并行

makeCluster 建进程集群,parLapply 并行应用。Windows 也支持。用后 stopCluster 回收。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
library(parallel)
cl <- makeCluster(2) # 2 个 worker
# 各 worker 独立环境,需传数据:
clusterExport(cl, 'shared_data')
res <- parLapply(cl, 1:4, function(i) i * 2)
stopCluster(cl)
unlist(res)
# 每 worker 加载包:
# clusterEvalQ(cl, library(dplyr))
# 预设变量:
# clusterExport(cl, varlist = c('x', 'y'))
# 随机种子:
# clusterSetRNGStream(cl)
# Windows 下并行必用集群

foreach 并行

foreach 循环收集结果,%dopar% 并行执行(需 doParallel)。%do% 是串行版。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# library(foreach)
# library(doParallel)
# registerDoParallel(cores = 2)
res <- foreach(i = 1:4) %do% {
i * 2 # 串行
}
# 并行版:
# res <- foreach(i = 1:4, .combine = c) %dopar% i * 2
# 组合结果:
# .combine = c / rbind / list
# 传包与依赖:
# .packages = 'dplyr', .export = 'fun'
# 停止:
# stopImplicitCluster()
# 结果合并:
unlist(res)

future 异步

future 包把并行抽象为「未来值」。future() 起异步任务,value() 取结果。plan 选策略。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# library(future)
plan(multisession) # 多会话策略
f <- future({
Sys.sleep(0.1)
42
}) # 立即返回 future
value(f) # 42,阻塞取结果
# 批量:
# library(furrr)
# plan(multisession)
# future_map(1:4, ~ .x * 2)
# 策略切换:
# plan(sequential) # 回串行
# 错误传递:
# try(value(future(stop('x'))))
# 全局变量自动传:
# future({ x + 1 }) 中的 x 自动带入

共享状态

并行 worker 不共享内存:各自拷贝环境。写文件要防并发冲突,结果用 return 收集。

1
2
3
4
5
6
7
8
9
10
11
12
# 数据要显式传给 worker:
shared <- 1:10
# mclapply(shared, function(x) x * 2)
# 用 clusterExport 或 future 自动捕获
# 并发写同一文件会冲突:
# 每 worker 写独立文件:
# file <- paste0('out_', i, '.csv')
# 汇总收集:
res <- parLapply(cl, 1:4, function(i) i^2)
final <- Reduce(`+`, res)
# 全局变量在 worker 不可见:
# 必须显式导出,否则报找不到对象

随机数种子

并行时随机数需每 worker 独立种子。set.seed 全局 + clusterSetRNGStream 或 future.seed 保证可复现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
set.seed(42)
rnorm(3) # 可复现串行
# 集群并行种子:
# clusterSetRNGStream(cl)
# 或:
# clusterSetRNGStream(cl, 123)
# foreach 并行种子:
# registerDoParallel(); set.seed(1)
# future 自动管理:
# future.seed = TRUE
# 验证可复现:
set.seed(7)
a <- rnorm(5)
set.seed(7)
b <- rnorm(5)
identical(a, b) # TRUE

并行性能

并行收益受任务粒度、核数与通信开销限制。先 profile 串行瓶颈,再并行最大块。

1
2
3
4
5
6
7
8
9
10
11
12
# 并行开销远大于任务本身时不要并行
f <- function(i) sqrt(i)
system.time(lapply(1:1e5, f))
# system.time(mclapply(1:1e5, f, mc.cores = 4))
# 合适:每个任务耗时 >= 几十 ms
# 线性扩展观察:
# time(1 core) vs time(4 cores)
# 通信瓶颈:大数据来回复制慢
# 建议:把结果整理成小对象返回
# 避免共享大对象频繁传
# 测速工具:
# microbenchmark::microbenchmark(...)

15.网络请求

下载、httr2/httr 请求、JSON API、URL 处理与网页抓取。

下载文件

download.file 下载文件。mode 指定二进制。R.utils 支持断点续传。慢速连接可设 timeout。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
download.file(
'https://example.com/data.csv',
'data.csv',
mode = 'wb'
)
# 指定超时:
# download.file(url, dest, timeout = 60)
# 检查结果:
file.info('data.csv')$size
# 读入下载的数据:
read.csv('data.csv')
# 二进制数据:
# download.file(url, 'img.png', mode = 'wb')
# 镜像相关:
# setInternet2(TRUE) # 旧 Windows

httr2 请求

httr2 是新一代 HTTP 客户端。req_perform 执行,resp_body_json 解析。管道式构建请求。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# library(httr2)
req <- request('https://api.example.com')
req <- req %>%
req_url_path_append('v1') |>
req_url_query(q = 'rlang') |>
req_headers('Authorization' = 'Bearer xx')
# resp <- req_perform(req)
# resp_body_json(resp)
# 错误处理:
# tryCatch(req_perform(req), error = function(e) ...)
# 限速:
# req_throttle(req, rate = 10)
# 认证:
# req_auth_bearer_token(req, 'token')

GET 请求

GET 读取资源。base 的 readLines 可拉文本;httr GET + content 解析。query 参数拼 URL。

1
2
3
4
5
6
7
8
9
10
11
12
13
# base 简单 GET:
txt <- readLines('https://example.com')
# library(httr)
resp <- httr::GET('https://api.example.com/items')
httr::status_code(resp) # 200
httr::content(resp, 'text')
# 带查询参数:
httr::GET('https://api.example.com/search',
query = list(q = 'rlang', page = 2))
# 响应头:
httr::headers(resp)
# 连接超时:
httr::GET(url, httr::timeout(30))

POST 请求

POST 提交数据。body 传 JSON 或表单。httr POST 与 httr2 req_body_json 两种风格。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# library(httr)
httr::POST(
'https://api.example.com/login',
body = list(user = 'a', pass = 'b'),
encode = 'form'
)
# JSON 体:
httr::POST(
'https://api.example.com/submit',
body = '{"x":1}',
content_type_json()
)
# httr2 风格:
# req_body_json(req, list(x = 1))
# 响应解析:
# resp <- httr::POST(...)
# httr::content(resp, 'parsed')
# 上传文件:
# httr::POST(url, body = list(f = upload_file('f.csv')))

JSON API

调用 JSON API:请求 + jsonlite 解析。结果常是嵌套列表,flatten 转数据框。分页与错误处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# library(httr)
# library(jsonlite)
url <- 'https://api.github.com/repos/tidyverse/dplyr'
resp <- httr::GET(url)
stopifnot(httr::status_code(resp) == 200)
js <- httr::content(resp, 'text')
info <- jsonlite::fromJSON(js)
info$full_name
info$stargazers_count
# 列表转数据框:
# fromJSON(js, flatten = TRUE)
# 分页:
# 用 query 参数 page / per_page 循环
# 统一错误处理:
# tryCatch(..., error = ...)

URL 处理

parse_url 拆分 URL 组件,URLencode 编码。URLdecode 还原。base 内置处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# library(httr)
u <- parse_url('https://user:[email protected]/a?b=1&c=2')
u$scheme; u$hostname; u$path
u$query # b=1 c=2
# 修改后重组:
u$query$d <- 3
build_url(u)
# URL 编码:
URLencode('a b/c') # a%20b%2Fc
URLdecode('%E4%BD%A0%E5%A5%BD')
# 编码中文:
URLencode('你好')
# 相对路径解析:
# url_absolute('/a', 'https://x.com')
# 查询字符串解析:
# parse_url('https://x.com/?a=1')$query

网页抓取

rvest 解析 HTML。html_elements 选节点,html_text 取文本。遵守 robots 与频率限制。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# library(rvest)
url <- 'https://example.com'
page <- read_html(url)
page %>% html_elements('h1') %>% html_text()
page %>% html_elements('a') %>%
html_attr('href')
# 表格:
# page %>% html_table()
# CSS 选择器:
page %>% html_elements('#main p')
page %>% html_elements('.item')
# XPath:
page %>% html_elements(xpath = '//div[@class="x"]')
# 尊重网站规则:
# 频率限制、User-Agent 标识
# 合法用途:公开数据分析

TCP 套接字

socketConnection 建立 TCP 连接,读写字符串。适合简单协议与内网服务。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
con <- socketConnection(
host = 'localhost',
port = 9999,
server = FALSE, # 客户端
open = 'r+b'
)
# 写入:
writeLines('hello', con)
# 读取:
line <- readLines(con, n = 1)
close(con)
# 起服务端:
# srv <- socketConnection(host = 'localhost',
# port = 9999, server = TRUE)
# 注意:阻塞等待
# 复杂协议建议用 httpuv / plumber
# 生产接口建议用 API 框架而非裸 socket

16.时间与日期

Sys.Date / POSIXct、格式化、lubridate 与时区。

当前时间

Sys.Date 今天日期,Sys.time 当前日期时间。date 当前时间字符串。

1
2
3
4
5
6
7
8
9
10
11
12
13
Sys.Date() # 2026-08-02
Sys.time() # POSIXct 日期时间
format(Sys.time())
date() # "Sat Aug ... 2026"
# 取组件:
as.POSIXlt(Sys.time())
unclass(as.POSIXlt(Sys.time()))
# 时间戳:
unclass(Sys.time()) # 秒
# 自定时区:
Sys.timezone()
# 手动设置:
# Sys.setenv(TZ = 'Asia/Shanghai')

日期基础

Date 类是日期类型。as.Date 转换字符串,加减天数。unclass 看天数数值。

1
2
3
4
5
6
7
8
9
10
11
12
13
d <- as.Date('2026-08-02')
class(d) # "Date"
d + 1 # 明天
Sys.Date() - d # 相差天数
difftime(Sys.Date(), d, units = 'days')
# 解析其它格式:
as.Date('2026/08/02')
as.Date('02-08-2026', format = '%d-%m-%Y')
# 序列:
seq(as.Date('2026-01-01'), by = 'day', length.out = 3)
# 组件:
format(d, '%Y-%m-%d')
format(d, '%A') # 星期几

POSIXct

POSIXct 存秒级时间戳,POSIXlt 拆分组件。as.POSIXct 解析字符串,处理时区。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
t <- as.POSIXct('2026-08-02 10:30:00')
class(t) # POSIXct POSIXt
unclass(t) # 自 1970 起秒数
# 指定时区:
as.POSIXct('2026-08-02 10:30:00', tz = 'UTC')
# 转组件:
lt <- as.POSIXlt(t)
lt$year + 1900 # 2026
lt$mon + 1 # 8 月
lt$mday; lt$hour; lt$min
# 格式化为字符串:
format(t, '%Y-%m-%d %H:%M:%S')
# 加减:
t + 3600 # +1 小时
# 与 Date 互转:
as.Date(t)

格式化

strftime / format 按占位符输出。%Y 年 %m 月 %d 日 %H 时 %M 分 %S 秒。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
d <- Sys.Date()
t <- Sys.time()
format(d, '%Y-%m-%d')
format(d, '%d/%m/%Y')
format(d, '%A') # 星期名
format(t, '%H:%M:%S')
format(t, '%Y 年第 %j 天')
# 解析:
as.Date('02/08/2026', format = '%d/%m/%Y')
# strptime:
strptime('2026-08-02 10:00', format = '%Y-%m-%d %H:%M')
# 占位符速查:
# %Y 四位年 %y 两位年 %m 月 %d 日
# %H %M %S 时/分/秒 %A 星期名
# %j 一年中的第几天

lubridate 简介

lubridate 提供友好日期函数:ymd / ymd_hms 解析,year / month 取组件,加减间隔。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# library(lubridate)
ymd('20260802')
ymd('2026-08-02')
ymd_hms('2026-08-02 10:30:00')
# 取组件:
year(t); month(t); day(t)
hour(t); minute(t); second(t)
wday(t) # 星期(数字)
# 加减:
t + days(1); t - weeks(2)
months(3) # 3 个月
# 间隔:
interval(d, Sys.Date()) |> as.duration()
# 时区设置:
with_tz(t, 'UTC')
# 四舍五入:
round_date(t, 'hour')
floor_date(t, 'day')

日期序列

seq 生成日期序列:by 日/月/年。任意间隔工作日用 seq.Date。

1
2
3
4
5
6
7
8
9
10
11
seq(as.Date('2026-01-01'), as.Date('2026-01-10'), by = 'day')
seq(as.Date('2026-01-01'), by = 'month', length.out = 3)
seq(as.Date('2026-01-01'), by = 'year', length.out = 2)
# 指定数量:
seq(as.Date('2026-01-01'), as.Date('2026-12-31'), length.out = 5)
# seq.Date 更明确:
seq.Date(as.Date('2026-01-01'), by = '2 days', length.out = 3)
# 与工作日:
# 过滤周末:
x <- seq(as.Date('2026-01-01'), by = 'day', length.out = 30)
x[!weekdays(x) %in% c('Saturday', 'Sunday')]

时间差

日期相减得 difftime。units 指定单位。as.numeric 取数值。用于耗时统计。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
t0 <- Sys.time()
Sys.sleep(0.5)
Sys.time() - t0 # Time difference
# 指定单位:
difftime(Sys.time(), t0, units = 'secs')
difftime(Sys.time(), t0, units = 'mins')
# 取数值:
as.numeric(Sys.time() - t0)
# 日期差:
Sys.Date() - as.Date('2026-01-01')
# 平均耗时:
# mean 对 difftime 有效
# 自定义单位:
# difftime(t2, t1, units = 'hours')
# 计时工具:
# system.time(expr)
# tictoc::tic() / toc()

时区

tz 参数指定时区。Sys.timezone 当前时区。OlsonNames 列出合法时区。比较跨时区时刻。

1
2
3
4
5
6
7
8
9
10
11
12
Sys.timezone() # 当前时区
OlsonNames() # 全部合法时区名
# 指定时区解析:
as.POSIXct('2026-08-02 10:00', tz = 'Asia/Shanghai')
# 同一时刻不同时区显示:
t <- as.POSIXct('2026-08-02 10:00', tz = 'UTC')
format(t, tz = 'Asia/Shanghai')
format(t, tz = 'America/New_York')
# 时区换算:
with_tz(t, 'Asia/Shanghai') # lubridate
# UTC 时间戳:
# as.integer(as.POSIXct('2026-08-02', tz = 'UTC'))

17.进程与环境

运行系统命令、环境变量、命令行参数、平台信息与路径。

运行命令

system 运行系统命令并返回退出码。system2 传参更安全。输出捕获用 capture 或 intern。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
system('echo hello')
system2('echo', 'hello')
# 捕获输出:
system('date', intern = TRUE)
system2('date', stdout = TRUE)
# 退出码:
code <- system('ls')
code # 0 成功
# 传参避免 shell 注入:
# system2('cp', c('a.txt', 'b.txt'))
# 忽略输出:
system('echo x', ignore.stdout = TRUE)
# 超时:
# system2('cmd', timeout = 10)

环境变量

Sys.getenv 读、Sys.setenv 写。unsetenv 删除。PATH / R 相关变量常用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Sys.getenv('PATH')
Sys.setenv(MY_VAR = 'hello')
Sys.getenv('MY_VAR') # "hello"
Sys.unsetenv('MY_VAR')
# 列出全部:
Sys.getenv()
# 带默认值:
Sys.getenv('NOPE', unset = 'default')
# 常见变量:
Sys.getenv('HOME')
Sys.getenv('R_VERSION') # 或:
R.version.string
# 条件判断:
if (nzchar(Sys.getenv('CI'))) print('在 CI 环境')

命令行参数

commandArgs 取脚本参数。trailingOnly 去掉 R 自身参数。解析用 optparse 或 base 手写。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 运行:Rscript app.R --name Rex
args <- commandArgs(trailingOnly = TRUE)
args # "--name" "Rex"
# 简易解析:
get_arg <- function(name) {
i <- which(args == name)
if (length(i)) args[i + 1] else NULL
}
get_arg('--name')
# 专业解析:
# library(optparse)
# parser <- OptionParser(option_list = list(
# make_option('--name', type = 'character')))
# opts <- parse_args(parser)
# 默认值:
# 参数个数校验:
stopifnot(length(args) >= 1)

退出状态

quit 退出 R。q('no') 不保存工作区。错误退出用 quit(status = 1) 供脚本判断。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
quit(save = 'no') # 退出,不保存
# 指定状态码:
# quit(status = 1) # 失败退出
# 条件退出:
if (!file.exists('data.csv')) {
message('缺文件')
quit(status = 1)
}
# 正常结束脚本:
# Rscript 脚本末尾自然结束
# 捕获退出码(shell):
# Rscript app.R; echo $?
# 阻止退出用 browse:
# browser() # 调试暂停

平台信息

R.version 版本,Sys.info 系统信息,.Platform 平台细节。路径分隔等跨平台处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
R.version.string # R 4.4.x
version # 完整版本
Sys.info() # 系统信息列表
Sys.info()['sysname'] # Windows/Linux/Darwin
.Platform$file.sep # / 或 \
.Platform$OS.type # "windows" / "unix"
# 条件判断:
if (.Platform$OS.type == 'windows') 'Win' else 'Unix'
# 架构:
R.version$arch
# 路径分隔符:
# file.path 自动处理:
file.path('a', 'b', 'c.txt') # 平台正确分隔

路径管理

file.path 拼接路径,dirname / basename 拆分,normalizePath 规范绝对路径。file.exists 判断。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
file.path('data', 'sub', 'f.csv')
basename('a/b/f.csv') # "f.csv"
dirname('a/b/f.csv') # "a/b"
file.exists('f.csv')
dir.exists('data')
# 工作目录:
getwd()
setwd('data') # 谨慎修改
# 规范路径:
normalizePath('..')
# 展开 ~:
path.expand('~/R')
# 列出目录:
list.files('.')
list.files('.', pattern = '\\.csv$')
# 创建目录:
dir.create('out', showWarnings = FALSE)

延时等待

Sys.sleep 暂停指定秒数。可用于限速或等待外部资源就绪。单位是秒。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Sys.sleep(1) # 暂停 1 秒
t0 <- Sys.time()
Sys.sleep(0.5)
Sys.time() - t0
# 循环限速:
for (i in 1:3) {
cat(i, '\n')
Sys.sleep(0.2) # 每 0.2s 一次
}
# 等待文件出现:
# while (!file.exists('done.txt')) Sys.sleep(1)
# 等待网络资源:
# 配合 httr timeout 使用
# 注意:阻塞当前进程

Rscript 脚本

Rscript 是非交互执行入口,适合定时任务与批处理。脚本开头可加 shebang。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 脚本开头(Unix):
# #!/usr/bin/env Rscript
# 运行:
# $ Rscript script.R arg1
# 使用:
args <- commandArgs(trailingOnly = TRUE)
message('开始处理: ', args[1])
# 输出结构化结果:
cat('RESULT:', mean(1:100), '\n')
# 退出码:
# 出错时 quit(status = 1)
# 批处理管道:
# 读 stdin:
input <- readLines(file('stdin'), n = 1)
# 写 stdout 即可管道接力

18.正则表达式

grep / grepl、gsub 替换、regexpr 定位、stringr 与常用模式。

grep 与 grepl

grep 返回匹配索引,grepl 返回逻辑向量。ignore.case 忽略大小写。value 返回匹配值。

1
2
3
4
5
6
7
8
9
10
11
12
13
x <- c('apple', 'banana', 'apricot')
grepl('^ap', x) # TRUE FALSE TRUE
grep('^ap', x) # 1 3
grep('^ap', x, value = TRUE) # "apple" "apricot"
grep('^AP', x, ignore.case = TRUE)
# 固定匹配:
grepl('a.', x, fixed = TRUE) # 字面 .
# 计数:
sum(grepl('a', x))
# 反选:
x[!grepl('^ap', x)]
# 多模式:
grepl('a|b', c('x', 'a'))

gsub 替换

gsub 替换全部匹配,sub 只替换第一个。\\1 引用捕获组。perl=TRUE 扩展语法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
gsub('a', 'o', 'banana') # "bonono"
sub('a', 'o', 'banana') # "bonana"
# 删除匹配:
gsub('[0-9]', '', 'a1b2')
# 捕获组引用:
gsub('(\\d{4})-(\\d{2})', '\\2/\\1', '2026-08')
# 多个替换:
gsub('a|b', 'x', 'abacus')
# 忽略大小写:
gsub('a', 'o', 'ABC', ignore.case = TRUE)
# 固定字面:
gsub('a.b', 'x', 'a.b', fixed = TRUE)
# 向量化:
gsub('s', 'S', c('sun', 'sea'))

regexpr 定位

regexpr 返回首个匹配位置与长度,gregexpr 返回全部。regmatches 提取匹配文本。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
s <- 'price is 99 dollars'
m <- regexpr('[0-9]+', s)
m # 位置与长度
regmatches(s, m) # "99"
# 全部匹配:
ms <- gregexpr('[a-z]+', 'a1bc2def')
regmatches('a1bc2def', ms) # 列表
# 提取匹配与捕获:
# 正则匹配后 unlist:
unlist(regmatches('x12y34', gregexpr('[0-9]+', 'x12y34')))
# 无匹配时:
regexpr('z', 'abc') # -1
# 匹配位置作为索引:
# 结合 substring 用

语法基础

基础正则元字符:^ 开头 $ 结尾 . 任意 . 字符类 [] 分组 () 量词 * + ? {} 转义 \\。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
'^abc' # 以 abc 开头
'abc$' # 以 abc 结尾
'^[a-z]+$' # 全小写字母
'[0-9]{2,4}' # 2 到 4 位数字
'colou?r' # colour/color
'a.c' # a?c 任一字符
'\\.' # 字面点号
'[^0-9]' # 非数字
'(ab|cd)' # 分组或
'\\d' '\\w' '\\s' # 数字/字母/空白
# R 里反斜杠要双写:
# 正则 \d 在 R 字符串写成 \\d
# 验证:
grepl('^[0-9]+$', '123') # TRUE

常用模式

常用模式速查:邮箱、电话、日期、空白清理、提取数字。按需求微调。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 提取数字:
regmatches('v1.2.3', regexpr('[0-9]+', 'v1.2.3'))
# 去掉空白:
gsub('[[:space:]]', '', 'a b c')
# 邮箱简配:
pat <- '^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}$'
grepl(pat, '[email protected]') # TRUE
# 日期 YYYY-MM-DD:
pat <- '^\\d{4}-\\d{2}-\\d{2}$'
grepl(pat, '2026-08-02')
# 中文:
grepl('[\\u4e00-\\u9fa5]', '你好')
# 提取括号内容:
sub('.*\\((.+)\\).*', '\\1', 'name(value)')
# 分隔:
strsplit('a,b;c', '[,;]')

stringr 匹配

stringr 正则语法一致但接口统一。str_detect / str_extract / str_match 及 _all 版本。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# library(stringr)
str_detect(c('a1', 'b2'), '\\d')
str_extract('price 99', '\\d+')
str_extract_all('a1 b22', '\\d+')
str_match('a=1;b=2', 'a=(\\d+)')
str_match_all('a1b2', '([ab])(\\d)')
# 位置:
str_locate('abcabc', 'b')
str_locate_all('abcabc', 'b')
# 替换:
str_replace('a-b', '-', '_')
str_replace_all('a-b-c', '-', '+')
# 拼接正则边界:
str_detect('apple', regex('^ap'))

stringr 替换

str_replace / str_replace_all 替换。regexp 可用 fixed / perl。str_remove 删除匹配。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# library(stringr)
str_replace('one-two', '-', '_')
str_replace_all('a-b-c', '-', '')
# 捕获引用:
str_replace('2026-08', '(\\d{4})-(\\d{2})', '\\2/\\1')
# 删除:
str_remove('xx-abc', 'xx-')
str_remove_all('a1b2', '\\d')
# 固定字面:
str_replace_all('a.b', fixed('.'), 'X')
# 大小写无关:
str_replace('ABC', 'a', 'x', regex(ignore_case = TRUE))
# 向量化自动:
str_replace_all(c('a1', 'b2'), '\\d', '#')
# 边界匹配:
str_replace('abc', '^a', 'X')

标志与扩展

perl=TRUE 启用 PCRE 扩展:前瞻、命名组。fixed=TRUE 字面匹配。ignore.case。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 前瞻:
grepl('a(?=b)', 'ab', perl = TRUE) # TRUE
# 非前瞻:
grepl('a(?!b)', 'ac', perl = TRUE) # TRUE
# 命名组:
# gsub('(?<y>\\d+)', '\\k<y>', s, perl = TRUE)
# 非贪婪:
regmatches('a<b>c<b>', regexpr('<.+?>', 'a<b>c<b>', perl = TRUE))
# 多行 / 点匹配换行:
# grepl('a.b', 'a\nb', perl = TRUE)
# fixed 字面:
grepl('a.b', 'a.b', fixed = TRUE)
# 全部标志组合:
# grepl(pat, x, ignore.case = TRUE, perl = TRUE)

19.包管理与构建

CRAN 安装、renv 依赖管理、包结构与 R CMD、testthat、roxygen。

安装包

install.packages 从 CRAN 安装。update.packages 更新。library 加载。开发版用 devtools / remotes。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
install.packages('dplyr')
# 从镜像安装:
install.packages('ggplot2', repos = 'https://cloud.r-project.org')
# 更新:
# update.packages()
# 开发版:
# remotes::install_github('tidyverse/dplyr')
# 本地:
# install.packages('path/pkg_0.1.0.tar.gz', repos = NULL)
# 查看已装:
rownames(installed.packages())
# 加载:
library(dplyr)
# 依赖安装:
# install.packages(c('dplyr', 'tidyr'))

CRAN 与仓库

CRAN 是官方包仓库。repos 指定镜像。available.packages 列出可安装。CRAN 政策约束发布。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 当前仓库:
getOption('repos')
# 全部可用包:
ap <- available.packages()
ap[1:5, 'Package']
# 搜索包:
# available.packages() 按字段 grep
# 包信息:
# packageDescription('dplyr')
# 依赖:
# packageDescription('dplyr')$Depends
# CRAN 检查:
# R CMD check 通过才可上架
# 镜像列表:
# https://cran.r-project.org/mirrors.html
# 中国镜像:
# https://mirrors.tuna.tsinghua.edu.cn/CRAN/

renv 依赖管理

renv 锁定项目依赖版本,类似 Python venv。renv::init 初始化,snapshot / restore 同步锁文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 初始化:
# renv::init()
# 安装依赖:
# renv::install('dplyr')
# 记录依赖:
# renv::snapshot() # 写 renv.lock
# 换机恢复:
# renv::restore()
# 查看状态:
# renv::status()
# 隔离项目库:
# renv::activate()
# 升级:
# renv::update()
# 使用注意:
# .Rprofile 会自动加载 renv
# 提交 renv.lock 到版本控制

包结构

R 包标准结构:DESCRIPTION、R/、man/、tests/、NAMESPACE。R/ 放源码,man/ 放文档。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# mypkg/
# ├── DESCRIPTION 元数据:包名/版本/依赖
# ├── NAMESPACE 导出哪些函数
# ├── R/ 源码文件 *.R
# ├── man/ 文档 *.Rd
# ├── tests/ 测试
# └── data/ 内置数据
# DESCRIPTION 关键字段:
# Package: mypkg
# Version: 0.1.0
# Imports: dplyr
# 用 usethis 生成骨架:
# usethis::create_package('mypkg')
# 文档:
# usethis::use_roxygen_md()

R CMD 命令

R CMD 系列命令构建/检查包:build 打包、check 检查、INSTALL 安装。发布前必经 check。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 构建 tar.gz:
# R CMD build mypkg
# 检查(关键步骤):
# R CMD check mypkg_0.1.0.tar.gz
# 安装:
# R CMD INSTALL mypkg
# 常用检查项:
# --as-cran 模拟 CRAN 严格检查
# 运行时等价:
# system('R CMD INSTALL --help')
# 在 R 会话内:
# system2('Rscript', c('-e', '1+1'))
# devtools 封装:
# devtools::check()

testthat 测试

testthat 是主流测试框架。expect_equal 等断言,test_that 组织用例。usethis 生成测试文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# library(testthat)
f <- function(x) x * 2
test_that('f 正确加倍', {
expect_equal(f(2), 4)
expect_identical(f(0), 0)
expect_true(f(-1) < 0)
expect_error(f('a'))
})
# 运行:
# testthat::test_dir('tests')
# 或开发时:
# devtools::test()
# 常用断言:
# expect_equal / expect_identical
# expect_true / expect_false
# expect_warning / expect_message
# 与 CI 结合自动跑

格式与检查

styler 统一代码风格,lintr 静态检查。配合 RStudio Addins 或 pre-commit 保持整洁。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# library(styler)
# 格式化文件:
# styler::style_file('R/foo.R')
# 格式化整个包:
# styler::style_pkg()
# 检查风格差异:
# styler::style_file('R/foo.R', dry = 'only')
# lintr 检查:
# lintr::lint('R/foo.R')
# 常用配置:
# .lintr 文件指定 linters
# 自动执行:
# pre-commit hooks
# CI 集成:
# lintr 作为 check 步骤

roxygen 文档

roxygen2 用注释生成 .Rd 文档。#' 开头,@param / @return / @export 标签。函数文档与源码同处。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#' 两数相加
#'
#' @param a 第一个数
#' @param b 第二个数
#' @return
#' @export
add2 <- function(a, b) a + b
# 生成文档:
# devtools::document()
# 或:
# roxygen2::roxygenise()
# 生成 NAMESPACE 导出:
# @export 自动写入 NAMESPACE
# 包级文档:
# @keywords internal
# 检查文档:
# R CMD check

会话复现

sessionInfo 记录版本与依赖,保证可复现。renv.lock 锁依赖。R 版本也要记录。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
sessionInfo() # R 版本 + 已加载包
R.version.string
# 输出示例:
# R version 4.4.x
# Platform: ...
# 附加包及其版本
# 复现要点:
# 1. 记录 sessionInfo() 输出
# 2. renv::snapshot() 锁依赖
# 3. 固定 R 版本(Docker 镜像)
# 检查缺失:
# 依赖清单:
# renv::dependencies()
# 报告问题时附上:
# sessionInfo() + 最小可复现代码

官方链接

直达官方文档与资源。

关于本速查

本页是 R 4.4 的自包含速查手册,覆盖 base R 与 tidyverse 在真实数据分析中最常用的约 80% 场景。内容偏向现代惯用法:原生管道 `|>`、匿名函数 `\(x)` 简写、`data.frame` 配 `stringsAsFactors = FALSE`、向量化 `ifelse`、`purrr::map_*` 类型化集合操作,以及 R 独特的复制时修改(copy-on-modify)与 environment 引用语义。权威参考见官方 R 手册与 R for Data Science。 19 个章节各自聚焦一个主题——从第一个程序、变量与类型到 apply 家族、S3/R6 面向对象、并行与网络。每节拆成 8–9 个带示例的小节(每个 5–20 行),共约 160 个主题。代码片段刻意短小、自解释,复制即可粘贴进 R 或 RStudio 运行。 所有处理都在浏览器中完成——无上传、无追踪。本页是 GuruToolkit 免费开发者工具集的一部分;代码片段可自由使用,无任何担保。

版本 2.1.0