向量与基础
变量、类型与赋值
R 使用 <- 作为首选赋值运算符(= 有效但在函数参数中可能有歧义)。R 中的一切都是向量——即使是单个数字也是长度为 1 的向量。核心类型是 character、numeric(double)、integer、logical 和 complex。NA 表示缺失数据并通过运算传播(使用 na.rm=TRUE 跳过)。NULL 是值的缺失(空对象),与 NA 不同。分析前始终检查 NA。
# assignment operators (all equivalent for simple values)
name <- "Alice" # preferred
age = 30L # = also works (avoid in functions)
30L -> age2 # rightward assignment
# basic types (called "modes" in R)
class("text") # "character"
class(42) # "numeric" (double)
class(42L) # "integer"
class(3.14) # "numeric"
class(TRUE) # "logical"
class(2 + 3i) # "complex"
# check and convert types
is.numeric(age) # TRUE
is.character(name) # TRUE
as.integer(3.9) # 3 (truncates, not rounds)
as.character(42) # "42"
as.numeric("3.14") # 3.14
# special values
NA # missing value (Not Available)
NULL # null object (no value)
NaN # Not a Number (0/0)
Inf # infinity (1/0)
is.na(NA) # TRUE
is.null(NULL) # TRUE创建与索引向量
c() 将值组合成向量——R 最基础的函数。R 从 1 开始索引(第一个元素是 [1],不是 [0])。负索引排除元素:nums[-1] 删除第一个。逻辑索引(nums[nums > 3])按条件筛选——非常强大。向量可以有名称,从而可以按标签访问。向量的所有元素必须是相同类型;如果混合类型,R 会强制转换(例如,c(1, 'a') 变为 c('1', 'a'))。
# create vectors with c() (combine)
nums <- c(1, 2, 3, 4, 5)
chars <- c("a", "b", "c")
logs <- c(TRUE, FALSE, TRUE)
# sequences
1:5 # 1 2 3 4 5
seq(1, 10, by = 2) # 1 3 5 7 9
seq(0, 1, length.out = 5) # 0 0.25 0.5 0.75 1
rep(1:3, times = 2) # 1 2 3 1 2 3
rep(1:3, each = 2) # 1 1 2 2 3 3
# indexing (1-indexed!)
nums[1] # 1 (first element)
nums[length(nums)] # 5 (last element)
nums[c(1, 3, 5)] # 1 3 5 (multiple indices)
nums[-1] # 2 3 4 5 (all EXCEPT first)
nums[-c(1, 2)] # 3 4 5 (exclude first two)
nums[2:4] # 2 3 4 (range)
# logical indexing
nums[nums > 2] # 3 4 5
nums[nums %% 2 == 0] # 2 4 (even numbers)
nums[nums > 2] <- 0 # modify in place
# named vectors
ages <- c(alice = 30, bob = 25, carol = 28)
ages["alice"] # 30
ages[c("alice", "bob")] # 30 25向量运算与函数
R 的向量化是其标志性特性——运算自动逐元素应用,无需循环。回收机制重用较短的向量以匹配较长的(c(1,2,3,4) + c(10,20) 得到 11,22,13,24)。order() 返回将排序向量的索引——对于按一个向量排序另一个向量至关重要。unique() 去除重复项。所有这些函数底层都是优化的 C 代码,使 R 在向量运算上很快。
# arithmetic is element-wise (vectorized)
a <- c(1, 2, 3)
b <- c(4, 5, 6)
a + b # 5 7 9
a * b # 4 10 18
a ^ 2 # 1 4 9
a / b # 0.25 0.4 0.5
# recycling: shorter vector repeats
c(1, 2, 3, 4) + c(10, 20) # 11 22 13 24
# summary functions
sum(a) # 6
mean(a) # 2
median(a) # 2
sd(a) # 1
var(a) # 1
min(a); max(a) # 1; 3
range(a) # 1 3
cumsum(a) # 1 3 6
cumprod(a) # 1 2 6
# sorting and ordering
sort(c(3, 1, 2)) # 1 2 3
sort(c(3, 1, 2), decreasing = TRUE) # 3 2 1
order(c(3, 1, 2)) # 2 3 1 (indices that would sort)
v[order(v)] # sort using order
# useful functions
length(a) # 3
unique(c(1, 1, 2, 2, 3)) # 1 2 3
rev(a) # 3 2 1
head(a, 2) # 1 2 (first n)
tail(a, 2) # 2 3 (last n)字符与字符串操作
paste/paste0 是 R 的字符串连接函数——paste0 没有分隔符(类似 Python 的 +)。substr 提取子字符串(从 1 开始索引)。gsub 替换所有匹配项;sub 只替换第一个。grep 返回匹配元素的索引;grepl 返回逻辑向量(对筛选更有用)。R 默认使用 POSIX 扩展正则表达式。sprintf 提供 C 风格格式化。stringr 包(tidyverse)提供更干净、更一致的 API。
# paste and paste0 (concatenation)
paste("Hello", "World") # "Hello World"
paste("Hello", "World", sep = "_") # "Hello_World"
paste0("a", "b", "c") # "abc" (no separator)
paste(c("a", "b", "c"), collapse = "-") # "a-b-c"
paste("file", 1:3, ".csv", sep = "") # "file1.csv" "file2.csv" "file3.csv"
# case conversion
toupper("hello") # "HELLO"
tolower("WORLD") # "world"
# substring
substr("Hello World", 1, 5) # "Hello"
nchar("Hello") # 5 (character count)
# split and replace
strsplit("a,b,c", ",")[[1]] # "a" "b" "c"
gsub("o", "0", "Hello World") # "Hell0 W0rld" (all matches)
sub("o", "0", "Hello World") # "Hell0 World" (first match only)
gsub("[0-9]+", "N", "a1b22c333") # "aNbNcN" (regex)
# grep and grepl (pattern matching)
grep("^A", c("Alice", "Bob", "Anna")) # 1 3 (indices)
grepl("^A", c("Alice", "Bob")) # TRUE FALSE
# sprintf (C-style formatting)
sprintf("Pi = %.2f", pi) # "Pi = 3.14"
sprintf("%s is %d", "Alice", 30) # "Alice is 30"缺失值与强制转换
NA(Not Available)表示缺失数据并通过大多数运算传播——始终使用 na.rm=TRUE 或将它们过滤掉。NULL 不同:它是值的缺失并会从向量中删除。R 在组合时强制转换为最通用的类型(logical < integer < numeric < character)。对非数值字符串使用 as.numeric 会产生 NA 并发出警告。ifelse 是向量化三元运算符——对于从连续变量创建分类变量极其有用。
# NA propagation and handling
x <- c(1, 2, NA, 4, 5)
mean(x) # NA (NA propagates!)
mean(x, na.rm = TRUE) # 3 (skip NA)
sum(x, na.rm = TRUE) # 12
is.na(x) # FALSE FALSE TRUE FALSE FALSE
sum(is.na(x)) # 1 (count of NAs)
x[!is.na(x)] # 1 2 4 5 (remove NAs)
# NULL vs NA
length(c(1, NA, 3)) # 3 (NA is an element)
length(c(1, NULL, 3)) # 2 (NULL is dropped)
# type coercion hierarchy: logical < integer < double < character
c(TRUE, 1) # 1 1 (logical -> numeric)
c(1L, 2.5) # 1.0 2.5 (integer -> double)
c(1, "a") # "1" "a" (numeric -> character)
# explicit coercion
as.numeric(c("1", "2", "abc")) # 1 2 NA (with warning)
as.logical(c(0, 1, 2)) # FALSE TRUE TRUE
factor(c("low", "high", "low")) # factor with 2 levels
# ifelse vectorized conditional
ifelse(x > 2, "big", "small") # "small" "small" NA "big" "big"数据结构
列表(异构容器)
列表是 R 最灵活的数据结构——它们可以保存任何类型和大小的元素(类似 Python 字典或 JavaScript 对象)。$ 运算符是命名访问的便捷快捷方式。关键区别:[ ] 返回子列表(仍然是列表),而 [[ ]] 提取实际元素。这是 R 初学者困惑的头号来源。当你想要值本身时使用 [[ ]],当你想要子集时使用 [ ]。lapply/sapply 遍历列表元素应用函数。
# lists can hold different types and sizes
user <- list(
name = "Alice",
age = 30,
scores = c(90, 85, 88),
active = TRUE
)
# access by name ($ or [[]])
user$name # "Alice"
user[["age"]] # 30
user[["scores"]][2] # 85
# [] returns a sublist (list); [[]] returns the element
user["name"] # list with one element
user[["name"]] # "Alice" (the string itself)
user[1:2] # sublist with first 2 elements
# modify and add
user$age <- 31
user$email <- "[email protected]" # add new element
user[["scores"]] <- NULL # remove element
# iterate over a list
for (key in names(user)) {
cat(key, ":", user[[key]], "\n")
}
# lapply and sapply on lists
lapply(user$scores, sqrt) # list of square roots
sapply(user$scores, sqrt) # vector of square roots数据框
数据框是 R 的主要表格数据结构——类似电子表格或 SQL 表,其中每列可以是不同类型。用 $ 或 [[ ]] 访问列;用逻辑索引筛选行(df[df$age > 25, ])。subset() 是更干净的替代方案。cbind 添加列;rbind 添加行。str() 显示结构(类型和预览)。始终设置 stringsAsFactors=FALSE(或使用 R 4.0+,这是默认值)以将字符串保持为字符而非因子。
# create a data frame (like a table/spreadsheet)
df <- data.frame(
name = c("Alice", "Bob", "Carol"),
age = c(30, 25, 28),
score = c(90, 85, 88),
stringsAsFactors = FALSE
)
# dimensions
nrow(df) # 3
ncol(df) # 3
dim(df) # 3 3
names(df) # "name" "age" "score"
str(df) # structure summary
head(df, 2) # first 2 rows
# access columns
df$name # vector (by name)
df[["age"]] # vector (by name, alternative)
df[, "age"] # vector (column)
df[, 2] # vector (by index)
df[2] # data frame with one column
# access rows
df[1, ] # first row (as data frame)
df[1:2, ] # first 2 rows
df[c(1, 3), ] # rows 1 and 3
# filter rows (logical indexing)
df[df$age > 26, ] # rows where age > 26
df[df$score >= 88, c("name", "score")]
subset(df, age > 26, select = c(name, score))
# add and modify columns
df$grade <- c("A", "B", "A") # add column
df$age <- df$age + 1 # modify column
df <- cbind(df, pass = df$score > 60) # column bind
# add rows
new_row <- data.frame(name = "Dan", age = 22, score = 75, grade = "C")
df <- rbind(df, new_row)矩阵与数组
矩阵是 2D 数组,其中所有元素必须是相同类型(与数据框不同)。%*% 是矩阵乘法;* 是逐元素。solve() 计算矩阵逆;det() 计算行列式。rowSums/colSums/rowMeans/colMeans 是快速的内置快捷方式。apply(m, MARGIN, FUN) 是跨行(MARGIN=1)或列(MARGIN=2)应用函数的通用方式。数组将矩阵扩展到 n 维。对于数据分析,优先使用数据框;线性代数使用矩阵。
# create a matrix (2D, all same type)
m <- matrix(1:12, nrow = 3, ncol = 4)
# [,1] [,2] [,3] [,4]
# [1,] 1 4 7 10
# [2,] 2 5 8 11
# [3,] 3 6 9 12
# fill by row instead of column
m2 <- matrix(1:6, nrow = 2, byrow = TRUE)
# dimensions and attributes
dim(m) # 3 4
nrow(m) # 3
ncol(m) # 4
rownames(m) <- c("r1", "r2", "r3")
colnames(m) <- c("c1", "c2", "c3", "c4")
# indexing
m[2, 3] # 8 (single element)
m[1, ] # 1 4 7 10 (first row)
m[, 2] # 4 5 6 (second column)
m[1:2, 2:3] # 2x2 submatrix
m["r1", "c2"] # 4 (by name)
# matrix operations
t(m) # transpose
m * m # element-wise multiply
m %*% t(m) # matrix multiplication
solve(m[, 1:3]) # inverse (square matrix)
det(matrix(1:4, 2)) # determinant
rowSums(m) # sum of each row
colMeans(m) # mean of each column
apply(m, 1, sum) # row sums (general)
apply(m, 2, max) # column maxima
# arrays (n-dimensional)
arr <- array(1:24, dim = c(2, 3, 4)) # 2x3x4 array
arr[1, 2, 3] # single element因子与分类数据
因子将分类数据高效存储为带标签映射的整数代码——对统计建模至关重要(lm、glm 使用因子进行分组)。常见陷阱:as.numeric(factor) 给出整数代码,而不是原始值——始终先通过 as.character 转换。cut() 将连续数据分箱为因子水平。ordered=TRUE 创建支持比较运算的有序因子。relevel 更改参考类别(对回归解释很重要)。table() 生成频数计数。
# factors represent categorical data (stored as integers with labels)
gender <- factor(c("male", "female", "male", "female"))
print(gender)
# [1] male female male female
# Levels: female male
levels(gender) # "female" "male" (sorted alphabetically)
table(gender) # frequency table
nlevels(gender) # 2
# ordered factor (for ordinal data)
size <- factor(c("M", "S", "L", "XL"),
levels = c("S", "M", "L", "XL"),
ordered = TRUE)
size[1] > size[2] # TRUE (M > S)
# convert to factor and back
nums <- factor(c(1, 2, 3, 2, 1))
as.numeric(nums) # 1 2 3 2 1 (level codes, NOT original values!)
as.numeric(as.character(nums)) # 1 2 3 2 1 (correct way)
# relevel (change reference level)
gender <- relevel(gender, ref = "male")
# cut: convert numeric to factor (binning)
ages <- c(15, 25, 35, 45, 55, 65)
age_groups <- cut(ages, breaks = c(0, 18, 35, 50, 100),
labels = c("child", "young", "adult", "senior"))
table(age_groups) # frequency per group
# interaction of factors
f1 <- factor(c("A", "A", "B", "B"))
f2 <- factor(c("X", "Y", "X", "Y"))
interaction(f1, f2) # A.X A.Y B.X B.Y列表到数据框与重塑
在宽格式和长格式之间重塑是常见的数据整理任务。pivot_longer/pivot_wider(tidyr、tidyverse)是现代、直观的函数。宽格式每个主体一行,每个时间点一列;长格式每次观察一行。长格式更适合 ggplot2 和大多数分析。split() 按因子将数据框分成列表;do.call(rbind, ...) 重新组合。基础 R 的 reshape() 函数功能强大但界面混乱——优先使用 tidyr。
# convert list to data frame
my_list <- list(
a = 1:3,
b = c("x", "y", "z")
)
df <- as.data.frame(my_list)
# stack multiple vectors
do.call(rbind, list(
data.frame(name = "A", val = 1),
data.frame(name = "B", val = 2)
))
# reshape: wide to long
library(tidyr)
wide_df <- data.frame(
id = 1:2,
q1 = c(10, 20),
q2 = c(15, 25)
)
long_df <- pivot_longer(wide_df, cols = starts_with("q"),
names_to = "quarter", values_to = "value")
# long to wide
pivot_wider(long_df, names_from = quarter, values_from = value)
# base R reshape (without tidyr)
reshape(wide_df, direction = "long",
varying = c("q1", "q2"), v.names = "value",
timevar = "quarter", idvar = "id")
# split and combine
split_results <- split(df, df$group) # list of data frames by group
combined <- do.call(rbind, split_results) # recombine
# melt and cast (reshape2 package, legacy)
# library(reshape2)
# melt(wide_df, id.vars = "id")
# dcast(long_df, id ~ quarter, value.var = "value")控制流与函数
If / Else 与 Switch
R 的 if/else 对于多行体需要花括号,且 else 必须与右花括号在同一行(否则 R 认为 if 已完成)。ifelse(test, yes, no) 是向量化的——它一次应用于整个向量,返回结果向量。对于多个条件,dplyr 的 case_when 比嵌套 ifelse 干净得多。switch 根据字符串(或数值位置)分派——是长 if-else 链的干净替代方案。
# if-else if-else
score <- 85
if (score >= 90) {
grade <- "A"
} else if (score >= 80) {
grade <- "B"
} else {
grade <- "C"
}
# ifelse (vectorized ternary)
ages <- c(15, 25, 35, 45)
ifelse(ages >= 18, "adult", "minor")
# "minor" "adult" "adult" "adult"
# nested ifelse (avoid deep nesting!)
ifelse(ages < 18, "minor",
ifelse(ages < 65, "adult", "senior"))
# dplyr::case_when is cleaner for multiple conditions
# case_when(
# ages < 18 ~ "minor",
# ages < 65 ~ "adult",
# TRUE ~ "senior"
# )
# switch (dispatch on a value)
day_type <- function(day) {
switch(day,
"Mon" = "weekday",
"Tue" = "weekday",
"Wed" = "weekday",
"Thu" = "weekday",
"Fri" = "weekday",
"Sat" = "weekend",
"Sun" = "weekend",
"unknown"
)
}循环:For、While、Repeat
R 中的 for 循环遍历元素(或序列)。当 x 可能为空时,seq_along(x) 比 1:length(x) 更安全(它返回 integer(0) 而不是 c(1,0))。next 跳到下一次迭代(类似 continue);break 退出。repeat 是必须显式中断的无限循环。始终预分配结果向量(result <- numeric(N))——在循环中用 c() 增长向量是 O(n²) 且极慢。但是,尽可能优先使用向量化运算或 apply 家族而非循环。
# for loop
for (i in 1:5) {
print(i)
}
# iterate over a vector
fruits <- c("apple", "banana", "cherry")
for (fruit in fruits) {
print(paste("Fruit:", fruit))
}
# iterate with index
for (i in seq_along(fruits)) {
print(paste(i, fruits[i]))
}
# while loop
count <- 0
while (count < 5) {
count <- count + 1
if (count == 3) next # skip (like continue)
print(count)
}
# repeat loop (infinite, must break)
i <- 0
repeat {
i <- i + 1
if (i >= 3) break # exit loop
print(i)
}
# preallocate for speed (IMPORTANT!)
result <- numeric(1000)
for (i in 1:1000) {
result[i] <- i^2
}
# nested loops
mat <- matrix(0, 3, 3)
for (i in 1:3) {
for (j in 1:3) {
mat[i, j] <- i * j
}
}函数定义与参数
R 函数自动返回最后求值的表达式(不需要显式 return,尽管 return() 对提前退出更清晰)。默认参数使函数灵活。...(省略号)捕获要传递的额外参数——对包装函数至关重要。命名参数可以按任何顺序。R 使用惰性求值:参数仅在首次使用时求值,因此未使用的参数不会导致错误。通过将多个值打包到列表中来返回多个值。
# basic function (last expression is returned)
add <- function(a, b) {
a + b
}
add(3, 4) # 7
# explicit return
is_positive <- function(x) {
if (x > 0) return(TRUE)
FALSE
}
# default arguments
greet <- function(name, greeting = "Hello", punctuation = "!") {
paste0(greeting, ", ", name, punctuation)
}
greet("Alice") # "Hello, Alice!"
greet("Bob", greeting = "Hi") # "Hi, Bob!"
greet(name = "Carol", punctuation = "?") # named args
# ... (ellipsis: pass arguments through)
my_plot <- function(x, y, ...) {
plot(x, y, col = "blue", ...)
}
my_plot(1:10, 1:10, main = "My Plot", type = "l")
# return multiple values via list
stats <- function(x) {
list(mean = mean(x), sd = sd(x), n = length(x))
}
result <- stats(1:10)
result$mean # 5.5
# lazy evaluation (args evaluated only when used)
f <- function(a, b) {
a * 2 # b is never used, so not evaluated
}
f(5) # 10 (no error despite missing b)Apply 家族
apply 家族用函数式迭代替代循环——更符合习惯且通常更快。lapply 始终返回列表;sapply 尝试简化为向量/矩阵(方便但不可预测);vapply 是保证返回类型的安全版本。apply 作用于矩阵(MARGIN=1 为行,2 为列)。tapply 按因子分组数据并应用函数——类似小型 GROUP BY。replicate 重复随机模拟。对于数据框,purrr 包(tidyverse)提供更干净、更一致的 map() 家族。
# lapply: apply function to each element of a list, returns list
my_list <- list(a = 1:3, b = 4:6, c = 7:9)
lapply(my_list, mean) # list: 2, 5, 8
lapply(my_list, sum) # list: 6, 15, 24
# sapply: like lapply but simplifies to vector/matrix
sapply(my_list, mean) # 2 5 8 (named vector)
sapply(my_list, range) # matrix with 2 rows
# vapply: like sapply but with guaranteed output type (safer)
vapply(my_list, mean, numeric(1)) # always numeric vector
# apply: apply function over matrix/array margins
m <- matrix(1:12, nrow = 3)
apply(m, 1, sum) # row sums: 22 26 30
apply(m, 2, mean) # column means: 2 5 8 11
apply(m, c(1, 2), sqrt) # element-wise sqrt
# mapply: multivariate apply (vectorized over multiple args)
mapply(rep, 1:3, 3:1) # rep(1,3), rep(2,2), rep(3,1)
# tapply: apply function by groups
df <- data.frame(
group = c("A", "A", "B", "B", "B"),
value = c(10, 20, 30, 40, 50)
)
tapply(df$value, df$group, mean) # A: 15, B: 40
# replicate: repeat an expression n times
replicate(3, mean(rnorm(10))) # 3 random means作用域与环境
R 使用词法作用域:函数在定义它们的环境中查找自由变量(而不是调用它们的地方)。这使得闭包成为可能——捕获其封闭环境的函数。<<- 运算符在父环境中赋值(超级赋值),这是闭包维护状态的方式(类似计数器示例)。每次函数调用创建一个新环境。搜索路径(search())决定 R 在哪里查找对象——globalenv 是你的工作区,后跟附加的包。
# lexical scoping: functions look up variables in defining environment
y <- 10
f <- function(x) {
x + y # y found in global env
}
f(5) # 15
# local variables shadow globals
g <- function(x) {
y <- 100 # local y
x + y
}
g(5) # 105
y # 10 (global unchanged)
# <<- assigns to parent environment (super-assignment)
counter <- function() {
count <- 0
function() {
count <<- count + 1 # modifies count in enclosing scope
count
}
}
c1 <- counter()
c1() # 1
c1() # 2
# search path for variables
search() # shows environments: globalenv, package namespaces
ls() # list objects in current environment
ls(envir = .GlobalEnv) # explicitly global
exists("y") # TRUE if variable exists
# new environment (isolated scope)
e <- new.env()
e$x <- 42
e$x # 42 (separate from global x)数据操作(dplyr 与 tidyr)
dplyr:筛选、选择与排列
dplyr(tidyverse 的一部分)提供镜像 SQL 操作的直观数据操作动词。filter 按条件选择行;select 选择列;arrange 排序。%in% 运算符测试成员资格。辅助函数如 starts_with、ends_with、contains 使列选择灵活。rename() 在不复制的情况下更改列名。这些动词与管道(%>%)组合形成可读的数据管道。dplyr 对大数据比基础 R 快得多,因为它内部使用 C++。
library(dplyr)
df <- data.frame(
name = c("Alice", "Bob", "Carol", "Dan"),
age = c(30, 25, 28, 35),
dept = c("Eng", "Sales", "Eng", "Sales"),
salary = c(80000, 50000, 75000, 90000)
)
# filter rows (like WHERE in SQL)
filter(df, age > 26)
filter(df, dept == "Eng" & salary > 70000)
filter(df, dept %in% c("Eng", "Sales"))
filter(df, age > 26 | salary > 85000)
# select columns (like SELECT in SQL)
select(df, name, age)
select(df, name:dept) # range of columns
select(df, -salary) # exclude column
select(df, starts_with("s")) # columns starting with 's'
select(df, ends_with("e")) # columns ending with 'e'
select(df, contains("am")) # columns containing 'am'
select(df, everything()) # all columns (useful for reordering)
# arrange (sort, like ORDER BY)
arrange(df, age) # ascending
arrange(df, desc(age)) # descending
arrange(df, dept, desc(salary)) # sort by dept, then salary desc
# rename columns
rename(df, years = age, department = dept)
# distinct rows
distinct(df, dept) # unique departments
distinct(df, dept, .keep_all = TRUE) # keep all columnsdplyr:Mutate、Summarize 与 Group By
mutate 添加或修改列(向量化)。summarize 将每组减少为单个汇总行。功能来自 group_by + summarize——R 中 SQL GROUP BY 的等价物。n() 计数行;across() 将函数应用于多列(dplyr 1.0 新增)。窗口函数(rank、cumsum、lag、lead)在组内操作,实现'部门内排名'等计算。管道 %>% 从左到右链接操作,使复杂管道可读。
library(dplyr)
# mutate: add/modify columns
df %>%
mutate(
bonus = salary * 0.1,
total = salary + bonus,
category = ifelse(age > 30, "senior", "junior")
)
# transmute: like mutate but only keeps new columns
transmute(df, name, annual = salary, monthly = salary / 12)
# summarize (reduces to single row per group)
summarize(df,
avg_salary = mean(salary),
max_age = max(age),
n = n()
)
# group_by + summarize (like GROUP BY in SQL)
df %>%
group_by(dept) %>%
summarize(
count = n(),
avg_salary = mean(salary),
avg_age = mean(age)
) %>%
arrange(desc(avg_salary))
# multiple summaries
df %>%
group_by(dept) %>%
summarize(across(everything(), list(mean, sd)))
# count and tally
count(df, dept) # count per dept
df %>% group_by(dept) %>% tally()
# window functions (within groups)
df %>%
group_by(dept) %>%
mutate(
rank = rank(desc(salary)),
cumsum = cumsum(salary)
) %>%
arrange(dept, rank)管道运算符(%>%)
管道(%>% 来自 magrittr/dplyr)将左侧作为第一个参数传递给右侧,将嵌套函数调用转换为可读的从左到右管道。这是 tidyverse 风格的决定性特性。点(.)在需要时代表管道传递的数据。%$% 暴露列名;%<>% 回赋;%T>% 在副作用(如绘图)后继续管道。R 4.1+ 有原生管道 |>,但灵活性较低(没有点占位符)。管道使数据整理代码可读性大幅提升。
library(magrittr) # or library(dplyr)
# without pipe: nested, hard to read
result <- arrange(
filter(
select(df, name, age, salary),
age > 25
),
desc(salary)
)
# with pipe: linear, readable
result <- df %>%
select(name, age, salary) %>%
filter(age > 25) %>%
arrange(desc(salary))
# the dot (.) refers to the piped data
df %>% plot(.$age, .$salary) # . = df
# %$% exposes column names (magrittr)
df %$% cor(age, salary) # correlation
# %<>% assigns result back (compound assignment)
df %<>% filter(age > 20)
# T pipe: returns first argument (for side effects)
rnorm(100) %T>%
hist() %>% # plot histogram (returns input)
mean() # then compute mean
# native pipe (R 4.1+): |>
df |>
subset(age > 25) |>
colMeans()连接数据框
dplyr 的连接函数镜像 SQL 连接:inner_join(交集)、left_join(所有左行)、right_join、full_join(并集)。semi_join 筛选到有匹配的行(不添加列);anti_join 找到没有匹配的行——两者对数据验证很有用。by 参数指定连接键;当列名不同时使用命名向量(c('id' = 'emp_id'))。连接比基础 R 的 merge() 快得多。始终在连接前后检查行数以捕获意外重复。
library(dplyr)
employees <- data.frame(
id = c(1, 2, 3, 4),
name = c("Alice", "Bob", "Carol", "Dan")
)
salaries <- data.frame(
id = c(1, 2, 3, 5),
salary = c(80000, 50000, 75000, 60000)
)
# inner join: only matching rows
inner_join(employees, salaries, by = "id")
# id name salary
# 1 Alice 80000
# 2 Bob 50000
# 3 Carol 75000
# left join: all rows from left, NA for non-matches
left_join(employees, salaries, by = "id")
# Dan has NA salary
# right join: all rows from right
right_join(employees, salaries, by = "id")
# id 5 has NA name
# full join: all rows from both
full_join(employees, salaries, by = "id")
# different column names
left_join(employees, salaries, by = c("id" = "id"))
# semi join: rows in left that have a match (no right columns)
semi_join(employees, salaries, by = "id")
# anti join: rows in left with NO match
anti_join(employees, salaries, by = "id")
# Dan (id 4 has no salary)
# multiple keys
left_join(df1, df2, by = c("dept" = "department", "year" = "yr"))tidyr:重塑数据
tidyr(tidyverse)处理数据重塑。pivot_longer/wider 替代传统的 gather/spread——它们更直观、更灵活。整洁数据每次观察一行,每个变量一列;pivot_longer 将宽格式数据转换为此格式(ggplot2 需要)。separate/unite 拆分和合并列。separate_rows 将分隔值展开为多行。drop_na/replace_na/fill 干净地处理缺失数据。这些动词通过管道与 dplyr 组合形成强大的数据管道。
library(tidyr)
# wide to long
wide_df <- data.frame(
id = 1:2,
q1_sales = c(100, 200),
q2_sales = c(150, 250),
q3_sales = c(120, 220)
)
long_df <- wide_df %>%
pivot_longer(
cols = starts_with("q"),
names_to = "quarter",
values_to = "sales"
)
# long to wide
long_df %>%
pivot_wider(
names_from = quarter,
values_from = sales
)
# separate one column into multiple
df <- data.frame(id = 1, name = "Alice_Smith")
df %>% separate(name, into = c("first", "last"), sep = "_")
# unite multiple columns into one
df %>% unite("full_name", first, last, sep = " ")
# separate_rows: split delimited values into rows
df <- data.frame(id = 1:2, tags = c("a,b,c", "x,y"))
df %>% separate_rows(tags, sep = ",")
# drop NA values
df %>% drop_na() # drop rows with any NA
df %>% drop_na(salary) # drop rows where salary is NA
# replace NA with a value
df %>% replace_na(list(salary = 0, name = "Unknown"))
# fill missing values (carry forward)
df %>% fill(salary, .direction = "down")统计与建模
描述性统计
summary() 是获取任何数据概览的最快方式——它显示最小值、四分位数、中位数、平均值和最大值。sd() 和 var() 计算样本(n-1)统计量。cor() 衡量线性关联(Pearson)或等级关联(Spearman)。对包含 NA 的真实世界数据始终使用 na.rm=TRUE。对于数据框,summary(df) 给出每列的统计信息。psych 和 Hmisc 包提供扩展的描述性统计。
data <- c(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
# central tendency
mean(data) # 5.5
median(data) # 5.5
# R has no built-in mode; use:
as.numeric(names(sort(table(data), decreasing = TRUE)[1]))
# spread
sd(data) # 3.028 (sample standard deviation)
var(data) # 9.167 (sample variance)
IQR(data) # 5 (interquartile range)
range(data) # 1 10
diff(range(data)) # 9
# quantiles
quantile(data) # 0% 25% 50% 75% 100%
quantile(data, c(0.1, 0.9)) # 10th and 90th percentiles
# summary (all at once)
summary(data)
# Min. 1st Qu. Median Mean 3rd Qu. Max.
# 1.00 3.25 5.50 5.50 7.75 10.00
# correlation and covariance
x <- c(1, 2, 3, 4, 5)
y <- c(2, 4, 5, 4, 5)
cor(x, y) # 0.77 (Pearson correlation)
cor(x, y, method = "spearman") # rank correlation
cov(x, y) # covariance
# handling NA
data_na <- c(1, 2, NA, 4, 5)
mean(data_na, na.rm = TRUE) # 3
sd(data_na, na.rm = TRUE) # 1.826概率分布
R 有每个常见分布,命名约定一致:d/p/q/r 前缀 + 分布名称。d 给出密度(连续)或概率质量(离散);p 给出累积概率;q 给出分位数(百分位数);r 生成随机样本。常见分布:norm(正态)、binom(二项)、pois(泊松)、unif(均匀)、exp(指数)、t、chisq、f。随机运算前始终调用 set.seed() 以获得可重现的结果。sample() 从向量中随机抽取元素。
# R uses d/p/q/r prefix for distributions:
# d = density (PDF/PMF)
# p = cumulative (CDF: P(X <= x))
# q = quantile (inverse CDF)
# r = random samples
# Normal distribution
dnorm(0) # 0.399 (density at 0)
pnorm(1.96) # 0.975 (P(Z <= 1.96))
qnorm(0.975) # 1.96 (97.5th percentile)
rnorm(5, mean = 100, sd = 15) # 5 random samples
# Binomial distribution
dbinom(3, size = 10, prob = 0.5) # P(X=3)
pbinom(3, size = 10, prob = 0.5) # P(X<=3)
rbinom(10, size = 1, prob = 0.5) # 10 coin flips
# Poisson distribution
dpois(2, lambda = 3) # P(X=2) with rate 3
rpois(100, lambda = 5) # 100 random samples
# Uniform distribution
runif(5, min = 0, max = 1) # 5 uniform random numbers
# Exponential
rexp(10, rate = 0.5) # 10 exponential samples
# set random seed for reproducibility
set.seed(42)
rnorm(3) # same results every time with same seed
# sample from a vector
sample(1:10, 5) # 5 random numbers without replacement
sample(1:10, 5, replace = TRUE) # with replacement
sample(c("A", "B", "C"), 2) # sample from categories假设检验
R 使假设检验变得简单。t.test 比较均值(单样本、双样本或配对)。p 值 < 0.05 通常表示统计显著性。var.equal=TRUE 假设方差相等(Student t);默认是 Welch(更鲁棒)。chisq.test 检查分类变量的独立性。wilcox.test 是非参数替代方案(无正态性假设)。aov 执行 ANOVA 比较 3+ 组。所有测试函数返回一个列表,包含 $p.value、$statistic、$conf.int,你可以以编程方式提取。
# one-sample t-test (is mean different from hypothesized?)
data <- c(5.1, 4.9, 5.0, 5.2, 4.8, 5.1, 5.0)
t.test(data, mu = 5.0)
# t = 0.527, df = 6, p-value = 0.616
# (fail to reject H0: mean = 5)
# two-sample t-test (are two means different?)
group1 <- c(5.1, 4.9, 5.0, 5.2)
group2 <- c(4.5, 4.7, 4.6, 4.4)
t.test(group1, group2)
t.test(group1, group2, var.equal = TRUE) # assume equal variance
# paired t-test (before/after)
before <- c(70, 80, 65, 90, 75)
after <- c(75, 85, 70, 92, 80)
t.test(before, after, paired = TRUE)
# chi-squared test (independence of categorical vars)
tbl <- table(c("A","A","B","B"), c("X","Y","X","Y"))
chisq.test(tbl)
# Wilcoxon (non-parametric alternative to t-test)
wilcox.test(group1, group2)
# ANOVA (compare means across multiple groups)
df <- data.frame(
value = c(5, 6, 7, 8, 9, 10),
group = factor(c("A","A","B","B","C","C"))
)
result <- aov(value ~ group, data = df)
summary(result) # F-test p-value
# extract p-value from any test
test_result <- t.test(data, mu = 5)
test_result$p.value # 0.616线性回归(lm)
lm() 使用公式语法拟合线性模型:y ~ x(简单)、y ~ x1 + x2(多元)、y ~ .(所有列)、y ~ x1*x2(带交互)、y ~ I(x^2)(变换)。summary() 显示系数、标准误、t 值、p 值、R² 和 F 检验。因子自动转换为虚拟变量。公式迷你语言很强大:- 移除项,: 是交互,* 是主效应 + 交互。始终检查诊断图(残差、Q-Q 图)以验证模型假设。
# simple linear regression: y ~ x
x <- c(1, 2, 3, 4, 5)
y <- c(2, 4, 5, 4, 5)
model <- lm(y ~ x)
# view results
print(model) # coefficients
summary(model) # full summary with R², t-tests, F-test
coef(model) # (Intercept) 2.2, x 0.6
fitted(model) # predicted values
residuals(model) # y - predicted
confint(model) # 95% confidence intervals
# make predictions
predict(model, newdata = data.frame(x = c(6, 7)))
# multiple regression
df <- data.frame(
y = c(10, 12, 15, 18, 20),
x1 = c(1, 2, 3, 4, 5),
x2 = c(5, 4, 3, 2, 1)
)
model2 <- lm(y ~ x1 + x2, data = df)
model3 <- lm(y ~ ., data = df) # all predictors
model4 <- lm(y ~ x1 + I(x1^2), data = df) # polynomial
# interactions
model5 <- lm(y ~ x1 * x2, data = df) # x1 + x2 + x1:x2
# diagnostic plots
par(mfrow = c(2, 2))
plot(model) # 4 diagnostic plots
# with factors (automatic dummy variables)
model6 <- lm(y ~ x1 + factor(group), data = df)GLM 与其他模型
glm() 通过 family 参数将 lm() 推广到非正态结果:binomial(二分类逻辑回归)、poisson(计数数据)、gaussian(与 lm 相同)。带 type='response' 的 predict() 给出概率(而非对数几率)用于逻辑模型。step() 执行自动变量选择(基于 AIC)。anova(model1, model2) 检验较大模型是否显著更好。对于高级方法,R 有适用于一切的包:lme4(混合模型)、survival(Kaplan-Meier、Cox)、randomForest、caret(ML 管道)、glmnet(正则化)。
# logistic regression (binary outcome)
df <- data.frame(
admit = c(0, 1, 0, 1, 1, 0),
gre = c(380, 660, 800, 640, 520, 760),
gpa = c(3.6, 3.7, 3.8, 3.9, 3.5, 3.6)
)
logit <- glm(admit ~ gre + gpa, data = df, family = binomial)
summary(logit)
# predict probabilities (type = "response")
predict(logit, newdata = df, type = "response")
# Poisson regression (count data)
counts <- c(2, 3, 1, 4, 2, 5)
pois <- glm(counts ~ x, family = poisson)
# stepwise model selection
full_model <- lm(y ~ x1 + x2 + x3, data = df)
step_model <- step(full_model) # AIC-based selection
# anova to compare models
model1 <- lm(y ~ x1, data = df)
model2 <- lm(y ~ x1 + x2, data = df)
anova(model1, model2) # is x2 significant?
# other models
# nls() - nonlinear least squares
# glm.nb() - negative binomial (MASS package)
# lme()/lmer() - mixed effects (nlme/lme4)
# rpart()/randomForest() - tree-based methods
# coxph() - survival analysis (survival package)
# cross-validation
library(boot)
cv_result <- cv.glm(df, logit, K = 10) # 10-fold CV
cv_result$delta # cross-validation error绘图(基础 R 与 ggplot2)
基础 R:plot、hist 与 boxplot
基础 R 图形快速且足以进行探索性分析。plot() 是泛型的——它根据输入类型分派(两个向量为散点图,公式为箱线图)。type 控制点/线样式;pch 设置点符号;lty 设置线型。par(mfrow=c(r,c)) 在网格中排列多个图。带 freq=FALSE 的 hist() 显示密度(因此你可以叠加密度曲线)。对于出版质量的图形,改用 ggplot2。基础 R 图形是命令式的——你逐步构建它们。
# scatter plot
x <- 1:10
y <- x^2
plot(x, y, type = "p", # p=points, l=lines, b=both, o=overplotted
main = "Quadratic", # title
xlab = "x", ylab = "y", # axis labels
col = "blue", pch = 16, # color and point character
xlim = c(0, 10), ylim = c(0, 100))
# add lines and points to existing plot
lines(x, x*10, col = "red", lty = 2) # dashed line
points(x, y + 5, col = "green")
legend("topleft", legend = c("x^2", "10x"),
col = c("blue", "red"), lty = c(NA, 2), pch = c(16, NA))
# histogram
data <- rnorm(1000)
hist(data, breaks = 30, col = "lightblue",
main = "Normal Distribution", xlab = "Value", freq = FALSE)
lines(density(data), col = "red", lwd = 2) # add density curve
# boxplot (compare groups)
boxplot(count ~ spray, data = InsectSprays,
col = "lightgreen", main = "Insect Count by Spray")
# barplot
counts <- table(mtcars$cyl)
barplot(counts, col = c("red","green","blue"),
main = "Cars by Cylinders", xlab = "Cylinders")
# multiple plots in one window
par(mfrow = c(2, 2)) # 2x2 grid
plot(x, y); hist(data); boxplot(data); plot(density(data))
par(mfrow = c(1, 1)) # resetggplot2:图形语法
ggplot2(tidyverse)实现图形语法:图形由层(数据、美学、几何、比例、分面、主题)用 + 组合而成。aes() 将数据列映射到视觉属性(x、y、color、size)。每个 geom_* 添加一层:geom_point(散点)、geom_line、geom_bar、geom_histogram、geom_boxplot、geom_smooth(趋势线)。这种分层方法意味着你可以增量构建复杂图形。与基础 R 不同,ggplot2 是声明式的——你描述想要什么,而不是如何绘制它。
library(ggplot2)
# basic structure: data + aesthetic mapping + geometry
ggplot(data = mtcars, aes(x = wt, y = mpg)) +
geom_point()
# add aesthetics (color, size, shape mapped to data)
ggplot(mtcars, aes(x = wt, y = mpg, color = cyl, size = hp)) +
geom_point()
# add layers
ggplot(mtcars, aes(x = wt, y = mpg)) +
geom_point(color = "blue", size = 3) +
geom_smooth(method = "lm", se = TRUE) + # regression line
labs(title = "MPG vs Weight",
x = "Weight (1000 lbs)", y = "MPG",
color = "Cylinders") +
theme_minimal()
# the + operator adds layers (like %>% but for ggplot)
p <- ggplot(mtcars, aes(x = wt, y = mpg)) +
geom_point()
p + geom_smooth() # add to saved plot
p + facet_wrap(~ cyl) # facet by cylinders
# key components:
# data - the data frame
# aes() - aesthetic mappings (x, y, color, size, shape)
# geom_*() - geometry (point, line, bar, histogram, etc.)
# scale_*() - control axes, colors, legends
# facet_*() - small multiples (subplots)
# theme_*() - overall appearance
# labs() - titles and labelsggplot2:几何对象与美学
ggplot2 有适用于每种图表类型的数十个 geom。geom_bar/geom_col 用于条形图,geom_histogram/geom_density 用于分布,geom_boxplot 用于比较,geom_line/geom_area 用于时间序列。stat_summary 计算并绘制汇总(均值、误差棒)。alpha 控制透明度(0-1)——对重叠点至关重要。coord_flip 旋转图形。geom_jitter 添加噪声以防止过度绘制。每个 geom 有它理解的特定美学(例如,geom_point 需要 x 和 y;geom_bar 只需要 x)。
library(ggplot2)
# bar chart (counts)
ggplot(mpg, aes(class)) +
geom_bar(fill = "steelblue") +
coord_flip() # horizontal bars
# histogram
ggplot(mpg, aes(hwy)) +
geom_histogram(binwidth = 2, fill = "orange", color = "black")
# density plot
ggplot(mpg, aes(hwy, fill = class)) +
geom_density(alpha = 0.5) # semi-transparent
# boxplot by group
ggplot(mpg, aes(class, hwy)) +
geom_boxplot() +
geom_jitter(width = 0.2, alpha = 0.5) # overlay points
# line plot (time series)
ggplot(economics, aes(date, unemploy)) +
geom_line(color = "red") +
geom_area(fill = "pink", alpha = 0.3)
# scatter with smoothing
ggplot(mpg, aes(displ, hwy, color = drv)) +
geom_point(size = 2) +
geom_smooth(method = "loess", se = FALSE)
# error bars
ggplot(df, aes(group, value)) +
stat_summary(fun = mean, geom = "bar") +
stat_summary(fun.data = mean_se, geom = "errorbar")
# text labels
ggplot(mtcars, aes(wt, mpg, label = rownames(mtcars))) +
geom_text(size = 3)ggplot2:分面、比例与主题
分面创建小倍数——每个类别一个子图——比较组的最佳方式。facet_wrap(~var) 创建 1D 带状;facet_grid(row~col) 创建 2D 网格。比例控制数据如何映射到视觉属性:scale_x_log10 用于对数轴,scale_color_brewer 用于色盲友好调色板,scale_fill_manual 用于自定义颜色。主题控制非数据元素(字体、网格线、图例)。theme_minimal/bw/classic 是预设;theme() 自定义单个元素。ggsave 以可控大小和 DPI 导出到 PNG/PDF/SVG。
library(ggplot2)
# facets: small multiples (subplots by a variable)
ggplot(mpg, aes(displ, hwy)) +
geom_point() +
facet_wrap(~ class) # one panel per class
# facet_grid: 2D grid of panels
ggplot(mpg, aes(displ, hwy)) +
geom_point() +
facet_grid(drv ~ cyl) # rows ~ cols
# scales: control axes and colors
ggplot(mpg, aes(displ, hwy, color = class)) +
geom_point() +
scale_x_log10() + # log scale
scale_color_brewer(palette = "Set1") + # color palette
scale_size_continuous(range = c(2, 8))
# manual colors
ggplot(mpg, aes(class, fill = drv)) +
geom_bar() +
scale_fill_manual(values = c("red", "green", "blue"))
# themes: overall appearance
ggplot(mpg, aes(displ, hwy)) +
geom_point() +
theme_minimal() + # or theme_classic, theme_bw
theme(
text = element_text(size = 14, family = "serif"),
plot.title = element_text(face = "bold", hjust = 0.5),
axis.text.x = element_text(angle = 45, hjust = 1),
legend.position = "bottom",
panel.grid.major = element_line(color = "gray90")
)
# save plot
ggsave("plot.png", width = 8, height = 6, dpi = 300)
ggsave("plot.pdf", device = "pdf")保存与导出数据
RDS 最适合保存单个 R 对象(保留类型、快速、紧凑)。RData 保存多个对象。CSV 最可移植(Excel、Python 等可读)但丢失类型信息——始终设置 stringsAsFactors=FALSE。readr 包(tidyverse)提供更快、更一致的 CSV 输入输出,返回 tibble(改进的数据框)。readxl/writexl 处理 Excel。对于大数据集,考虑 data.table::fread(非常快)或 parquet(arrow 包)用于列式存储。写入 CSV 时始终使用 row.names=FALSE。
# save single object to RDS (binary, preserves type)
saveRDS(my_data, "data.rds")
restored <- readRDS("data.rds")
# save multiple objects to RData
save(df1, df2, model, file = "workspace.RData")
load("workspace.RData") # loads all objects into workspace
# CSV (most portable)
write.csv(df, "data.csv", row.names = FALSE)
df <- read.csv("data.csv")
df <- read.csv("data.csv", stringsAsFactors = FALSE)
df <- read.csv("data.csv", na.strings = c("", "NA", "N/A"))
# readr (tidyverse): faster, smarter CSV I/O
library(readr)
write_csv(df, "data.csv")
df <- read_csv("data.csv") # returns a tibble
df <- read_csv("data.csv", col_types = cols(
age = col_integer(),
name = col_character()
))
# Excel (requires readxl/writexl packages)
library(readxl)
df <- read_excel("data.xlsx", sheet = 1, range = "A1:D100")
library(writexl)
write_xlsx(df, "output.xlsx")
# RDS vs RData vs CSV
# RDS: one object, binary, fast, preserves types
# RData: multiple objects, binary, fast
# CSV: text, portable to other tools, loses type info
# save and load workspace
save.image("project.RData") # save everything
load("project.RData") # restore everything
# serialize to JSON (jsonlite package)
library(jsonlite)
write_json(df, "data.json")
df <- fromJSON("data.json")OOP 与函数式编程
S3 类与方法(简单 OOP)
S3 是 R 最常见的 OOP 系统——轻量级且非正式。'类'只是带 class 属性的列表;方法是名为 generic.classname 的函数。泛型内部的 UseMethod() 根据对象的类分派到适当的方法。大多数 R 对象(data.frame、lm、ggplot)都是 S3。print()、summary()、plot() 是你可以扩展的泛型。S3 是非正式的(无验证),这使其灵活但容易出错。使用 methods(generic) 查看所有方法,methods(class='x') 查看类的方法。
# S3 is R's simplest OOP system: a class is just an attribute
# create an object: list + class attribute
account <- list(owner = "Alice", balance = 1000)
class(account) <- "BankAccount"
# generic function dispatches by class
print.BankAccount <- function(x, ...) {
cat("Account owner:", x$owner, "\n")
cat("Balance: $", x$balance, "\n", sep = "")
}
print(account)
# Account owner: Alice
# Balance: $1000
# define a custom method for an existing generic
deposit <- function(obj, amount) {
UseMethod("deposit") # dispatch based on class of obj
}
deposit.BankAccount <- function(obj, amount) {
obj$balance <- obj$balance + amount
obj
}
account <- deposit(account, 500)
account$balance # 1500
# check available methods for a generic
methods(print) # all print methods
methods(class = "lm") # methods for lm objects
# default method (fallback)
deposit.default <- function(obj, amount) {
stop("No deposit method for this class")
}S4 类(正式 OOP)
S4 是 R 的正式 OOP 系统:类有定义的槽(字段),带类型、验证和继承。用 setClass() 定义;用 new() 实例化。用 @ 访问槽(不是 $)。setMethod() 为泛型定义方法。setValidity() 强制约束。S4 被 Bioconductor 和需要严格契约的包使用(例如 Matrix、sp)。S4 比 S3 更鲁棒但更冗长。大多数日常 R 使用 S3;当你需要类型安全和正式继承时使用 S4。
# S4 is formal: classes are defined with setClass and validated
setClass("Person",
slots = list(
name = "character",
age = "numeric"
),
prototype = list(name = NA_character_, age = NA_real_)
)
# constructor: new(ClassName, ...)
alice <- new("Person", name = "Alice", age = 30)
# access slots with @ (not $)
alice@name # "Alice"
slot(alice, "age") # 30
# define a method
setMethod("show", "Person", function(object) {
cat("Person:", object@name, "(", object@age, "y)\n")
})
show(alice) # Person: Alice ( 30 y)
# validity check
setValidity("Person", function(object) {
if (object@age < 0) return("age must be non-negative")
TRUE
})
# new("Person", name="Bob", age=-5) # error
# inheritance
setClass("Student", contains = "Person",
slots = list(gpa = "numeric"))
stu <- new("Student", name="Bob", age=20, gpa=3.8)闭包与函数工厂
闭包是一个保留对其定义环境中变量访问权限的函数——R 创建有状态函数和封装私有数据的主要方式。<<- 运算符在封闭(父)环境中赋值,而不是本地环境。函数工厂(返回函数的函数)对于创建专用函数很强大。常见用途:计数器、记忆化(缓存结果)和模块模式(返回共享私有状态的函数列表)。这是 R 中最接近带私有字段的类的等价物。
# a closure is a function that remembers its enclosing environment
# function factory: creates functions with private state
make_counter <- function() {
count <- 0
function() {
count <<- count + 1 # <<- modifies in enclosing env
count
}
}
c1 <- make_counter()
c1() # 1
c1() # 2
c1() # 3
c2 <- make_counter()
c2() # 1 (independent counter)
# memoization (cache expensive results)
memoize <- function(f) {
cache <- new.env(parent = emptyenv())
function(x) {
key <- as.character(x)
if (!exists(key, envir = cache)) {
assign(key, f(x), envir = cache)
}
get(key, envir = cache)
}
}
slow_sqrt <- function(x) { Sys.sleep(0.1); sqrt(x) }
fast_sqrt <- memoize(slow_sqrt)
fast_sqrt(16) # slow first time
fast_sqrt(16) # instant second time
# capturing state in a list (module pattern)
bank_account <- function(initial) {
balance <- initial
list(
deposit = function(amt) { balance <<- balance + amt },
withdraw = function(amt) { balance <<- balance - amt },
get_balance = function() balance
)
}
acc <- bank_account(100)
acc$deposit(50)
acc$get_balance() # 150函数式编程:Map/Reduce/Filter
R 有内置的函数式编程原语:Map(对每个元素应用函数)、Reduce(折叠/累积)、Filter(保留匹配元素)、Find/Position(搜索)、Negate(反转谓词)。这些返回列表或向量并避免显式循环。purrr 包(tidyverse)提供更一致的 API:map_dbl/map_chr 返回有类型向量,keep/discard 筛选,reduce 累积。~ .x 公式语法简洁地创建匿名函数。函数式编程使代码更具声明性且更易推理,特别是对于数据转换管道。
# Map: apply a function to each element (returns list)
Map(function(x) x^2, 1:5)
# [[1]] 1 [[2]] 4 [[3]] 9 [[4]] 16 [[5]] 25
# Reduce: combine elements with a binary function
Reduce("+", 1:5) # 15 (1+2+3+4+5)
Reduce("*", 1:5) # 120 (factorial 5!)
Reduce(c, list(1:2, 3:4, 5:6)) # 1 2 3 4 5 6
# Filter: keep elements satisfying a predicate
Filter(function(x) x > 2, 1:5) # 3 4 5
Filter(is.numeric, list(1, "a", 2, "b")) # 1 2
# Negate: invert a predicate
Negate(is.null)
is_not_null <- Negate(is.null)
# Position: find first index satisfying a predicate
Position(function(x) x > 3, 1:10) # 4
# Find: first element satisfying a predicate
Find(function(x) x > 3, 1:10) # 4
# purrr (tidyverse): cleaner functional programming
library(purrr)
map_dbl(1:5, ~ .x^2) # 1 4 9 16 25 (vector output)
map_chr(c("a","b"), toupper) # "A" "B"
keep(1:5, ~ .x > 2) # 3 4 5
reduce(1:5, `+`) # 15
walk(1:3, print) # side effects only调试与错误处理
browser() 是 R 的交互式调试器——在代码中插入它以暂停并检查变量(n=下一步,c=继续,Q=退出)。debug(fn) 逐行执行函数。traceback() 显示崩溃后的调用栈。tryCatch() 是 R 的 try/catch:它通过处理函数捕获错误和警告,返回回退值。withCallingHandlers() 处理警告而不中断执行。设置 options(warn=2) 将警告转为错误(用于查找来源)。options(error=browser) 在任何未捕获错误时自动进入调试器。掌握这些工具对于诊断复杂 R 代码中的问题至关重要。
# browser(): pause execution and inspect
f <- function(x) {
browser() # pauses here; use n, c, Q, ls, print
y <- x * 2
if (y > 10) stop("y too big")
y
}
# f(6) # enters browser at the browser() line
# debug() / undebug(): debug a function
debug(lm)
# lm(y ~ x) # steps through lm line by line
undebug(lm)
# traceback(): see call stack after an error
# log("abc") # error
# traceback() # shows the call stack
# tryCatch(): handle errors gracefully
safe_log <- function(x) {
tryCatch(
log(x),
error = function(e) {
message("Error: ", conditionMessage(e))
NA_real_
},
warning = function(w) {
message("Warning: ", conditionMessage(w))
log(x) # retry or handle
}
)
}
safe_log("abc") # NA with message
safe_log(-1) # NA with warning (NaN)
# withCallingHandlers(): handle warnings without stopping
withCallingHandlers(
{ warn("oops"); 42 },
warning = function(w) { message("caught: ", w$message); invokeRestart("muffleWarning") }
)
# options for debugging
options(warn = 2) # warnings become errors (for debugging)
options(error = browser) # enter browser on error
options(warn = 0) # reset时间序列与预测
创建时间序列对象(ts)
ts() 是基础 R 的时间序列类——带时间属性(start、frequency)的向量。frequency 编码周期:12 为月度,4 为季度,52 为周度。window() 按时间范围子集化。diff() 计算差分(用于使序列平稳)。lag() 移动值。aggregate() 转换为较低频率(例如,月度到季度)。ts 适用于规则的固定频率数据。对于不规则时间戳(例如,有间隔的股票价格),改用 xts/zoo 包。
# ts(): create a regular time series
# frequency: 12=monthly, 4=quarterly, 52=weekly, 365.25=daily
monthly_sales <- ts(c(30, 35, 40, 38, 42, 45, 50, 48, 52, 55, 60, 58),
start = c(2020, 1), frequency = 12)
print(monthly_sales)
# Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec
# 2020 30 35 40 38 42 45 50 48 52 55 60 58
# attributes
start(monthly_sales) # 2020 1
end(monthly_sales) # 2020 12
frequency(monthly_sales) # 12
time(monthly_sales) # time points
# window(): subset a time range
q1 <- window(monthly_sales, start = c(2020, 3), end = c(2020, 5))
# multiple series (multivariate)
ts1 <- ts(rnorm(12), start = 2020, frequency = 12)
ts2 <- ts(rnorm(12), start = 2020, frequency = 12)
mts <- cbind(ts1, ts2) # multivariate ts
# lag and diff
lag(monthly_sales, k = 1) # shift by 1 period
diff(monthly_sales) # first differences (month-over-month change)
diff(monthly_sales, lag = 12) # year-over-year change
# aggregate to lower frequency
aggregate(monthly_sales, nfrequency = 4, FUN = mean) # monthly -> quarterly分解与平滑
时间序列分解将序列分离为趋势、季节和残差分量。decompose() 使用经典移动平均分解(加法或乘法)。stl() 使用 LOESS 平滑,对异常值更鲁棒且能处理变化的季节性。HoltWinters() 拟合指数平滑(水平 + 趋势 + 季节)。forecast 包(现在 tidyverse 中为 fable)提供 forecast() 用于带置信区间的预测。建模前始终绘制分解以理解结构。当季节振幅随趋势增长时,乘法分解是合适的。
# decompose(): split a series into trend/seasonal/random
decomp <- decompose(monthly_sales, type = "additive")
# type = "additive" (T+S+R) or "multiplicative" (T*S*R)
plot(decomp) # shows observed, trend, seasonal, random panels
decomp$trend # trend component
decomp$seasonal # seasonal component
decomp$random # remainder
# stl(): Seasonal-Trend-Loess decomposition (more flexible)
stl_fit <- stl(monthly_sales, s.window = "periodic")
plot(stl_fit)
stl_fit$time.series[, "trend"] # extract trend
# moving average smoothing
ma3 <- filter(monthly_sales, filter = rep(1/3, 3), sides = 2)
ma12 <- filter(monthly_sales, filter = rep(1/12, 12), sides = 1)
# HoltWinters(): exponential smoothing
hw <- HoltWinters(monthly_sales, seasonal = "additive")
plot(hw)
hw$fitted # fitted values
hw$coefficients # alpha, beta, gamma
# forecast with HoltWinters
library(forecast)
fc <- forecast(hw, h = 12) # 12-step ahead forecast
plot(fc) # with prediction intervals
autoplot(fc) # ggplot2 versionACF、PACF 与 ARIMA 建模
ACF/PACF 图诊断自相关结构:ACF 显示每个滞后处的总相关,PACF 显示直接(偏)相关。它们的模式暗示 ARIMA 阶数:ACF 截断暗示 MA,PACF 截断暗示 AR。adf.test() 检查平稳性(p<0.05 表示平稳)。auto.arima()(forecast 包)通过 AICc 自动选择最佳 ARIMA(p,d,q)(P,D,Q) 模型。d 是差分阶数(以实现平稳性);(P,D,Q) 是季节分量。checkresiduals() 验证模型拟合良好(残差应为白噪声)。ARIMA 是时间序列预测的主力。
# ACF (autocorrelation) and PACF (partial autocorrelation)
acf(monthly_sales, main = "ACF") # correlation with lagged self
pacf(monthly_sales, main = "PACF") # direct correlation at each lag
# interpret:
# ACF tails off slowly -> need differencing (non-stationary)
# ACF cuts off at lag p -> AR(p) process
# PACF cuts off at lag q -> MA(q) process
# stationarity test
library(tseries)
adf.test(monthly_sales) # Augmented Dickey-Fuller
# p < 0.05 -> stationary
# auto.arima(): automatically select ARIMA order
library(forecast)
fit <- auto.arima(monthly_sales)
summary(fit) # shows ARIMA(p,d,q)(P,D,Q)[m]
# ARIMA(0,1,1)(1,0,0)[12] example output
# forecast
fc <- forecast(fit, h = 12)
plot(fc)
accuracy(fc) # ME, RMSE, MAE, MAPE
# manual ARIMA
fit2 <- arima(monthly_sales, order = c(1, 1, 1),
seasonal = list(order = c(1, 0, 0), period = 12))
# residuals should look like white noise
checkresiduals(fit) # Ljung-Box test
tsdisplay(residuals(fit)) # ACF + PACF of residualsxts 与 zoo(不规则时间序列)
xts/zoo 扩展 ts 用于不规则时间序列(例如,缺少周末/假日的金融数据)。xts 对象按实际日期/时间索引,实现直观的子集化,如 prices['2024-01'] 获取整个一月。merge() 按日期对齐多个序列,用 NA 填充间隔(使用 fill=na.locf 向前填充)。rollmean/rollapply 计算滚动统计量。endpoints/period.apply 聚合到较粗的周期(周、月)。xts 是大多数 R 金融包(quantmod、TTR、PerformanceAnalytics)的基础。对于整洁时间序列,tsibble/fable 包提供现代替代方案。
library(xts)
library(zoo)
# create xts from a matrix and time index
dates <- as.Date(c("2024-01-01", "2024-01-03", "2024-01-10"))
prices <- xts(c(100, 102, 98), order.by = dates)
colnames(prices) <- "AAPL"
# subset by date range (very intuitive)
prices["2024-01-03"] # specific date
prices["2024-01-01/2024-01-05"] # date range
prices["2024-01"] # entire January
prices["/2024-01-05"] # up to a date
# lag and diff (returns xts)
lag(prices, k = 1) # previous day's price
diff(prices) # daily change
daily_returns <- diff(prices) / lag(prices, 1)
# rolling operations (zoo)
rollmean(prices, k = 3) # 3-day rolling mean
rollapply(prices, 3, sd) # 3-day rolling std dev
rollmax(prices, 3) # 3-day rolling max
# period.apply: aggregate by period
ep <- endpoints(prices, on = "weeks") # week endpoints
period.apply(prices, ep, mean) # weekly averages
# merge multiple series (aligns by date)
aapl <- xts(c(100, 102, 98), as.Date(c("2024-01-01","2024-01-02","2024-01-03")))
msft <- xts(c(200, 201), as.Date(c("2024-01-01","2024-01-03")))
merged <- merge(aapl, msft) # NA fills gaps
merged <- merge(aapl, msft, fill = na.locf) # carry forward
# to.ts <- as.ts(prices) # convert to base ts (loses dates)预测评估与可视化
始终在留出的测试集上评估预测,而不是样本内。accuracy(fit, test) 计算误差指标:MAE 和 RMSE(绝对尺度)、MAPE(百分比,无尺度但在零附近不稳定)、MASE(按朴素预测误差缩放;<1 表示比朴素好)。tsCV() 执行时间序列交叉验证(滚动原点)。比较多个模型(朴素基线、ETS、ARIMA)并选择误差最低的。autoplot() + autolayer() 用预测区间可视化预测。朴素预测(最后值)是关键基线——你的模型必须超越它才有用。永远不要对时间序列使用随机训练/测试分割(它们会泄漏未来信息);始终按时间顺序分割。
library(forecast)
library(ggplot2)
# split into train/test
train <- window(monthly_sales, end = c(2020, 9))
test <- window(monthly_sales, start = c(2020, 10))
# fit multiple models
fit_naive <- naive(train, h = 3) # naive: last value
fit_snaive <- snaive(train, h = 3) # seasonal naive
fit_ets <- ets(train) %>% forecast(h = 3) # exponential smoothing
fit_arima <- auto.arima(train) %>% forecast(h = 3)
# compare accuracy on test set
accuracy(fit_naive, test)
accuracy(fit_arima, test)
# metrics: ME, RMSE, MAE, MPE, MAPE, MASE
# time series cross-validation
ts_cv <- tsCV(train, forecastfunction = naive, h = 3)
sqrt(mean(ts_cv^2, na.rm = TRUE)) # CV RMSE
# visualize forecasts with ggplot
autoplot(monthly_sales, series = "Actual") +
autolayer(fit_arima, series = "ARIMA", PI = FALSE) +
autolayer(fit_ets, series = "ETS", PI = FALSE) +
labs(title = "Forecast Comparison", x = "Time", y = "Sales") +
theme_minimal()
# prediction intervals
fc <- forecast(auto.arima(train), h = 3, level = c(80, 95))
autoplot(fc) +
autolayer(test, series = "Actual")
# accuracy measures explained:
# MAE = Mean Absolute Error (same units as data)
# RMSE = Root Mean Squared Error (penalizes large errors)
# MAPE = Mean Absolute Percentage Error (scale-free, %)
# MASE = Mean Absolute Scaled Error (< 1 beats naive)dplyr 深入
核心动词:filter、select、mutate、summarize
dplyr 的五个核心动词涵盖大多数数据操作:filter(按条件筛选行)、select(按名称选择列)、mutate(添加/转换列)、summarize(折叠为汇总统计)和 arrange(排序)。管道 %>%(或原生 |>)从左到右链接操作,使代码可读。starts_with、ends_with、contains 和 everything() 等辅助函数使列选择简洁。summarize 前始终 group_by 以获得每组统计;n() 计算每组行数。
library(dplyr)
df <- tibble(
name = c("Alice","Bob","Carol","Dave"),
dept = c("Eng","Sales","Eng","Sales"),
salary = c(90000, 70000, 95000, 72000),
years = c(5, 3, 8, 4)
)
# filter rows
eng <- df %>% filter(dept == "Eng", salary > 85000)
# select columns (with helpers)
df %>% select(name, salary)
df %>% select(starts_with("s"))
df %>% select(-years)
df %>% select(name:dept) # range
df %>% select(dept, everything())# reorder
# mutate: add/modify columns
df %>% mutate(
bonus = salary * 0.1,
total = salary + bonus,
level = if_else(years >= 5, "Senior", "Junior")
)
# transmute: keep only new columns
df %>% transmute(name, annual_k = salary / 1000)
# summarize (with group_by)
df %>%
group_by(dept) %>%
summarize(
avg_salary = mean(salary),
max_years = max(years),
n = n()
)连接与集合运算
dplyr 连接镜像 SQL:inner、left、right、full 用于组合;semi 和 anti 用于按另一个表筛选。始终指定 by 以避免在意外列上静默匹配。对于不同的键名使用 by = c('left_col' = 'right_col')。集合运算(union、intersect、setdiff)要求相同的列集。bind_rows 堆叠(用 NA 填充缺失列);bind_cols 并排粘贴而不检查键——通常你需要的是连接。连接是微妙数据错误的最常见来源,因此在前后验证行数。
employees <- tibble(id = 1:4, name = c("Al","Bo","Cy","Di"))
salaries <- tibble(id = c(1,2,4,5), salary = c(50,60,80,90))
# mutating joins (combine columns)
inner_join(employees, salaries, by = "id") # only matching ids
left_join(employees, salaries, by = "id") # all from left
right_join(employees, salaries, by = "id") # all from right
full_join(employees, salaries, by = "id") # all rows
# filtering joins (filter rows, no new columns)
semi_join(employees, salaries, by = "id") # rows in left with match
anti_join(employees, salaries, by = "id") # rows in left WITHOUT match
# different column names
left_join(x, y, by = c("emp_id" = "id"))
# set operations (require identical columns)
union(a, b) # unique rows in either
union_all(a, b) # all rows (with dups)
intersect(a, b) # rows in both
setdiff(a, b) # rows in a but not b
# bind rows/columns
bind_rows(list(df1, df2)) # stack vertically
bind_cols(df1, df2) # side by side (no key check!)窗口函数与分组 Mutate
窗口函数在 group_by 定义的组内操作。row_number、min_rank 和 dense_rank 在处理平局时不同。lead/lag 访问相邻行——对时间序列和变化检测至关重要。累积函数(cumsum、cummax、cummean)计算运行汇总。slice_max/min/head/sample 提取每组的特定行。rowwise() + c_across() 启用跨列的逐行操作(比向量化慢,但有时必要)。这些函数使 dplyr 与 SQL 窗口函数一样强大。
df <- tibble(
dept = c("A","A","A","B","B","B"),
name = c("x","y","z","p","q","r"),
salary = c(50, 70, 60, 80, 90, 75)
)
# ranking within groups
df %>% group_by(dept) %>%
mutate(
rank = row_number(), # 1, 2, 3 (ties get distinct)
min_rank = min_rank(salary), # ties get same rank, gaps
dense_rank = dense_rank(salary),
pct_rank = percent_rank(salary)
)
# offsets (lead / lag)
df %>% group_by(dept) %>%
mutate(
prev = lag(salary),
next = lead(salary),
change = salary - lag(salary)
)
# cumulative aggregates
df %>% group_by(dept) %>%
mutate(
cum_total = cumsum(salary),
cum_max = cummax(salary),
running_avg = cummean(salary)
)
# top N per group
df %>% group_by(dept) %>%
slice_max(salary, n = 2) # or slice_min, slice_head, slice_sample
# row-wise operations
df %>% rowwise() %>%
mutate(total = sum(c_across(where(is.numeric))))across、where 与多列操作
across()(dplyr 1.0+)是将函数应用于多列的现代方式——替代旧的 _at、_if、_all 后缀。where(is.numeric) 按谓词选择列。.names 参数用 {col} 和 {fn} 模板控制输出名称。if_all/if_any 筛选所有/任何选定列满足条件的行。.data 代词启用编程式列访问(在函数和 Shiny 应用中很有用)。这些工具使 dplyr 对多列批量操作高度表达。
df <- tibble(id = 1:3, a = c(1,2,3), b = c(4,5,6), c = c("x","y","z"))
# apply function to multiple columns
df %>% mutate(across(a:b, ~ .x * 10))
df %>% mutate(across(where(is.numeric), log))
df %>% mutate(across(everything(), as.character))
# summarize multiple columns
df %>% summarize(across(where(is.numeric), list(
mean = ~mean(.x),
sd = ~sd(.x)
), .names = "{.col}_{.fn}"))
# rename multiple columns
df %>% rename_with(toupper, starts_with("a"))
# conditional transformation
df %>% mutate(across(where(is.numeric), ~ if_else(.x > 3, "high", "low")))
# use .data pronoun for programmatic access
col <- "a"
df %>% mutate(new = .data[[col]] * 2)
# pick columns by type in any verb
df %>% filter(if_all(a:b, ~ .x > 1)) # all must satisfy
df %>% filter(if_any(a:b, ~ .x > 4)) # any must satisfy数据库后端与 dbplyr
dbplyr 将 dplyr 动词翻译成 SQL,让你用与本地数据框相同的语法操作数据库表。这对大数据意义重大:繁重的计算在数据库中进行(通常是列式/并行的),只有结果通过 collect() 拉入 R。show_query() 显示生成的 SQL 用于调试。大多数 dplyr 动词直接翻译;窗口函数和某些字符串操作可能需要 SQL 特定函数。完成后始终 dbDisconnect。对于生产环境,使用连接池和参数化查询以防止 SQL 注入。
library(DBI)
library(dbplyr)
# connect to a database
con <- dbConnect(RSQLite::SQLite(), "my.db")
# or: con <- dbConnect(odbc::odbc(), "PostgreSQL")
# copy data to database
copy_to(con, mtcars, "mtcars_db", temporary = FALSE)
# reference a remote table
mtcars_remote <- tbl(con, "mtcars_db")
# dplyr verbs translate to SQL!
mtcars_remote %>%
group_by(cyl) %>%
summarize(avg_mpg = mean(mpg), n = n()) %>%
arrange(desc(avg_mpg))
# see the generated SQL
mtcars_remote %>%
filter(mpg > 20) %>%
select(mpg, cyl, hp) %>%
show_query()
# collect: pull results into local tibble
result <- mtcars_remote %>%
filter(cyl == 4) %>%
collect()
# write back to database
copy_to(con, result, "efficient_cars")
dbDisconnect(con)ggplot2 深入
图形语法与分层绘图
ggplot2 建立在图形语法之上:每个图形都是数据、美学映射(aes)、几何对象(geom_*)、统计(stat_*)、比例、坐标系和分面的组合。层用 + 添加。美学将数据列映射到视觉属性(x、y、color、size、shape);固定值放在 aes() 外。facet_wrap 和 facet_grid 创建小倍数——最强大的探索工具之一。大多数 geom 有默认 stat(例如,geom_bar 使用 stat_count),但你可以用 stat_summary 覆盖以进行自定义聚合。
library(ggplot2)
df <- data.frame(x = 1:10, y = (1:10)^2 + rnorm(10))
# basic structure: data + aesthetic + geom
ggplot(df, aes(x = x, y = y)) +
geom_point()
# multiple layers
ggplot(df, aes(x, y)) +
geom_point(color = "blue", size = 3) +
geom_smooth(method = "lm", se = TRUE)
# aesthetics map data to visual properties
ggplot(mpg, aes(displ, hwy, color = class)) +
geom_point() +
geom_smooth(method = "loess", se = FALSE)
# facets: small multiples
ggplot(mpg, aes(displ, hwy)) +
geom_point() +
facet_wrap(~ class, ncol = 4)
ggplot(mpg, aes(displ, hwy)) +
geom_point() +
facet_grid(drv ~ cyl) # rows ~ cols
# statistics (compute then plot)
ggplot(diamonds, aes(price)) +
geom_histogram(bins = 50) # stat_bin
ggplot(diamonds, aes(cut, price)) +
stat_summary(fun = "mean", geom = "bar")比例、主题与注释
比例控制从数据到视觉属性的映射——每个美学都有对应的比例(scale_x_*、scale_color_* 等)。scale_*_log10、scale_*_sqrt 变换轴;scale_*_continuous/discrete/manual 自定义值。Viridis 调色板是色盲安全的,在灰度下打印效果好。labs() 一次调用设置所有标签。theme() 控制非数据元素(字体、网格线、图例位置);从 theme_minimal 或 theme_classic 开始并微调。annotate() 添加独立于数据的固定元素(文本、矩形、线段)。
p <- ggplot(mpg, aes(displ, hwy, color = class)) +
geom_point(size = 3)
# scales control how data maps to visuals
p + scale_x_log10() +
scale_y_continuous(limits = c(0, 50), breaks = seq(0, 50, 10))
# manual colors
p + scale_color_manual(values = c("red","blue","green"))
# color brewer / viridis (colorblind-safe)
p + scale_color_brewer(palette = "Set1")
p + scale_color_viridis_d()
# labels
p + labs(
title = "Fuel Efficiency",
subtitle = "By vehicle class",
x = "Engine Displacement (L)",
y = "Highway MPG",
color = "Class",
caption = "Source: EPA"
)
# themes
p + theme_minimal()
p + theme_classic()
p + theme(
plot.title = element_text(face = "bold", size = 16),
panel.grid.minor = element_blank(),
legend.position = "bottom"
)
# annotations
p + annotate("text", x = 5, y = 40, label = "Outlier", color = "red") +
annotate("rect", xmin = 4, xmax = 6, ymin = 30, ymax = 45,
alpha = 0.2, fill = "blue")统计几何对象与分布
统计几何对象可视化分布和汇总。箱线图显示四分位数和异常值;小提琴图添加密度形状。geom_density 平滑直方图;geom_bin2d/hex 为大数据集显示 2D 分布。geom_qq 检查正态性(点应跟随线)。geom_errorbar/geom_pointrange 显示不确定性。对于配对比较,ggsignif 添加显著性括号。ggridges 包创建山脊图——非常适合跨多组比较分布。始终选择能诚实表示底层数据的 geom。
# boxplot by group
ggplot(mpg, aes(class, hwy)) +
geom_boxplot() +
coord_flip()
# violin + boxplot overlay
ggplot(mpg, aes(class, hwy, fill = class)) +
geom_violin() +
geom_boxplot(width = 0.1)
# density plot
ggplot(mpg, aes(hwy, fill = drv)) +
geom_density(alpha = 0.5)
# 2D density / heatmap
ggplot(diamonds, aes(carat, price)) +
geom_bin2d(bins = 50) # or geom_hex()
# quantile-quantile plot
ggplot(mtcars, aes(sample = mpg)) +
geom_qq() + geom_qq_line()
# error bars
df <- data.frame(group = c("A","B","C"),
mean = c(5, 7, 4), se = c(0.5, 0.8, 0.3))
ggplot(df, aes(group, mean)) +
geom_col() +
geom_errorbar(aes(ymin = mean - se, ymax = mean + se), width = 0.2)
# ridgeline plot (ggridges)
# library(ggridges)
# ggplot(diamonds, aes(price, cut, fill = cut)) + geom_density_ridges()分面、坐标系与扩展
带 scales = 'free' 的分面让每个面板有自己的轴范围——当组有非常不同的比例时很有用。coord_polar 将条形图转为饼图/雷达图;coord_flip 交换轴(对水平条形图很方便)。sf 包用 geom_sf 集成空间数据用于地图。patchwork 用 +、/ 和 | 运算符组合多个图形——比 gridExtra 灵活得多。ggsave 导出到 PNG/PDF/SVG;cairo_pdf 处理自定义字体。ggplotly 将 ggplot 转换为交互式 HTML 小部件用于 Web 部署。
# free scales per facet
ggplot(mpg, aes(displ, hwy)) +
geom_point() +
facet_wrap(~ class, scales = "free")
# polar coordinates (pie chart from bar)
ggplot(mtcars, aes(x = factor(1), fill = factor(cyl))) +
geom_bar(width = 1) +
coord_polar(theta = "y")
# flipped coordinates
ggplot(mpg, aes(class, hwy)) +
geom_boxplot() +
coord_flip()
# map coordinates (sf)
# library(sf)
# ggplot(nc_sf) + geom_sf(aes(fill = AREA))
# patchwork: combine multiple plots
# library(patchwork)
# p1 <- ggplot(...)
# p2 <- ggplot(...)
# p1 + p2
# p1 / (p2 + p3)
# save plots
ggsave("plot.png", width = 8, height = 6, dpi = 300)
ggsave("plot.pdf", device = cairo_pdf) # vector, supports fonts
# interactive (plotly)
# library(plotly)
# ggplotly(p)可重现绘图与函数
将 ggplot 调用包装在函数中使其可重用。{{ }} 运算符(整洁求值)让你传递未加引号的列名;.data[[string]] 处理编程式访问。保存的图形对象(.rds)可以重新加载和修改——对于需要轻微变化的报告很有用。自定义主题可以定义一次并到处应用,确保项目中的视觉一致性。对于批量生成,用 .data[[]] 遍历列名并 ggsave。这种函数式方法对 Shiny 应用和自动报告管道至关重要。
# wrap plots in functions for reuse
make_scatter <- function(data, x, y, color = NULL) {
ggplot(data, aes({{ x }}, {{ y }}, color = {{ color }})) +
geom_point() +
theme_minimal()
}
make_scatter(mpg, displ, hwy, class)
# using .data pronoun (string column names)
plot_col <- function(data, col) {
ggplot(data, aes(.data[[col]])) +
geom_bar() +
theme_minimal()
}
plot_col(mpg, "class")
# save and load plot objects
p <- ggplot(mpg, aes(displ, hwy)) + geom_point()
saveRDS(p, "plot.rds")
p_loaded <- readRDS("plot.rds")
# modify saved plot
p_loaded + geom_smooth()
# themes as reusable objects
my_theme <- theme_minimal() +
theme(text = element_text(family = "Helvetica"),
plot.title = element_text(face = "bold"))
ggplot(mpg, aes(displ, hwy)) + geom_point() + my_theme
# programmatic plot generation
for (col in c("hwy","cty","cyl")) {
p <- ggplot(mpg, aes(.data[[col]])) + geom_histogram()
ggsave(paste0("hist_", col, ".png"), p)
}tidyr 数据整理
Pivot Longer 与 Wider
整洁数据每次观察一行,每个变量一列——大多数分析函数期望此格式。pivot_longer 将宽格式转为长格式(将列收集为键值对);pivot_wider 做相反的操作。names_to 中的 .value 哨兵将列名的一部分保留为单独的列(例如,'a_1' 变为 a=1, b=1)。绘图或建模前始终重塑:ggplot2 需要长格式用于分组美学;某些建模函数需要宽格式。names_pattern 参数用正则表达式处理更复杂的列名结构。
library(tidyr)
# wide format (one row per observation, columns for each variable)
wide <- tibble(
country = c("A","B","C"),
`2020` = c(100, 150, 200),
`2021` = c(110, 160, 210),
`2022` = c(120, 170, 220)
)
# pivot_longer: wide -> long
long <- wide %>%
pivot_longer(
cols = -country, # all columns except country
names_to = "year",
values_to = "gdp"
)
# A tibble: 9 x 3
# pivot_wider: long -> wide
wide2 <- long %>%
pivot_wider(names_from = year, values_from = gdp)
# multiple value columns
df <- tibble(id = 1:2, a_1 = c(1,2), a_2 = c(3,4), b_1 = c(5,6), b_2 = c(7,8))
df %>%
pivot_longer(
-id,
names_to = c(".value", "time"), # .value keeps a, b as columns
names_sep = "_"
)
# id time a b
# 1 1 1 5
# 1 2 3 7Separate、Unite 与 Extract
separate 按分隔符将一列拆分为多列;unite 将多列合并为一列。extract 使用正则表达式捕获组进行更灵活的拆分。separate_rows 将分隔字符串展开为多行——当单元格包含列表(例如,标签、类别)时至关重要。convert = TRUE 选项自动转换类型(数字、日期)。这些函数清理杂乱的真实世界数据:拆分全名、解析日期、规范化分隔字段。与 pivot_* 结合,它们处理几乎任何重塑任务。
df <- tibble(
name = c("John_Smith", "Jane_Doe", "Bob_Jones"),
date_range = c("2020-01-01_2020-12-31", "2021-01-01_2021-12-31", "2022-01-01_2022-12-31")
)
# separate one column into many
df %>% separate(name, into = c("first", "last"), sep = "_")
# separate with conversion
df %>% separate(name, into = c("first","last"), sep = "_", convert = TRUE)
# extract with regex groups
df %>%
extract(date_range,
into = c("start", "end"),
regex = "(.+)_(.+)")
# unite multiple columns into one
df2 <- tibble(first = c("John","Jane"), last = c("Smith","Doe"))
df2 %>% unite("full_name", first:last, sep = " ")
# separate_rows: split delimited values into rows
df3 <- tibble(id = 1:2, tags = c("a,b,c", "x,y"))
df3 %>% separate_rows(tags, sep = ",")
# id tags
# 1 a
# 1 b
# 1 c
# 2 x
# 2 y处理缺失值
缺失值在真实数据中无处不在。drop_na 移除带 NA 的行;replace_na 用常量填充它们。fill 将最后观察向前传播(LOCF)——在时间序列中常见。coalesce 跨列选择第一个非 NA 值(用于合并重叠源很有用)。na_if 将哨兵值(如 -99 或 'N/A')转换为正确的 NA。在插补前始终调查值为什么缺失;MCAR(完全随机缺失)、MAR 和 MNAR 有不同的含义。对于复杂的插补,使用 mice 或 Amelia 包。
df <- tibble(
x = c(1, 2, NA, 4, 5),
y = c(NA, 2, 3, NA, 5),
z = c("a", NA, "c", "d", NA)
)
# drop rows with any NA
df %>% drop_na()
df %>% drop_na(x) # only column x
# replace NA with a value
df %>% replace_na(list(x = 0, y = -1, z = "unknown"))
# fill NA with previous/next value
df %>% fill(x, y, .direction = "down") # carry forward
df %>% fill(x, y, .direction = "up") # carry backward
# coalesce: first non-missing value
df %>% mutate(x_filled = coalesce(x, y, 0))
# na_if: replace specific value with NA
df %>% mutate(z = na_if(z, "a"))
# detect missing values
is.na(df$x)
sum(is.na(df)) # total NAs
df %>% map_df(~ sum(is.na(.))) # NAs per column
# imputation (simple)
df %>% mutate(x = if_else(is.na(x), mean(x, na.rm = TRUE), x))嵌套与列表列
列表列每行存储多个值(甚至整个 tibble、模型、图形)——对拆分-应用-组合工作流程很强大。nest() 将行分组为嵌套 tibble;map() 对每个应用函数;unnest() 将结果展开回来。这种模式(nest → map → unnest)替代了许多 for 循环,是 tidyverse 进行每组分析的习惯方式。broom::tidy/glance/augment 将模型对象转换为 tibble,使其适合嵌套。列表列也是 R 中基于 purrr 的函数式编程的基础。
# nest: group rows into list-columns
nested <- mtcars %>%
group_by(cyl) %>%
nest()
# cyl data
# 4 <tibble 11x10>
# 6 <tibble 7x10>
# 8 <tibble 14x10>
# access nested data
nested$data[[1]] # first group's tibble
# fit models to each group
models <- nested %>%
mutate(
model = map(data, ~ lm(mpg ~ wt, data = .x)),
glance = map(model, broom::glance),
tidy = map(model, broom::tidy)
)
# unnest results
models %>% unnest(glance)
models %>% unnest(tidy)
# unnest a list-column back to rows
df <- tibble(id = 1:2, vals = list(c(1,2,3), c(4,5)))
df %>% unnest(vals)
# id vals
# 1 1
# 1 2
# 1 3
# 2 4
# 2 5
# chop/unchop (similar but keeps other columns as lists)
df %>% chop(vals)
df %>% unchop(vals)矩形化与 JSON
矩形化将嵌套/层次数据(JSON、API 响应)转换为整洁的 tibble。hoist() 从列表列中提取特定元素;unnest_wider() 将列表展开为列;unnest_longer() 将每个元素展开为一行。对于深度嵌套结构,将 purrr 的 map 函数与 tibble 构造结合。带 simplifyDataFrame = TRUE 的 jsonlite::fromJSON 自动展平简单 JSON。此工作流程对于使用 REST API、NoSQL 数据库和配置文件至关重要——现代数据工程师的日常任务。
library(jsonlite)
library(tidyr)
# parse JSON
json <- '[
{"name":"Alice","age":30,"skills":["R","Python"]},
{"name":"Bob","age":25,"skills":["SQL"]}
]'
parsed <- fromJSON(json, simplifyDataFrame = FALSE)
# convert list to tibble
tibble(person = parsed) %>%
hoist(person,
name = "name",
age = "age",
skills = "skills"
)
# name age skills
# Alice 30 <chr [2]>
# Bob 25 <chr [1]>
# unnest longer for nested lists
tibble(person = parsed) %>%
unnest_wider(person) # spread list to columns
tibble(skills = list(c("R","Python"), c("SQL"))) %>%
unnest_longer(skills)
# deeply nested: use purrr + tibble
flatten_df <- function(lst) {
tibble(
name = lst$name,
age = lst$age,
n_skills = length(lst$skills)
)
}
map_dfr(parsed, flatten_df)
# rectangularize arrays
jsonlite::fromJSON(json, simplifyDataFrame = TRUE) # auto-flattenpurrr 函数式编程
map 家族与类型安全变体
purrr 的 map 家族用一致、类型安全的变体替代 lapply/sapply。map_dbl/chr/int/lgl 返回有类型向量(不匹配时报错)——比静默强制转换的 sapply 安全得多。map2 和 pmap 并行遍历多个向量。imap 同时提供值和索引。walk 用于副作用(打印、写文件)而不需要返回值。~ .x 简写创建匿名函数;.x 是第一个参数,.y 是第二个。在生产代码中始终优先使用 map_* 而非 sapply 以避免类型不稳定。
library(purrr)
# map: apply function to each element, return list
map(1:3, ~ .x ^ 2) # list(1, 4, 9)
# type-specific variants (safer, faster)
map_dbl(1:3, ~ .x ^ 2) # numeric vector: 1 4 9
map_chr(c(1,2,3), ~ paste("n=", .x))
map_int(c(1.5, 2.7), floor)
map_lgl(c(1, NA, 3), ~ !is.na(.x))
# map over two vectors in parallel
map2_dbl(c(1,2,3), c(10,20,30), ~ .x + .y) # 11 22 33
# map over multiple vectors (pmap)
pmap_dbl(list(a = 1:3, b = 4:6, c = 7:9), sum) # 12 15 18
# map over indices (imap)
imap_chr(c("a","b","c"), ~ paste0(.y, ":", .x))
# "1:a" "2:b" "3:c"
# walk: side effects (no return value)
walk(c("file1.csv","file2.csv"), ~ print(read.csv(.x)))
# map over columns of a data frame
map_dbl(mtcars, mean) # mean of each column
map(mtcars, class) # class of each column匿名函数与公式语法
purrr 提供多种指定函数的方式:简单情况用公式简写(~ .x + 1),复杂体用完整 function(x) 语法,可重用用命名函数。map 函数还接受字符串/数字以按名称/位置提取——无需包装函数。pluck() 带 .default 回退安全地导航深度嵌套结构;chuck() 是在缺失路径上报错的严格版本。这使 purrr 非常适合处理 JSON、API 响应和其他层次数据,在这些情况下基础 R 的 [[ 提取变得笨拙。
# formula syntax (~ creates a function)
map(1:3, ~ .x + 10) # .x is the argument
map2(1:3, 10:12, ~ .x + .y) # .x, .y for two args
pmap(list(1:3, 4:6, 7:9), ~ ..1 + ..2 + ..3) # ..1, ..2 for many
# full function syntax (for complex bodies)
map(1:3, function(x) {
if (x > 2) return(x * 10)
x
})
# named function
square <- function(x) x^2
map(1:3, square)
# extract by name/position (no function needed)
map(list(a = list(x = 1), b = list(x = 2)), "x")
map(list(list(1,2), list(3,4)), 1) # first element of each
# pluck: safely extract deeply nested
deep <- list(a = list(b = list(c = 42)))
pluck(deep, "a", "b", "c") # 42
pluck(deep, "a", "x", "y", .default = NA) # NA (no error)
# chuck: same but errors if missing
# chuck(deep, "a", "x") # errorReduce、Accumulate 与谓词函数
reduce() 成对组合元素(左折叠)——非常适合合并多个数据框或计算乘积。accumulate() 保留中间结果,对运行总计很有用。keep/discard 按谓词筛选元素(类似 dplyr::filter 但用于向量/列表)。some/every 测试是否有任何/所有元素满足条件。detect 找到第一个匹配元素。这些高阶函数用简洁、声明式代码替代许多循环。negate() 反转谓词函数——用于组合条件很方便。它们一起使 purrr 成为完整的函数式编程工具包。
# reduce: combine elements pairwise
reduce(c(1,2,3,4), `+`) # 10 (sum)
reduce(c(1,2,3,4), `*`) # 24 (product)
reduce(list(df1, df2, df3), full_join, by = "id") # merge many
# accumulate: keep intermediate results
accumulate(c(1,2,3,4), `+`) # 1 3 6 10 (running sum)
accumulate(c(2,3,4), `*`) # 2 6 24 (running product)
# keep/discard: filter by predicate
keep(1:10, ~ .x %% 2 == 0) # 2 4 6 8 10
discard(1:10, ~ .x %% 2 == 0) # 1 3 5 7 9
keep(mtcars, is.numeric) # keep numeric columns
# some/every/detect: test predicates
some(1:10, ~ .x > 5) # TRUE
every(1:10, ~ is.numeric(.x)) # TRUE
detect(1:10, ~ .x > 5) # 6 (first match)
detect_index(1:10, ~ .x > 5) # 6 (position)
# head_while/tail_while
head_while(1:10, ~ .x < 5) # 1 2 3 4
tail_while(10:1, ~ .x > 5) # 10 9 8 7 6
# negate a predicate
negate(is.na)(5) # TRUE
keep(c(1, NA, 3), negate(is.na)) # 1 3Safely、Possibly 与错误处理
safely() 包装函数以始终返回带 'result' 和 'error' 的列表——从不抛出。这对批量操作至关重要,其中一个失败不应停止整个运行。possibly() 在出错时返回默认值(当你不需要错误详情时更干净)。quietly() 捕获警告和消息。transpose() 将 {result, error} 对列表翻转为单独的列表——便于将成功与失败分开。在处理可能单独失败的许多项目(API 调用、文件读取、模型拟合)时使用这些。
# safely: capture errors without stopping
safe_log <- safely(log)
safe_log(10) # list(result = 2.3, error = NULL)
safe_log(-1) # list(result = NULL, error = <error>)
# process many, capturing failures
results <- map(c(10, -1, 0, "x"), safely(log))
successes <- map(results, "result") %>% discard(is.null)
failures <- map(results, "error") %>% discard(is.null)
# possibly: return default on error
safe_log2 <- possibly(log, otherwise = NA_real_)
map_dbl(c(10, -1, 0, "x"), safe_log2) # 2.3 NA NA NA
# quietly: capture warnings/messages
quiet_log <- quietly(log)
quiet_log(10) # list(result, warnings, messages)
# transpose: restructure list-of-lists
transposed <- transpose(results)
transposed$result # all results
transposed$error # all errors
# rate-limited / retried operations
# library(purrr)
# safely_slow <- slowly(safely(f), rate = rate_backoff())
# walk + safely for batch file processing
walk(files, ~ safely(read.csv)(.x))向量化与并行 purrr
modify() 类似 map() 但保留输入类型——非常适合原地转换数据框列。modify_if 和 modify_at 针对特定列。list_modify/list_merge 非破坏性地更新列表。对于并行化,furrr 使用 future 后端提供 future_map(map 的直接替代)——通过更改 plan() 从顺序切换到并行。.progress = TRUE 选项显示进度条,对长时间运行的 map 非常宝贵。这些工具使 purrr 既适合交互式探索也适合生产管道。
# vectorized map (faster for simple operations)
# map_vec returns a vector, auto-detecting type
map_vec(1:3, ~ .x * 2) # numeric vector
map_vec(c("a","b"), ~ toupper(.x)) # character vector
# modify: like map but preserves type
modify(mtcars, ~ .x * 2) # still a data frame
modify_if(mtcars, is.numeric, ~ .x * 2)
modify_at(mtcars, c("mpg","cyl"), ~ .x * 2)
# list_modify / list_merge
l1 <- list(a = 1, b = 2)
list_modify(l1, b = 20, c = 30) # a=1, b=20, c=30
list_merge(l1, list(b = 20, c = 30))
# parallel mapping with future + furrr
# library(furrr)
# plan(multisession) # parallel backend
# future_map(1:100, slow_function, .options = furrr_options(seed = TRUE))
# progress bar
# walk(1:100, ~ Sys.sleep(0.1), .progress = TRUE)
# conditional execution in map
map(1:5, ~ if (.x > 3) .x * 10 else .x)
# 1 2 3 40 50stringr 与 lubridate
stringr 模式匹配与提取
stringr 提供一致、管道友好的 API 包装 ICU 正则表达式引擎。所有函数以 str_ 开头便于自动补全。str_detect/which/subset 按模式筛选。str_extract/match 提取匹配项(match 捕获组)。str_replace/remove 修改文本。str_split 将字符串分成片段。底层正则表达式语法是标准的(类 PCRE),带有 \\w、\\d、\\s 等辅助。对于固定字符串(无正则),使用 str_detect(text, fixed('a.b')) 进行字面匹配。stringr 比基础 R 的 grep/sub 家族一致得多。
library(stringr)
text <- c("apple pie", "banana bread", "cherry tart")
# detect pattern
str_detect(text, "pie") # TRUE FALSE FALSE
str_which(text, "pie") # 1
str_count(text, "a") # 1 3 1
# subset strings
str_subset(text, "pie") # "apple pie"
str_extract(text, "[aeiou]") # first vowel
str_extract_all(text, "[aeiou]") # list of all vowels
# match groups
str_match(text, "(\\w+) (\\w+)") # matrix with groups
str_match_all(text, "(\\w+)")
# locate pattern positions
str_locate(text, "a") # start/end matrix
str_locate_all(text, "a")
# replace
str_replace(text, "a", "A") # first match
str_replace_all(text, "a", "A") # all matches
str_remove(text, "a") # str_replace(text, "a", "")
# split
str_split("a,b,c", ",") # list of vectors
str_split_fixed("a,b,c", ",", 3) # matrix字符串操作与转换
stringr 以一致的接口覆盖所有常见字符串操作。大小写转换(str_to_upper/lower/title/sentence)尊重区域设置。str_trim 移除空白;str_squish 还折叠内部空白。str_pad 将字符串对齐到固定宽度(对格式化表格很有用)。str_sub 用 R 的从 1 开始的索引提取或替换子字符串(负索引从末尾计数)。str_c 是 paste0 的管道友好等价物。这些函数使字符串操作与基础 R 的分散字符串函数相比可预测且可读。
library(stringr)
# case conversion
str_to_upper("hello") # "HELLO"
str_to_lower("WORLD") # "world"
str_to_title("the wind in the willows") # "The Wind In The Willows"
str_to_sentence("hello world") # "Hello world"
# trimming and padding
str_trim(" hello ") # "hello" (both sides)
str_trim(" hello ", side = "left")
str_squish(" hello world ") # "hello world" (collapse spaces)
str_pad("5", width = 3, pad = "0") # "005"
str_pad("5", width = 3, side = "left", pad = "0") # "005"
# subsetting
str_sub("hello", 1, 3) # "hel"
str_sub("hello", -3, -1) # "llo" (negative from end)
str_sub("hello", 2) # "ello" (to end)
str_sub("hello", 1, 1) <- "H" # assignment: "Hello"
# length
str_length("hello") # 5
str_length(c("a","bb","ccc")) # 1 2 3
# combine and duplicate
str_c("a", "b", "c", sep = "-") # "a-b-c"
str_c(c("x","y"), collapse = ",") # "x,y"
str_dup("ab", 3) # "ababab"
# truncate
str_trunc("Hello World", 8) # "Hello..."正则表达式深入
stringr 使用带标准语法的 ICU 正则表达式引擎。^ 和 $ 锚定到开头/结尾。字符类 [a-z] 匹配范围;\w、\d、\s 是简写。量词 {n,m}、?、+、* 控制重复。圆括号创建捕获组;| 是交替。先行断言 (?=) 和后行断言 (?<=) 断言但不消费。命名组 (?<name>...) 使提取自文档化。匹配包含正则表达式元字符的字面字符串时始终使用 fixed()——它也更快。对于复杂解析,考虑 rebus 包以可读方式构建正则表达式。
library(stringr)
# anchors
str_detect(c("cat","scat","catch"), "^cat") # starts with: T F T
str_detect(c("cat","scat","catch"), "cat$") # ends with: T F F
# character classes
str_extract_all("a1 b2 c3", "[a-z]") # letters
str_extract_all("a1 b2 c3", "[0-9]") # digits
str_extract_all("a1 b2 c3", "[^0-9]") # non-digits
str_extract_all("a1 b2 c3", "\\w") # word chars
str_extract_all("a1 b2 c3", "\\s") # whitespace
# quantifiers
str_detect("aaa", "a{2,3}") # 2-3 a's
str_detect("color", "colou?r") # u optional
str_detect("abbbbc", "ab+c") # one or more
str_detect("ac", "ab*c") # zero or more
# groups and alternation
str_match("2024-01-15", "(\\d{4})-(\\d{2})-(\\d{2})")
str_detect("cat", "cat|dog|bird") # alternation
# lookahead/lookbehind
str_extract("abc123", "(?<=abc)\\d+") # 123 (lookbehind)
str_extract("abc123", "\\d+(?=end)") # nothing (no 'end')
# named capture (stringr 1.5+)
str_match("John 30", "(?<name>[A-Za-z]+) (?<age>\\d+)")
# use fixed() for literal matching
str_detect("a.b.c", fixed(".")) # TRUE (no regex)lubridate 日期与时间解析
lubridate 的解析函数(ymd、mdy、dmy)自动检测分隔符和格式——比基础 R 的 strptime 宽容得多。函数名指示顺序:ymd = 年-月-日。make_date/assemble 从组件构建。today() 和 now() 返回当前日期/时间。组件函数(year、month、day、wday、yday)既获取又设置值;wday(label=TRUE) 返回工作日名称。update() 一次修改多个组件。始终为 datetime 显式指定 tz(时区)以避免静默的 UTC 假设。
library(lubridate)
# parse dates (auto-detect format)
ymd("2024-01-15") # 2024-01-15
ymd("2024/01/15")
ymd("24/1/15")
mdy("01-15-2024") # month-day-year
dmy("15/01/2024") # day-month-year
ymd_hms("2024-01-15 14:30:00")
ymd_hm("2024-01-15 14:30")
hms("14:30:45")
# from components
make_date(2024, 1, 15)
make_datetime(2024, 1, 15, 14, 30, 0, tz = "UTC")
# current date/time
today() # 2024-01-15
now() # 2024-01-15 14:30:00 UTC
# extract components
d <- ymd("2024-01-15")
year(d) # 2024
month(d) # 1
month(d, label = TRUE) # "Jan"
day(d) # 15
wday(d, label = TRUE) # "Mon"
yday(d) # 15 (day of year)
quarter(d) # 1
semester(d) # 1
# set components
year(d) <- 2025
month(d) <- 6
d <- update(d, year = 2025, month = 6, day = 1)时区、持续时间与间隔
时区是日期时间处理中最棘手的部分。with_tz 在另一个时区显示同一时刻;force_tz 在不改变时钟的情况下更改时区(用于修复错误标记的数据很有用)。持续时间(dseconds、dhours)是精确的秒数——适用于物理学。周期(minutes、hours、days)是日历感知的:向 1 月 31 日添加 months(1) 得到 2 月 28 日,days(1) 处理夏令时转换。间隔(start %--% end)表示带固定端点的跨度。使用周期进行人类尺度算术(调度),使用持续时间进行经过时间测量。
library(lubridate)
# timezones
t <- ymd_hms("2024-01-15 14:30:00", tz = "UTC")
with_tz(t, "America/New_York") # 09:30 EST
with_tz(t, "Asia/Shanghai") # 22:30 CST
force_tz(t, "America/New_York") # keeps clock, changes zone
# list timezones
OlsonNames()[1:5]
# durations (exact seconds)
d <- dseconds(60) + dminutes(2) # 180 seconds
as.numeric(d, "seconds")
dhours(1) + ddays(1)
# periods (calendar-aware, variable length)
p <- minutes(5) + hours(2)
ymd("2024-01-15") + p # 2024-01-15 02:05:00
ymd("2024-03-10") + days(1) # handles DST
months(1) # variable length!
# intervals (start to end)
int <- interval(ymd("2024-01-01"), ymd("2024-02-01"))
int_length(int) # seconds
int %within% int2 # test overlap
as.period(int) # "1M 0S"
# arithmetic
ymd("2024-01-15") %--% ymd("2024-02-15") # interval
ymd("2024-01-15") + weeks(2) # period
ymd("2024-01-15") + dweeks(2) # duration (exact)
# rounding dates
floor_date(t, "hour") # round down
ceiling_date(t, "day")
round_date(t, "hour")R Markdown 与报告
R Markdown 基础与代码块
R Markdown 将散文(Markdown)、代码(R/Python/SQL)和输出(表格、图形)组合成可重现报告。YAML 头设置元数据和输出格式。用三个反引号分隔的代码块在 knit 时执行;代码块选项控制行为(echo=FALSE 隐藏代码,include=FALSE 运行但不显示任何内容,fig.width 设置图形大小)。带单反引号的内联代码将值插入文本。kable() 格式化表格;更精美的表格使用 kableExtra 或 gt。knit 按钮渲染为 HTML/PDF/Word。
---
title: "Analysis Report"
author: "Data Team"
date: "`r format(Sys.Date(), '%B %d, %Y')`"
output: html_document
---
## Introduction
This is **Markdown** with embedded R.
Inline code: the mean is `r mean(mtcars$mpg)`.
```{r setup, include=FALSE}
knitr::opts_chunk$set(echo = FALSE, warning = FALSE, message = FALSE)
library(dplyr)
library(ggplot2)
```
```{r load-data}
df <- mtcars
head(df)
```
```{r plot, fig.width=8, fig.height=6, fig.cap="MPG by weight"}
ggplot(df, aes(wt, mpg)) + geom_point() + theme_minimal()
```
```{r table}
library(knitr)
kable(summary(df), caption = "Summary statistics")
```输出格式与参数
一个 R Markdown 文件可以从同一源产生多种输出格式(HTML、PDF、Word、幻灯片)——只需在 output 下列出它们。HTML 特定选项(toc_float、code_folding、theme)创建交互式文档。参数(params)让你用不同输入渲染同一报告——对批量报告至关重要(每个地区、客户或时间段一份)。用 rmarkdown::render() 编程式渲染以在 cron 作业或 Shiny 应用中自动化报告生成。params 对象在代码块内可用于筛选数据。
---
title: "Quarterly Report"
output:
html_document:
toc: true
toc_float: true
code_folding: hide
theme: flatly
df_print: paged
pdf_document:
toc: true
number_sections: true
word_document: default
params:
quarter: "Q1"
region: "North"
---
## Report for `r params$quarter` - `r params$region`
```{r}
data <- filter(all_data, quarter == params$quarter, region == params$region)
```
# Render with parameters:
# rmarkdown::render("report.Rmd", params = list(quarter = "Q2"))
# parameterized batch rendering
quarters <- c("Q1","Q2","Q3","Q4")
for (q in quarters) {
rmarkdown::render("report.Rmd",
params = list(quarter = q),
output_file = paste0("report_", q, ".html")
)
}使用 kable、gt 与 DT 的表格
kable + kableExtra 生成带样式、分组和条件格式的出版质量表格。gt 是更现代的替代方案,具有更具表现力的语法(类似表格的 ggplot)。DT 创建带搜索、排序和分页的交互式 HTML 表格——非常适合探索性报告。reactable 提供更多交互性。根据输出选择:PDF/Word 用 kable/gt,HTML 用 DT/reactable。始终一致地格式化数字(fmt_number、formatRound)并添加标题以提供上下文。好的表格与好的图形一样重要,用于传达结果。
library(knitr); library(kableExtra)
# basic kable
kable(head(mtcars), caption = "First 6 rows")
# styled kable
kable(head(mtcars), "html") %>%
kable_styling(bootstrap_options = c("striped","hover","condensed"),
full_width = FALSE) %>%
row_spec(0, bold = TRUE, background = "lightblue") %>%
column_spec(1, bold = TRUE)
# gt package (modern, flexible)
library(gt)
mtcars %>%
head() %>%
gt() %>%
tab_header(title = "Car Specifications", subtitle = "Top 6") %>%
fmt_number(columns = mpg, decimals = 1) %>%
data_color(columns = mpg, colors = scales::col_numeric("Reds", NULL))
# DT: interactive tables
library(DT)
datatable(mtcars,
filter = "top",
options = list(pageLength = 10),
caption = "Searchable table"
) %>%
formatRound(columns = c("mpg","disp"), digits = 2)
# reactable for even more interactivity
# library(reactable)Quarto 与高级功能
Quarto 是 R Markdown 的继任者,在一个文档中支持 R、Python、Julia 和 Observable。交叉引用(@fig-label、@tbl-label)自动编号图形和表格。用于代码块选项的 #| 语法比旧的 knitr opts 更干净。代码折叠为交互式 HTML 创建可折叠代码块。Quarto 的多语言支持使其非常适合同时使用 R 和 Python 的团队。现有 .Rmd 文件可以转换;语法类似但更一致。Quarto 还从同一源生成演示文稿(revealjs)、网站和书籍。
---
title: "Modern Report"
format:
html:
toc: true
code-fold: true
pdf:
documentclass: article
execute:
echo: false
warning: false
filters:
- lightbox
---
## Cross-references
See Figure @fig-scatter and Table @tbl-summary.
```{r}
#| label: fig-scatter
#| fig-cap: "MPG vs Weight"
ggplot(mtcars, aes(wt, mpg)) + geom_point()
```
```{r}
#| label: tbl-summary
#| tbl-cap: "Summary by cylinder"
mtcars %>%
group_by(cyl) %>%
summarize(mean_mpg = mean(mpg)) %>%
gt()
```
## Multiple languages
```{python}
import pandas as pd
df = pd.DataFrame({'a': [1,2,3]})
print(df)
```
```{sql}
SELECT * FROM mtcars WHERE mpg > 20
```
# Quarto supports Python, Julia, Observable, and more
# in the same document, sharing data via ojs.自动化报告与 Cron
自动化报告将一次性分析转换为定期交付物。rmarkdown::render() 以编程方式生成报告;与 params 结合进行自定义。用 blastula 或 emayili 发送结果。用 cronR(Linux/Mac)或 taskscheduleR(Windows)安排每日/每周运行。对于批量生成,用 purrr::walk 遍历参数。启用代码块缓存(cache=TRUE)以加速昂贵计算的迭代——只有更改的代码块重新运行。此管道是 R 中商业智能和自动化数据产品的骨干。
# render a report programmatically
rmarkdown::render("daily_report.Rmd",
output_dir = "reports",
output_file = paste0("report_", Sys.Date(), ".html"),
params = list(date = Sys.Date() - 1),
quiet = TRUE
)
# email the report
# library(blastula)
# render_email("email_template.Rmd") %>%
# smtp_send(
# to = "[email protected]",
# from = "[email protected]",
# subject = paste("Daily Report -", Sys.Date())
# )
# schedule with cron (Linux/Mac) or Task Scheduler (Windows)
# crontab -e:
# 0 8 * * * cd /path/to/project && Rscript -e 'rmarkdown::render("daily.Rmd")'
# or use the cronR package
# library(cronR)
# cmd <- cron_rscript("render_report.R")
# cron_add(cmd, frequency = "daily", at = "08:00")
# batch render multiple reports
purrr::walk(regions, function(r) {
rmarkdown::render("regional.Rmd",
params = list(region = r),
output_file = paste0("report_", r, ".html")
)
})
# version control: cache expensive computations
# knitr::opts_chunk$set(cache = TRUE)Shiny Web 应用
应用结构:UI 与 Server
每个 Shiny 应用有两部分:ui(HTML 布局)和 server(R 逻辑)。UI 使用 fluidPage 和布局函数(sidebarLayout、tabsetPanel、navbarPage)。输入(sliderInput、selectInput 等)收集用户数据;输出(plotOutput、textOutput)显示结果。server 函数通过 render* 函数连接它们。响应式表达式(reactive({...}))缓存计算且仅在输入更改时重新运行。保存为 app.R 并用 runApp() 运行或托管在 shinyapps.io / RStudio Connect / Shiny Server 上。
library(shiny)
ui <- fluidPage(
titlePanel("My First Shiny App"),
sidebarLayout(
sidebarPanel(
sliderInput("n", "Number of points:", min = 1, max = 100, value = 50),
selectInput("color", "Color:", choices = c("red","blue","green"))
),
mainPanel(
plotOutput("scatter"),
verbatimTextOutput("summary")
)
)
)
server <- function(input, output, session) {
data <- reactive({
data.frame(x = rnorm(input$n), y = rnorm(input$n))
})
output$scatter <- renderPlot({
plot(data()$x, data()$y, col = input$color, pch = 19,
xlab = "X", ylab = "Y", main = paste("n =", input$n))
})
output$summary <- renderPrint({
summary(data())
})
}
shinyApp(ui, server)
# save as app.R in a folder, then runApp("folder")
# or run with shiny::runApp()响应式编程
响应式是 Shiny 的核心概念。reactive() 创建惰性、缓存的表达式,仅在依赖项更改时重新运行。observe() 为副作用(更新输入、日志记录)急切运行。observeEvent/eventReactive 在特定事件(按钮点击)上触发。reactiveVal 和 reactiveValues 保存可变状态。isolate() 读取值而不创建依赖项。关键洞察:输出在其响应式依赖项更改时自动重新渲染。误解响应式是 Shiny 错误的头号来源——使用 reactiveLogViewer() 调试依赖图。
server <- function(input, output, session) {
# reactive expression: lazy, cached
filtered <- reactive({
mtcars %>% filter(cyl == input$cyl)
})
# observe: eager, for side effects
observe({
updateSelectInput(session, "model",
choices = unique(filtered()$model))
})
# observeEvent: triggered by specific input
observeEvent(input$reset, {
updateSliderInput(session, "cyl", value = 4)
})
# eventReactive: lazy, triggered by event
result <- eventReactive(input$go, {
run_analysis(input$param)
})
# reactiveVal: mutable value
counter <- reactiveVal(0)
observeEvent(input$add, counter(counter() + 1))
# reactiveValues: multiple mutable values
rv <- reactiveValues(data = NULL, status = "ready")
observeEvent(input$load, {
rv$data <- read.csv(input$file$datapath)
rv$status <- "loaded"
})
# isolate: read without dependency
output$plot <- renderPlot({
plot(isolate(rv$data))
})
}动态 UI 与模块
动态 UI(renderUI + uiOutput)根据数据或用户选择生成控件。insertUI/removeUI 添加/移除元素而不重新渲染整个页面。模块(NS + moduleServer)封装 UI+server 逻辑以重用——对带重复组件的复杂应用至关重要。每个模块实例获得唯一命名空间(ns)以使输入 ID 不冲突。模块是构建可维护 Shiny 应用的关键:将应用拆分为小的、可测试的模块(图表模块、筛选模块、数据上传模块)并组合它们。
# dynamic UI
ui <- fluidPage(
uiOutput("dynamic_controls"),
plotOutput("plot")
)
server <- function(input, output, session) {
output$dynamic_controls <- renderUI({
cols <- names(input$data)
selectInput("xvar", "X variable:", choices = cols)
})
# insert/remove UI
observeEvent(input$add, {
insertUI("#placeholder", "beforeEnd",
sliderInput(paste0("s", input$add), "Slider", 0, 100, 50))
})
# modules: reusable UI+server
histogramUI <- function(id) {
ns <- NS(id)
tagList(
selectInput(ns("var"), "Variable:", names(mtcars)),
plotOutput(ns("hist"))
)
}
histogramServer <- function(id) {
moduleServer(id, function(input, output, session) {
output$hist <- renderPlot(hist(mtcars[[input$var]]))
})
}
# use module
ui <- fluidPage(histogramUI("hist1"), histogramUI("hist2"))
server <- function(input, output, session) {
histogramServer("hist1")
histogramServer("hist2")
}
}输入、输出与渲染
Shiny 支持多种输入类型(file、date、slider、selectize、checkbox)和输出类型(plot、table、text、image、UI)。DT::renderDataTable 创建带搜索/排序的交互式表格。downloadHandler 让用户导出数据。.data[[]] 在 ggplot 中启用动态列选择。对于大数据,使用 DT 的服务器端处理或 plotly 进行交互式绘图。renderImage 显示预生成文件(比 renderPlot 对复杂视觉效果更快)。将输入与响应式表达式结合以构建复杂、响应迅速的仪表板。
# file upload
fileInput("data", "Upload CSV:", accept = ".csv")
# in server: input$data$datapath (temp file path)
# date range
dateRangeInput("dates", "Period:", start = Sys.Date() - 30, end = Sys.Date())
# tabset with conditional panels
tabsetPanel(
tabPanel("Plot", plotOutput("p")),
tabPanel("Table", DT::dataTableOutput("t")),
tabPanel("Summary", verbatimTextOutput("s"))
)
# render DataTable (interactive)
output$t <- DT::renderDataTable({
datatable(filtered(), filter = "top", options = list(pageLength = 25))
})
# render UI from ggplot
output$p <- renderPlot({
ggplot(filtered(), aes(.data[[input$x]], .data[[input$y]])) +
geom_point() + theme_minimal()
})
# download handlers
output$download <- downloadHandler(
filename = function() paste0("data_", Sys.Date(), ".csv"),
content = function(file) write.csv(filtered(), file)
)
# render image (pre-generated)
output$img <- renderImage({
list(src = "plot.png", contentType = "image/png", width = 400)
}, deleteFile = FALSE)性能、部署与扩展
Shiny 性能:debounce/throttle 快速输入(搜索框)以避免过多重新计算。使用 future + promises 进行异步操作(不阻塞事件循环)。bindCache 按键缓存渲染结果——对许多用户访问的昂贵图形有巨大收益。部署到 shinyapps.io(托管云)、RStudio Connect(商业)或 Shiny Server(开源)在 Docker 后面。对于高流量,运行多个工作进程并负载均衡。用 shinylogs 监控使用情况以了解用户行为并捕获错误。用 profvis::profvis() 分析慢应用以找到瓶颈。
# performance: debounce/throttle rapid inputs
input_debounced <- debounce(reactive({ input$text }), 500)
# async with future
library(future)
plan(multisession)
result <- reactive({
future_promise({ expensive_computation(input$params) })
})
# caching expensive computations
# in UI: add resourcePath or use bindCache
output$plot <- renderPlot({ ... }) %>% bindCache(input$dataset)
# deploy to shinyapps.io
# library(rsconnect)
# deployApp("myapp/")
# run multiple Shiny apps behind a load balancer
# (shinyapps.io / RStudio Connect handle this automatically)
# dockerize for self-hosting
# Dockerfile:
# FROM rocker/shiny
# COPY app.R /srv/shiny-server/
# EXPOSE 3838
# shiny server config (/etc/shiny-server/shiny-server.conf)
# run_as shiny;
# server { listen 3838; location / { site_dir /srv/shiny-server; } }
# monitor with shinylogs or shiny.telemetry统计检验与推断
假设检验框架
假设检验评估观察到的数据是否与零假设一致。t 检验比较均值(参数,假设正态性);Wilcoxon 是非参数替代方案。始终报告效应量和置信区间,而不仅仅是 p 值——p 取决于样本量,而 CI 显示实际显著性。解释前检查假设:正态性(Shapiro-Wilk)、等方差(Levene)。功效分析(pwr 包)在收集数据前确定所需样本量。0.05 阈值是常规的,不是神奇的——考虑效应量和上下文。
# one-sample t-test: is mean different from hypothesized value?
t.test(mtcars$mpg, mu = 20)
# t = 0.085, df = 31, p-value = 0.933
# 95% CI: [17.92, 22.26]
# mean of x: 20.09
# two-sample t-test
t.test(mpg ~ cyl, data = subset(mtcars, cyl %in% c(4, 6)))
# paired t-test
t.test(pre, post, paired = TRUE)
# Wilcoxon (non-parametric alternative)
wilcox.test(mpg ~ cyl, data = subset(mtcars, cyl %in% c(4,6)))
# interpret results:
# p < 0.05: reject null hypothesis (significant)
# p > 0.05: fail to reject (not enough evidence)
# CI shows plausible range of effect
# check assumptions: normality (shapiro.test), equal variance
# power analysis
library(pwr)
pwr.t.test(d = 0.5, sig.level = 0.05, power = 0.8)
# n = 64 per group needed to detect medium effectANOVA 与多重比较
ANOVA 检验 3+ 组之间均值是否不同。F 检验告诉你是否存在任何差异;事后检验(Tukey HSD、带校正的 pairwise.t.test)识别哪些组不同。始终检查假设(正态性、方差齐性),如果违反则使用非参数替代方案(Kruskal-Wallis)。对于重复测量或层次数据,使用混合效应模型(lme4::lmer)正确处理受试者内相关性。多重比较校正(Bonferroni、FDR)在运行许多检验时防止假阳性。afex 包简化了重复测量 ANOVA。
# one-way ANOVA: compare means across 3+ groups
fit <- aov(mpg ~ factor(cyl), data = mtcars)
summary(fit)
# Df Sum Sq Mean Sq F value Pr(>F)
# factor(cyl) 2 824.8 412.4 39.7 4.98e-09
# check assumptions
plot(fit) # residuals vs fitted, QQ
shapiro.test(resid(fit)) # normality of residuals
library(car); leveneTest(fit) # equal variance
# post-hoc tests (which groups differ?)
TukeyHSD(fit) # Tukey HSD
pairwise.t.test(mtcars$mpg, mtcars$cyl, p.adjust = "bonferroni")
# two-way ANOVA with interaction
fit2 <- aov(mpg ~ cyl * am, data = mtcars)
summary(fit2)
# Kruskal-Wallis (non-parametric ANOVA)
kruskal.test(mpg ~ factor(cyl), data = mtcars)
# repeated measures ANOVA
# library(afex)
# aov_ez(id = "subject", dv = "score", data = df, within = "condition")
# mixed-effects models (lme4)
library(lme4)
lmer(score ~ treatment + (1|subject), data = df)相关与回归诊断
相关衡量线性关联(-1 到 1);cor.test 添加推断。线性回归(lm)拟合 y = β0 + β1*x + ε。四个诊断图揭示假设违反:非线性、非正态残差、异方差和影响点。VIF > 5-10 表示多重共线性(预测变量过于相关)。Cook 距离识别影响观察值。适当使用置信区间(均值响应)与预测区间(新观察值)。用 anova(嵌套)或 AIC/BIC(非嵌套,越低越好)比较模型。信任 p 值前始终可视化。
# correlation
cor(mtcars$mpg, mtcars$wt) # -0.867
cor.test(mtcars$mpg, mtcars$wt) # with p-value
cor(mtcars[, c("mpg","wt","hp","disp")]) # correlation matrix
library(corrplot); corrplot(cor(mtcars[,1:4]))# visualize
# linear regression
fit <- lm(mpg ~ wt + hp, data = mtcars)
summary(fit)
confint(fit) # CIs for coefficients
# diagnostics
par(mfrow = c(2,2)); plot(fit)
# 1. Residuals vs Fitted: check linearity
# 2. Normal Q-Q: check normality
# 3. Scale-Location: check homoscedasticity
# 4. Residuals vs Leverage: check influential points
# check for multicollinearity
library(car); vif(fit) # variance inflation factor
# influential observations
cooks.distance(fit) |> plot()
influence.measures(fit)
# predictions with intervals
predict(fit, newdata, interval = "confidence") # CI for mean
predict(fit, newdata, interval = "prediction") # PI for new obs
# compare nested models
anova(fit1, fit2) # F-test
AIC(fit1, fit2); BIC(fit1, fit2) # information criteria分类数据:卡方与 Fisher
卡方检验分类变量之间的独立性;Fisher 精确检验对小样本(期望计数 < 5)更准确。信任卡方结果前检查期望计数。拟合优度比较观察值与理论比例。McNemar 检验配对名义数据(之前/之后)。效应量(Cramer's V、phi)量化超越 p 值的关联强度。马赛克图直观可视化列联表。对于有序数据,考虑 Spearman 相关或趋势检验。vcd(可视化分类数据)包提供全面工具。
# contingency table
tbl <- table(mtcars$cyl, mtcars$am)
# 0 1
# 4 3 8
# 6 4 3
# 8 12 2
# chi-square test of independence
chisq.test(tbl)
# X-squared = 8.74, df = 2, p-value = 0.0126
# expected counts (should be > 5 for valid chi-square)
chisq.test(tbl)$expected
# Fisher's exact test (small samples)
fisher.test(tbl)
# goodness of fit (one variable vs expected proportions)
chisq.test(c(10, 20, 30), p = c(1/3, 1/3, 1/3))
# McNemar's test (paired nominal data)
mcnemar.test(table(pre, post))
# Cochran-Mantel-Haenszel (stratified)
mantelhaen.test(tbl3d)
# visualize
library(ggplot2); library(ggmosaic)
ggplot(as.data.frame(tbl)) +
geom_mosaic(aes(weight = Freq, x = product(Var1), fill = Var2))
# effect size
library(vcd); assocstats(tbl) # Cramer's V, phi使用 brms 的贝叶斯推断
贝叶斯推断(通过 brms,它包装 Stan)提供完整的后验分布而非点估计。指定先验以编码领域知识;弱信息先验(normal(0, 10))在不产生偏差的情况下正则化。后验汇总给出可信区间(直接概率陈述,不同于频率派 CI)。pp_check 通过比较模拟数据与观察数据验证模型拟合。LOO-CV 和 WAIC 通过交叉验证预测准确性比较模型。层次模型自然处理分组数据。贝叶斯方法在小样本、复杂模型和需要不确定性量化时表现出色。
library(brms)
# Bayesian linear regression
fit <- brm(
mpg ~ wt + hp,
data = mtcars,
family = gaussian(),
prior = c(
prior(normal(0, 10), class = "b"), # weakly informative
prior(cauchy(0, 2), class = "sigma")
),
chains = 4, cores = 4, iter = 2000
)
# summary
summary(fit)
plot(fit) # posterior distributions
pp_check(fit) # posterior predictive check
# extract posterior samples
posterior <- as_draws_df(fit)
mean(posterior$b_wt > 0) # P(coefficient > 0)
# predictions
posterior_predict(fit, newdata) |> as.data.frame() -> preds
# model comparison
fit_null <- brm(mpg ~ 1, data = mtcars, ...)
loo(fit, fit_null) # leave-one-out CV
waic(fit, fit_null)
# hierarchical model
fit_h <- brm(
score ~ treatment + (1 + treatment|subject),
data = df, family = gaussian()
)
# Stan code behind the model
stancode(fit)使用 caret 的机器学习
数据分割与预处理
正确的数据分割和预处理是 ML 成功的 80%。createDataPartition 进行分层抽样(保留类平衡)。trainControl 配置重采样(CV、bootstrap、重复 CV)。preProcess 处理标准化、变换、PCA 和插补——始终只在训练数据上拟合并应用于测试以避免泄漏。nzv 移除无信息列。对于时间序列,使用 createTimeSlices 而非随机 CV。caret 的统一接口意味着相同的预处理适用于所有模型类型。
library(caret)
# split data
set.seed(42)
idx <- createDataPartition(iris$Species, p = 0.8, list = FALSE)
train <- iris[idx, ]; test <- iris[-idx, ]
# stratified split for classification
# createDataPartition preserves class proportions
# k-fold cross-validation
ctrl <- trainControl(
method = "cv", number = 10,
savePredictions = "final",
classProbs = TRUE, # for AUC
summaryFunction = multiClassSummary
)
# preprocessing pipeline
preproc <- preProcess(train[, -5],
method = c("center","scale","YeoJohnson","nzv"))
train_processed <- predict(preproc, train[, -5])
test_processed <- predict(preproc, test[, -5])
# common preprocessing methods:
# center, scale: standardize
# BoxCox, YeoJohnson: transform to normality
# pca: principal components
# nzv: remove near-zero variance
# knnImpute, bagImpute: impute missing values
# dummy variables for categorical
dummies <- dummyVars(Species ~ ., data = train)
predict(dummies, train)训练模型与调优
caret 的 train() 函数为 200+ 模型提供统一接口——只需更改 method 字符串。tuneLength 自动生成调优网格;tuneGrid 给出完全控制。resamples() 通过重采样性能比较多个模型(比单次测试集评估更诚实)。始终在模型间使用相同的 trControl 以进行公平比较。resamples 的点图显示性能重叠——如果 CI 重叠,模型没有显著差异。选择最佳模型一个标准误差内的最简单模型('一 SE 规则')。
library(caret)
# train a random forest
ctrl <- trainControl(method = "cv", number = 5)
rf_fit <- train(
Species ~ ., data = train,
method = "rf",
trControl = ctrl,
tuneGrid = expand.grid(mtry = 1:4),
tuneLength = 5 # auto-tune grid
)
print(rf_fit) # shows accuracy by tuning param
plot(rf_fit) # tuning curve
# try multiple models
models <- c("rf","gbm","svmRadial","knn","rpart")
fits <- lapply(models, function(m) {
train(Species ~ ., data = train, method = m, trControl = ctrl)
})
names(fits) <- models
# compare models
resamps <- resamples(fits)
summary(resamps)
dotplot(resamps, metric = "Accuracy")
# custom tuning grid
grid <- expand.grid(
mtry = c(2, 4, 8),
splitrule = c("gini","extratrees"),
min.node.size = c(1, 5, 10)
)
fit <- train(Species ~ ., data = train, method = "ranger",
trControl = ctrl, tuneGrid = grid)分类指标与混淆矩阵
对于不平衡数据,仅准确率具有误导性。confusionMatrix 提供每类敏感度(召回率)、特异度、精确率和 F1。对于二分类问题,ROC-AUC 衡量判别能力;当正类罕见时 PR 曲线更好。对于多分类,使用宏/微平均指标或对数损失。始终在留出测试集上评估(或通过嵌套 CV 获得无偏估计)。在 caret 中,在 trainControl 中设置 summaryFunction 以优化正确的指标(例如,概率预测用 mnLogLoss)。报告性能的置信区间,而不仅仅是点估计。
# predictions
pred <- predict(rf_fit, test)
prob <- predict(rf_fit, test, type = "prob")
# confusion matrix
cm <- confusionMatrix(pred, test$Species)
print(cm)
# Reference
# Prediction setosa versicolor virginica
# setosa 10 0 0
# versicolor 0 10 1
# virginica 0 0 9
# Overall Accuracy: 0.9667
# byClass: Sensitivity, Specificity, etc.
# two-class metrics
cm$byClass[, c("Sensitivity","Specificity","Precision","Recall","F1")]
# ROC and AUC
library(pROC)
roc_curve <- roc(test$Species == "versicolor", prob$versicolor)
auc(roc_curve)
plot(roc_curve)
# multi-class AUC
library(pRoc)
multiclass.roc(test$Species, prob)
# precision-recall curve (better for imbalanced data)
library(PRROC)
pr <- pr.curve(scores.class0 = prob$versicolor,
weights.class0 = test$Species == "versicolor",
curve = TRUE)
plot(pr)
# custom metrics in trainControl
twoClassSummary # built-in for 2-class
mnLogLoss # multi-class log loss特征选择与解释
特征选择提高模型性能和可解释性。RFE(递归特征消除)包装模型并迭代移除最不重要的特征。varImp 从任何 caret 训练的模型(随机森林、gbm 等)提取重要性。过滤方法(findCorrelation、findLinearCombos)在训练前移除冗余特征。对于黑盒解释,SHAP 值(fastshap、shapviz)将预测归因于特征。始终在交叉验证内进行特征选择以避免选择偏差。具有较少特征的更简单模型通常泛化更好。
library(caret)
# recursive feature elimination (RFE)
ctrl <- rfeControl(functions = rfFuncs, method = "cv", number = 10)
rfe_fit <- rfe(train[,-5], train$Species,
sizes = c(1:4), rfeControl = ctrl)
print(rfe_fit) # optimal subset
predictors(rfe_fit) # selected features
plot(rfe_fit)
# variable importance from trained model
varImp(rf_fit) # caret extracts importance
plot(varImp(rf_fit))
# filter methods (before training)
# remove highly correlated features
cor_matrix <- cor(train[,-5])
high_cor <- findCorrelation(cor_matrix, cutoff = 0.9)
train_filtered <- train[,-high_cor]
# remove linear dependencies
findLinearCombos(train[,-5])
# genetic algorithm / simulated annealing selection
# gafsFit <- gafs(x, y, iters = 20)
# safsFit <- safs(x, y, iters = 20)
# SHAP values (model-agnostic interpretation)
# library(fastshap)
# explain(rf_fit, X = test, nsim = 100)集成与堆叠
集成组合多个模型以获得比任何单个模型更好的性能。caretEnsemble 用相同的重采样训练模型(公平堆叠所必需)。caretStack 在基础预测上训练元模型——元模型学习何时信任每个基础模型。对于相似准确度的模型,简单平均效果出奇地好。加权集成让你强调更好的模型。Bagging(treebag)通过平均自助模型减少方差。基础模型的多样性比其各自准确度更重要——组合犯不同错误的模型。
library(caretEnsemble)
# train multiple models with same resampling
ctrl <- trainControl(method = "cv", number = 5,
savePredictions = "final",
classProbs = TRUE)
models <- caretList(
Species ~ ., data = train,
trControl = ctrl,
methodList = c("rf","gbm","svmRadial","knn")
)
# simple ensemble (average predictions)
ens <- caretEnsemble(models)
summary(ens)
plot(ens)
# stack: train a meta-model on base predictions
stack <- caretStack(models, method = "glm",
metric = "Accuracy", trControl = ctrl)
print(stack)
# predict with ensemble
pred_ens <- predict(ens, test)
pred_stack <- predict(stack, test)
# weighted ensemble (custom weights)
weights <- c(0.4, 0.3, 0.2, 0.1)
probs <- lapply(models, function(m) predict(m, test, type = "prob"))
final_prob <- Reduce('+', Map(function(p, w) p * w, probs, weights))
# bagging (bootstrap aggregating)
bag_fit <- train(Species ~ ., data = train, method = "treebag",
trControl = ctrl)Apply 家族与性能
apply、lapply、sapply、vapply
apply 家族是基础 R 的函数式编程工具包。apply 作用于数组(用 margin 1 为行,2 为列),但内置的 rowSums/colSums 更快。lapply 始终返回列表;sapply 尝试简化为向量(方便但类型不稳定)。vapply 是安全版本——你指定输出模板,因此不匹配时报错而不是静默强制转换。在生产代码中使用 vapply,交互式使用 sapply。replicate 对模拟很方便。mapply(或 Map)并行遍历多个参数。对于现代代码,为一致性优先使用 purrr 的 map 家族。
# apply: over array margins (rows or columns)
m <- matrix(1:12, 3, 4)
apply(m, 1, sum) # row sums: 22 26 30
apply(m, 2, mean) # column means
apply(m, c(1,2), sqrt) # element-wise (silly but valid)
# built-in row/col functions are faster
rowSums(m); rowMeans(m)
colSums(m); colMeans(m)
# lapply: list in, list out
lapply(list(a=1:3, b=4:6), mean) # list(a=2, b=5)
lapply(mtcars, class) # class of each column
# sapply: list in, vector out (simplifies)
sapply(mtcars, mean) # named numeric vector
sapply(mtcars, is.numeric) # logical vector
# vapply: type-safe sapply (specify output template)
vapply(mtcars, mean, numeric(1)) # always numeric(1)
vapply(mtcars, class, character(1)) # always character(1)
# replicate: repeat expression n times
replicate(5, rnorm(3)) # 3x5 matrix
replicate(100, mean(rexp(30))) # 100 sample means
# mapply: multivariate map
mapply(rep, 1:3, 3:1) # list(1,1,1, 2,2, 3)
mapply(function(a,b) a+b, 1:3, 4:6) # 5 7 9向量化与速度
向量化是 R 头号性能优化。R 的算术、比较和数学函数通过优化的 C 代码作用于整个向量——R 中的循环是解释的且慢。ifelse 是向量化的但仍有开销;直接逻辑索引(x * (x > 5))最快。避免在数据框上使用 apply(它会强制转换为矩阵);改用向量化列操作。始终预分配结果向量——用 c() 增长它们是 O(n^2)。对于真正的热循环,Rcpp 让你内联编写 C++。用 microbenchmark 微基准测试以验证改进;system.time 太粗糙。
# BAD: element-wise loop
x <- 1:1e6
y <- numeric(length(x))
for (i in seq_along(x)) y[i] <- x[i]^2
# GOOD: vectorized
y <- x^2 # ~100x faster
# ifelse vs vectorized
# BAD
y <- numeric(length(x))
for (i in seq_along(x)) {
y[i] <- if (x[i] > 5) x[i] else 0
}
# GOOD
y <- ifelse(x > 5, x, 0)
# BEST (fastest)
y <- x * (x > 5)
# row-wise operations (avoid if possible)
df <- data.frame(a = 1:1000, b = 1001:2000)
# BAD
df$sum <- apply(df, 1, sum)
# GOOD
df$sum <- df$a + df$b
# preallocate!
n <- 1000
result <- numeric(n) # not result <- c()
for (i in 1:n) result[i] <- i^2
# use Rcpp for hot loops
# library(Rcpp)
# cppFunction('double sumc(NumericVector x) { return sum(x); }')
# benchmark
library(microbenchmark)
microbenchmark(
loop = {y <- numeric(length(x)); for(i in seq_along(x)) y[i] <- x[i]^2},
vectorized = x^2,
times = 10
)内存与 data.table
data.table 对于大数据(1M+ 行)比 data.frame/dplyr 快得多且更节省内存。dt[i, j, by] 语法在一个表达式中组合筛选、选择和分组。引用语义(:=)原地修改而不复制——对内存至关重要。setkey 创建启用二分搜索查找和快速合并的索引。fread/fwrite 比 read.csv/write.csv 快 5-10 倍。对于适合内存的数据,data.table 通常甚至胜过 Spark。权衡是学习曲线更陡峭,语法与 dplyr 相比可读性较差。
# data.table: faster, memory-efficient alternative to data.frame
library(data.table)
dt <- data.table(mtcars)
# syntax: dt[i, j, by]
dt[mpg > 20, .(mean_hp = mean(hp)), by = .(cyl, gear)]
# cyl gear mean_hp
# 1: 4 4 76.0
# 2: 4 5 102.0
# reference semantics (modify in place, no copy)
dt[, mpg_dbl := mpg * 2] # add column in place
dt[1:5, mpg := 0] # modify subset in place
dt[, c("a","b") := .(mpg*2, hp/10)] # multiple columns
# setkey for fast lookups and joins
setkey(dt, cyl)
dt[.(4)] # all rows where cyl == 4 (binary search)
dt[.(4), mean(mpg)] # fast aggregation
# joins
dt1 <- data.table(id = 1:3, x = letters[1:3])
dt2 <- data.table(id = 2:4, y = LETTERS[2:4])
setkey(dt1, id); setkey(dt2, id)
dt1[dt2] # right join
dt2[dt1] # left join
merge(dt1, dt2, by = "id") # inner join
# fread/fwrite: fast I/O
big <- fread("huge.csv") # ~10x faster than read.csv
fwrite(big, "out.csv")并行计算
R 默认是单线程的,但并行化很简单。parallel 包(内置)提供 mclapply(fork,仅 Linux/Mac)和 parLapply(集群,所有平台)。foreach + doParallel 是流行的替代方案。future 生态系统(带 furrr)是现代且统一的——通过更改 plan() 切换后端。对于 caret,设置 allowParallel = TRUE 并注册后端。始终在工作进程上导出所需变量并加载包。并行化有开销——只有当每个任务实质性(>100ms)时才有帮助。基准测试以验证加速;阿姆达尔定律限制收益。
# parallel package (built-in)
library(parallel)
ncores <- detectCores() - 1
# parallel lapply
cl <- makeCluster(ncores)
results <- parLapply(cl, 1:100, function(i) slow_function(i))
stopCluster(cl)
# foreach with doParallel
library(foreach); library(doParallel)
registerDoParallel(ncores)
results <- foreach(i = 1:100, .combine = "c") %dopar% {
slow_function(i)
}
stopImplicitCluster()
# future ecosystem (modern, flexible)
library(future); library(furrr)
plan(multisession, workers = ncores) # or multicore (Linux/Mac)
results <- future_map(1:100, slow_function)
# parallel caret
library(caret)
ctrl <- trainControl(method = "cv", number = 10, allowParallel = TRUE)
fit <- train(y ~ ., data = train, method = "rf", trControl = ctrl)
# benchmark
library(microbenchmark)
microbenchmark(
serial = lapply(1:100, slow_function),
parallel = parLapply(makeCluster(4), 1:100, slow_function),
times = 5
)
# export variables to workers
clusterExport(cl, c("my_data", "my_function"))
clusterEvalQ(cl, library(dplyr))性能分析与优化工作流程
优化前分析——对瓶颈的直觉通常是错的。profvis 提供交互式火焰图,显示每行和调用的时间。Rprof 是基础 R 的等价物。Rprofmem 跟踪分配。object.size 衡量内存;gc() 强制垃圾收集并报告使用情况。优化层次:(1) 向量化,(2) 预分配,(3) 切换到 data.table,(4) 对不可约循环使用 Rcpp,(5) 并行化。memoise 缓存函数结果——对于以相同参数重复调用的昂贵纯函数很好。始终在前后测量以确认改进。
# profile to find bottlenecks
library(profvis)
profvis({
result <- my_analysis(big_data)
})
# opens interactive flame graph
# Rprof (base R)
Rprof("profile.out")
my_analysis(big_data)
Rprof(NULL)
summaryRprof("profile.out")
# memory profiling
Rprofmem("mem.out")
my_analysis(big_data)
Rprofmem(NULL)
# object sizes
object.size(my_data) # bytes
format(object.size(my_data), "MB")
# find memory hogs
sort(sapply(ls(), function(x) object.size(get(x))))
# garbage collection
gc() # force collection, show memory
gcinfo(TRUE) # report on auto GC
# common optimizations:
# 1. Vectorize (avoid loops)
# 2. Preallocate
# 3. Use data.table instead of data.frame
# 4. Use Rcpp for hot loops
# 5. Avoid growing objects (c(), rbind())
# 6. Use appropriate data types (integer vs double)
# 7. Cache expensive computations (memoise)
library(memoise)
slow_cached <- memoise(slow_function)dplyr 深入
核心动词
dplyr 的五个核心动词:filter(按条件筛选行)、select(列)、mutate(新列)、arrange(排序)、summarize(聚合)。用 %>% 链接。group_by + summarize 是聚合的主力。na.rm = TRUE 至关重要——否则数据中的任何 NA 都会使汇总变为 NA。
library(dplyr)
starwars %>%
filter(species == "Human", height > 170) %>%
select(name, height, mass, homeworld) %>%
mutate(bmi = mass / (height/100)^2) %>%
arrange(desc(height)) %>%
slice_head(n = 5)
# group_by + summarize
starwars %>%
group_by(species) %>%
summarize(
n = n(),
avg_height = mean(height, na.rm = TRUE),
avg_mass = mean(mass, na.rm = TRUE)
) %>%
arrange(desc(n)) %>%
filter(n >= 2)连接
变异连接组合列;筛选连接子集化行。left_join 是最常见的——保留 x 的所有行,用 NA 填充非匹配。始终检查右表中的重复键(导致行乘法)。semi_join/anti_join 非常适合基于另一个表进行筛选而不引入其列。
library(dplyr)
# mutating joins (add columns from y to x)
left_join(x, y, by = "id") # all rows in x
right_join(x, y, by = "id") # all rows in y
inner_join(x, y, by = "id") # only matching rows
full_join(x, y, by = "id") # all rows from both
# different column names
left_join(x, y, by = c("id" = "user_id"))
# multiple keys
left_join(x, y, by = c("first", "last"))
# filtering joins (filter x, no columns added)
semi_join(x, y, by = "id") # rows in x that match y
anti_join(x, y, by = "id") # rows in x that DON'T match y
# check for duplicates
x %>% count(id) %>% filter(n > 1)窗口函数
窗口函数跨与当前行相关的行计算值。lag/lead 访问前/后行——对时间序列至关重要。cumsum/cummean 是累积聚合。slice_max/slice_min 是每组 top-n 的简写。始终先 group_by 以在组内计算。
library(dplyr)
# row numbering and ranking
df %>% group_by(group) %>%
mutate(
row_num = row_number(),
rank = rank(value),
dense_rank = dense_rank(value),
min_rank = min_rank(value)
)
# offsets
df %>% group_by(group) %>%
mutate(
prev = lag(value),
next = lead(value, 2),
diff = value - lag(value)
)
# cumulative
df %>% group_by(group) %>%
mutate(
cum_sum = cumsum(value),
cum_mean = cummean(value),
cum_max = cummax(value),
cum_min = cummin(value)
)
# top n per group
df %>% group_by(group) %>%
slice_max(value, n = 3) # top 3
df %>% group_by(group) %>%
slice_min(value, n = 2)across 与多列
across(dplyr 1.0+)替代旧的 _at、_if、_all 后缀。使用 where(is.numeric) 按谓词选择列。.x 代词在 lambda(~)内指当前列。rename_with 使用函数重命名列。across 是操作多列的现代、一致方式。
library(dplyr)
# apply function to multiple columns
starwars %>%
mutate(across(where(is.numeric), ~ replace_na(.x, 0)))
# summarize multiple columns
starwars %>%
summarize(across(where(is.numeric), mean, na.rm = TRUE))
# rename multiple columns
starwars %>%
rename_with(tolower, everything())
# transform specific columns
df %>%
mutate(across(c(height, mass), ~ .x * 0.453592)) # lb to kg
# multiple functions
df %>%
summarize(across(value, list(mean = mean, sd = sd, n = ~ n()), na.rm = TRUE))
# conditional transformation
df %>%
mutate(across(where(is.character), tolower))Summarize 模式
summarize 将组减少为单个值。n() 计数行;n_distinct() 计数唯一值。始终向统计函数传递 na.rm = TRUE,否则 NA 会传播。across + list 让你一次计算多个统计量。count() 是 group_by + summarize(n = n()) + ungroup 的简写。
library(dplyr)
# basic
df %>%
group_by(category) %>%
summarize(
count = n(),
distinct = n_distinct(id),
total = sum(value, na.rm = TRUE),
avg = mean(value, na.rm = TRUE),
median = median(value, na.rm = TRUE),
sd = sd(value, na.rm = TRUE),
min = min(value, na.rm = TRUE),
max = max(value, na.rm = TRUE),
first = first(value),
last = last(value),
q25 = quantile(value, 0.25, na.rm = TRUE)
)
# multiple summary stats at once
df %>%
group_by(category) %>%
summarize(
across(value, list(mean = mean, sd = sd, n = ~ n()), na.rm = TRUE)
)
# count and proportions
df %>%
count(category) %>%
mutate(pct = n / sum(n))ggplot2 高级
层与美学
ggplot 用 + 连接层构建图形。aes() 将数据列映射到视觉属性。geom_* 定义几何;scale_* 控制轴/颜色;labs 标记一切;theme_* 设置非数据元素样式。facet_wrap 按一个变量拆分;facet_grid 制作两个变量的 2D 网格。
library(ggplot2)
ggplot(mpg, aes(displ, hwy, color = class)) +
geom_point(size = 2, alpha = 0.7) +
geom_smooth(method = "lm", se = TRUE) +
scale_color_brewer(palette = "Set2") +
labs(
title = "Fuel efficiency by engine size",
subtitle = "Source: EPA mpg dataset",
x = "Engine displacement (L)",
y = "Highway MPG",
color = "Vehicle class"
) +
theme_minimal(base_size = 12) +
theme(legend.position = "bottom")
# facets
ggplot(mpg, aes(displ, hwy)) +
geom_point() +
facet_wrap(~ class, ncol = 4)
ggplot(mpg, aes(displ, hwy)) +
geom_point() +
facet_grid(drv ~ cyl)比例与坐标
scale_* 函数控制数据如何映射到视觉属性。scale_x_log10 对数变换;scale_color_viridis_c 给出感知均匀的色图。coord_cartesian 放大而不丢弃数据(与 xlim 不同)。coord_flip 交换轴。coord_polar 将条形转为饼图切片。scale_x_date 格式化日期轴。
ggplot(mpg, aes(displ, hwy, color = cty)) +
geom_point() +
scale_x_log10() +
scale_y_continuous(limits = c(10, 50), breaks = seq(10, 50, 5)) +
scale_color_viridis_c(option = "plasma") +
coord_cartesian(xlim = c(1, 7)) # zoom without removing data
# coord flip
ggplot(mpg, aes(class, hwy)) +
geom_boxplot() +
coord_flip()
# coord polar (pie chart from bar)
ggplot(mtcars, aes(factor(1), fill = factor(cyl))) +
geom_bar(width = 1) +
coord_polar(theta = "y")
# date axis
ggplot(economics, aes(date, unemploy)) +
geom_line() +
scale_x_date(date_labels = "%Y", date_breaks = "5 years")主题与自定义
theme() 控制每个非数据元素。element_text/rect/line/blank 是构建块。常见调整:旋转 x 轴标签(angle、hjust)、粗体标题、隐藏次要网格(panel.grid.minor = element_blank())。ggsave 导出到 PNG/PDF/SVG——为光栅格式指定英寸尺寸和 dpi。
library(ggplot2)
p <- ggplot(mpg, aes(class, hwy)) + geom_boxplot()
# built-in themes
p + theme_minimal()
p + theme_classic()
p + theme_bw()
# custom theme
p + theme(
panel.background = element_rect(fill = "white"),
panel.grid.major = element_line(color = "gray90"),
panel.grid.minor = element_blank(),
axis.text.x = element_text(angle = 45, hjust = 1, size = 10),
axis.title = element_text(face = "bold"),
plot.title = element_text(size = 16, hjust = 0.5),
plot.subtitle = element_text(color = "gray40"),
legend.position = "right",
legend.key = element_blank(),
strip.background = element_rect(fill = "lightblue"),
strip.text = element_text(face = "bold")
)
# save
ggsave("plot.png", width = 8, height = 6, dpi = 300)
ggsave("plot.pdf", device = cairo_pdf)统计与平滑
stat_summary 计算自定义汇总(mean、median、mean_se、mean_cl_normal)。geom_smooth 添加趋势线——method='lm' 为线性,'loess' 为局部回归,'gam' 为广义加性模型。geom_density/geom_density_2d 显示分布。geom_violin 在箱线图旁显示完整分布形状。
library(ggplot2)
# stat_summary for custom summaries
ggplot(mtcars, aes(factor(cyl), mpg)) +
stat_summary(fun = "mean", geom = "point", size = 3) +
stat_summary(fun.data = "mean_se", geom = "errorbar")
# smooth
ggplot(mtcars, aes(wt, mpg)) +
geom_point() +
geom_smooth(method = "lm", formula = y ~ x, se = TRUE) +
geom_smooth(method = "loess", se = FALSE, color = "red")
# density
ggplot(iris, aes(Sepal.Length, fill = Species)) +
geom_density(alpha = 0.5)
# 2d density
ggplot(diamonds, aes(carat, price)) +
geom_density_2d() +
scale_x_log10() + scale_y_log10()
# histograms with binning
ggplot(diamonds, aes(price)) +
geom_histogram(bins = 50, fill = "steelblue") +
scale_x_log10()
# boxplot and violin
ggplot(iris, aes(Species, Sepal.Length)) +
geom_boxplot() +
geom_violin(alpha = 0.3, fill = "orange")扩展与 patchwork
patchwork 用 +(并排)、/(堆叠)和 plot_layout 组合多个图形以进行精细控制。plot_annotation 添加整体标题和标签(A、B、C...)。ggplot2 扩展生态系统庞大:ggrepel 用于标签,ggridges 用于山脊图,gganimate 用于动画,ggiraph 用于交互性,geom_sf 用于地图。
library(ggplot2)
library(patchwork)
p1 <- ggplot(mtcars, aes(wt, mpg)) + geom_point()
p2 <- ggplot(mtcars, aes(factor(cyl))) + geom_bar()
p3 <- ggplot(mtcars, aes(mpg)) + geom_histogram(bins = 10)
p4 <- ggplot(mtcars, aes(factor(cyl), mpg)) + geom_boxplot()
# combine plots
p1 + p2 # side by side
p1 / p2 # stacked
(p1 + p2) / p3 # grouped
p1 + p2 + p3 + plot_layout(nrow = 2, byrow = FALSE)
# annotations
p1 + p2 + plot_annotation(
title = "MT cars analysis",
tag_levels = "A"
)
# other extensions:
# ggrepel: non-overlapping labels
# ggridges: joy plots
# gganimate: animated plots
# ggiraph: interactive
# ggplot2::geom_sf: maps
library(ggrepel)
ggplot(mtcars, aes(wt, mpg, label = rownames(mtcars))) +
geom_point() +
geom_text_repel()tidyr 数据整理
Pivot longer 与 wider
pivot_longer/pivot_wider(替代 gather/spread)重塑数据。长格式最适合 ggplot 和 dplyr 聚合;宽格式更适合人类阅读。带 .value 的 names_pattern 让你根据列名结构拆分为多列。始终显式指定 cols、names_to 和 values_to。
library(tidyr)
# wide to long
# id q1 q2 q3
# A 10 20 30
wide_data %>%
pivot_longer(
cols = starts_with("q"),
names_to = "quarter",
values_to = "score"
)
# id quarter score
# A q1 10
# A q2 20
# A q3 30
# long to wide
long_data %>%
pivot_wider(
names_from = quarter,
values_from = score
)
# multiple value columns
df %>%
pivot_longer(
cols = c(starts_with("q"), starts_with("r")),
names_to = c(".value", "quarter"),
names_pattern = "([a-z])([0-9])"
)Separate 与 unite
separate 将一列拆分为多列;unite 将多列合并为一列。separate_rows 拆分为多行(对标签列表有用)。extract 使用正则表达式捕获组。所有都接受 sep 参数(默认为非字母数字)。在 separate 中用 convert = TRUE 自动转换类型。
library(tidyr)
# split a column
df <- tibble(x = c("a_1", "b_2", "c_3"))
df %>% separate(x, c("letter", "number"), sep = "_")
# letter number
# a 1
# b 2
# split into rows
df %>% separate_rows(x, sep = "_")
# x
# a
# 1
# b
# 2
# unite columns
df %>%
separate(x, c("letter", "number")) %>%
unite("combined", letter, number, sep = "-")
# combined
# a-1
# b-2
# extract with regex
df %>% extract(x, c("letter", "number"), "([a-z])_([0-9])")缺失值
replace_na 用常量填充 NA;fill 传播值(对时间序列很好);drop_na 移除不完整的行;coalesce 跨列选择第一个非 NA。complete 扩展到指定列的所有组合(类似笛卡尔积)——用于确保缺失组出现在汇总中。
library(tidyr)
# replace NA
df %>% replace_na(list(value = 0, name = "unknown"))
# fill NA with previous/next value
df %>% fill(value, .direction = "down") # forward fill
df %>% fill(value, .direction = "up") # backward fill
df %>% fill(value, .direction = "downup") # down then up
# drop rows with NA
df %>% drop_na()
df %>% drop_na(value) # only specific columns
# detect NA
df %>% filter(!is.na(value))
sum(is.na(df$value))
df %>% mutate_all(~ sum(is.na(.))) # NA count per column
# coalesce: first non-NA
df %>% mutate(value = coalesce(value, fallback, 0))
# complete: expand combinations
df %>% complete(group, year) # all group-year combos, NA filled
df %>% complete(group, year, fill = list(value = 0))嵌套与列表列
nest 将分组行打包成列表列——使用 purrr 进行拆分-应用-组合的基础。unnest 反转它。unnest_wider 将列表元素展开为列;unnest_longer 将它们展开为行。列表列让你在 tibble 内保存任意对象(模型、数据框、向量)。
library(tidyr)
library(dplyr)
# nest: pack rows into list columns
nested <- mtcars %>%
nest(data = -cyl)
# cyl data
# 4 <tibble 11x10>
# 6 <tibble 7x10>
# 8 <tibble 14x10>
# operate on each group
nested %>%
mutate(model = map(data, ~ lm(mpg ~ wt, data = .x)))
# unnest
df %>%
unnest(data)
# unnest specific column
df %>%
unnest_wider(data) # list-col of named lists -> cols
df %>%
unnest_longer(data) # list-col of vectors -> rows
# chop (similar to nest)
df %>% chop(value)
# pack (opposite of unnest)
df %>% pack(x = c(a, b))Tibble 与读取数据
Tibble 是改进的数据框:无字符串到因子转换、无行名处理、更好的打印。read_csv 比 read.csv 快得多并返回 tibble。指定 col_types 以避免意外(例如,ID 被读取为数值)。na 参数让你将多个字符串视为 NA。对于表格数据,始终使用 readr 而非基础 R。
library(tibble)
library(readr)
# create tibble
tibble(
x = 1:5,
y = c("a", "b", "c", "d", "e"),
z = runif(5)
)
# tribble: row-wise construction
tribble(
~name, ~age, ~score,
"Alice", 30, 90,
"Bob", 25, 80
)
# read csv
df <- read_csv("data.csv", col_names = TRUE, col_types = "cdd")
df <- read_csv("data.csv", na = c("", "NA", "N/A"))
df <- read_tsv("data.tsv")
df <- read_delim("data.txt", delim = "|")
# write
write_csv(df, "out.csv")
write_tsv(df, "out.tsv")
# read from clipboard (Excel)
df <- read_clipboard()
# column types
# c = character, d = double, i = integer, l = logical, f = factor, D = date统计检验
t 检验
t 检验比较均值。单样本(与一个值比较)、双样本(组间)、配对(受试者内)。默认是 Welch(不等方差)——通常是你想要的。始终检查假设:正态性(Shapiro)和等方差(var.test)。报告效应量(Cohen's d),而不仅仅是 p 值。
# one-sample t-test
t.test(x, mu = 5)
# H0: mean of x equals 5
# two-sample t-test
t.test(x, y)
t.test(x, y, var.equal = TRUE) # Student's (assume equal var)
t.test(x, y, alternative = "greater")
# paired t-test
t.test(before, after, paired = TRUE)
# output interpretation
result <- t.test(x, y)
result$p.value # < 0.05 -> reject H0
result$conf.int # 95% CI of difference
result$statistic # t value
# check assumptions
shapiro.test(x) # normality
var.test(x, y) # equal variances
# effect size (effsize package)
library(effsize)
cohen.d(x, y)ANOVA
ANOVA 检验 3+ 组之间的差异。使用 * 进行带交互的因子设计。显著 ANOVA 后始终做事后检验(TukeyHSD)以找出哪些对不同。检查方差齐性(Levene)。对于非正态数据,使用 Kruskal-Wallis。对于重复测量,使用 lme4 中的 lmer。
# one-way ANOVA
result <- aov(yield ~ fertilizer, data = df)
summary(result)
# two-way ANOVA with interaction
result <- aov(yield ~ fertilizer + density + fertilizer:density, data = df)
result <- aov(yield ~ fertilizer * density, data = df) # shorthand
# Tukey post-hoc
TukeyHSD(result)
# check assumptions
plot(result) # diagnostic plots
library(car)
leveneTest(yield ~ fertilizer, data = df) # equal variances
# Kruskal-Wallis (non-parametric alternative)
kruskal.test(yield ~ fertilizer, data = df)
# repeated measures
result <- aov(score ~ time + Error(subject/time), data = df)
# mixed effects (lme4)
library(lme4)
model <- lmer(score ~ time + (1 | subject), data = df)
anova(model)卡方与分类
卡方检验两个分类变量是否独立。每个单元格的期望频数应 >= 5;如果不是,使用 Fisher 精确检验。McNemar 用于配对二分类数据(之前/之后)。Pearson 残差显示哪些单元格偏离期望最多。assocstats 给出 Cramer's V 作为效应量。
# chi-square test of independence
tbl <- table(df$gender, df$vote)
chisq.test(tbl)
# goodness of fit
chisq.test(table(df$color), p = c(0.25, 0.25, 0.25, 0.25))
# Fisher's exact (small samples)
fisher.test(tbl)
# McNemar (paired binary)
mcnemar.test(tbl)
# expected frequencies
ct <- chisq.test(tbl)
ct$expected
ct$observed
ct$residuals # Pearson residuals
# association measures
library(vcd)
assocstats(tbl)
# visualize
library(ggplot2)
ggplot(df, aes(gender, fill = vote)) +
geom_bar(position = "fill")相关
Pearson 衡量线性相关;Spearman/Kendall 衡量单调(基于等级)——对异常值和非线性鲁棒。cor.test 给出 p 值和 CI。corrplot 可视化矩阵;ggpairs 显示散点图和相关。使用 use='pairwise.complete.obs' 处理缺失数据而不丢弃整行。
# Pearson (linear, normal)
cor(x, y, method = "pearson")
cor.test(x, y, method = "pearson")
# Spearman (rank, non-parametric)
cor(x, y, method = "spearman")
# Kendall (rank, smaller samples)
cor(x, y, method = "kendall")
# correlation matrix
cor(mtcars[, c("mpg", "wt", "hp", "disp")])
# with p-values
library(psych)
corr.test(mtcars[, 1:6])
# visualize
library(corrplot)
M <- cor(mtcars)
corrplot(M, method = "circle", type = "lower", order = "hclust")
# GGally for scatterplot matrix
library(GGally)
ggpairs(mtcars[, c("mpg", "wt", "hp", "cyl")])
# handle missing
cor(df, use = "complete.obs") # listwise deletion
cor(df, use = "pairwise.complete.obs")非参数检验
非参数检验不假设正态性——当样本小、数据偏斜或有有序数据时使用它们。Mann-Whitney/Wilcoxon 是 t 检验的基于等级的对应物。置换检验和自助法是计算机密集型的但做出最少的假设。始终在 p 值旁报告效应量。
# Mann-Whitney U (alternative to two-sample t)
wilcox.test(x, y)
wilcox.test(x, y, paired = FALSE)
# Wilcoxon signed-rank (alternative to paired t)
wilcox.test(before, after, paired = TRUE)
# Kruskal-Wallis (alternative to one-way ANOVA)
kruskal.test(y ~ group, data = df)
# Friedman (alternative to repeated measures ANOVA)
friedman.test(y ~ group | subject, data = df)
# permutation test
library(coin)
oneway_test(y ~ group, data = df, distribution = approximate(nresample = 9999))
# bootstrap CI
library(boot)
boot_mean <- function(data, idx) mean(data[idx])
b <- boot(x, boot_mean, R = 10000)
boot.ci(b, type = "perc")
# sign test
library(BSDA)
SIGN.test(x, md = 5) # median different from 5?回归分析
线性回归
lm 拟合线性模型。summary 显示系数、标准误、t 值、p 值、R² 和 F 统计量。始终检查诊断:残差图用于线性/方差齐性、VIF 用于多重共线性(>5 令人担忧)。I() 保护公式中的算术;poly(x, 2) 给出正交多项式。
# simple linear
model <- lm(mpg ~ wt, data = mtcars)
summary(model)
coef(model)
confint(model)
fitted(model)
residuals(model)
predict(model, newdata = new_df, interval = "confidence")
# multiple regression
model <- lm(mpg ~ wt + hp + cyl, data = mtcars)
# interactions
model <- lm(mpg ~ wt * hp, data = mtcars)
model <- lm(mpg ~ wt + hp + wt:hp, data = mtcars)
# transformations
model <- lm(log(mpg) ~ wt, data = mtcars)
model <- lm(mpg ~ poly(wt, 2), data = mtcars) # quadratic
model <- lm(mpg ~ I(wt^2) + wt, data = mtcars)
# categorical predictors
model <- lm(mpg ~ factor(cyl), data = mtcars)
# diagnostics
plot(model) # 4 diagnostic plots
library(car)
vif(model) # variance inflation factor广义线性模型
glm 将 lm 扩展到非正态响应。family=binomial 用于逻辑回归(二分类结果);family=poisson 用于计数。对于逻辑回归,exp(coef) 给出优势比。用 anova(..., test='Chisq') 比较嵌套模型。对于过分散计数(方差 > 均值),使用 glm.nb(负二项式)而非 Poisson。
# logistic regression (binary)
model <- glm(am ~ wt + hp, data = mtcars, family = binomial)
summary(model)
# predicted probabilities
predict(model, type = "response")
# odds ratios
exp(coef(model))
# Poisson regression (counts)
model <- glm(count ~ group, data = df, family = poisson)
# negative binomial (overdispersed counts)
library(MASS)
model <- glm.nb(count ~ group, data = df)
# compare models
model1 <- glm(y ~ x1, family = binomial, data = df)
model2 <- glm(y ~ x1 + x2, family = binomial, data = df)
anova(model1, model2, test = "Chisq")
# goodness of fit
1 - pchisq(model$deviance, model$df.residual)
# McFadden pseudo R²
1 - model$deviance / model$null.deviance模型选择
逐步选择简单但有偏——优先使用全子集(regsubsets)或正则化(glmnet)。AIC/BIC 平衡拟合和复杂度(越低越好)。交叉验证给出诚实的样本外估计。glmnet 中 alpha=0 是岭回归,alpha=1 是 lasso(可将系数归零——特征选择)。
# stepwise selection
full <- lm(mpg ~ ., data = mtcars)
step(full, direction = "both")
step(full, direction = "backward")
step(full, direction = "forward", scope = list(lower = ~1, upper = full))
# AIC and BIC
AIC(model1, model2)
BIC(model1, model2)
# all subsets
library(leaps)
leaps <- regsubsets(mpg ~ ., data = mtcars, nvmax = 10)
plot(leaps, scale = "adjr2")
plot(leaps, scale = "Cp")
# cross-validation
library(caret)
train(mpg ~ ., data = mtcars, method = "lm",
trControl = trainControl(method = "cv", number = 10))
# regularized (glmnet)
library(glmnet)
X <- model.matrix(mpg ~ ., mtcars)[, -1]
y <- mtcars$mpg
cv_model <- cv.glmnet(X, y, alpha = 0) # ridge
cv_model <- cv.glmnet(X, y, alpha = 1) # lasso混合效应模型
混合效应模型处理相关数据(重复测量、聚类样本)。(1 | subject) 是每个受试者的随机截距;(time | subject) 添加随机斜率。嵌套组用 /。交叉组用 +。lmer 用于连续响应,glmer 用于非连续响应。使用 lmerTest 获取 p 值(lme4 默认不计算它们)。
library(lme4)
library(lmerTest)
# random intercept
model <- lmer(score ~ treatment + (1 | subject), data = df)
# random intercept and slope
model <- lmer(score ~ time + (time | subject), data = df)
# nested random effects
model <- lmer(score ~ 1 + (1 | school/class), data = df)
# crossed random effects
model <- lmer(score ~ 1 + (1 | subject) + (1 | item), data = df)
# generalized mixed model
glmer(outcome ~ treatment + (1 | subject), data = df, family = binomial)
# summary and CIs
summary(model)
confint(model, method = "Wald")
confint(model, method = "boot")
# anova
anova(model)
# random effects
ranef(model)
VarCorr(model)
# predict
predict(model, newdata = df, allow.new.levels = TRUE)诊断与预测
始终检查诊断:残差图揭示非线性和异方差性;Cook 距离(>4/n)标记强影响点;hatvalues(>2p/n)标记高杠杆点。predict 用 interval='confidence' 表示均值,'prediction' 表示单个值(更宽)。ggeffects 计算边际效应用于可视化。
model <- lm(mpg ~ wt + hp, data = mtcars)
# diagnostic plots
par(mfrow = c(2, 2))
plot(model)
par(mfrow = c(1, 1))
# residuals vs leverage
plot(model, which = 5)
# influence measures
influence.measures(model)
hatvalues(model) # leverage
cooks.distance(model) # Cook's distance
dfbeta(model) # change in coef per obs
# identify outliers
which(cooks.distance(model) > 4 / nrow(mtcars))
# predictions
new_data <- data.frame(wt = c(2, 3, 4), hp = c(100, 150, 200))
predict(model, newdata = new_data, interval = "confidence")
predict(model, newdata = new_data, interval = "prediction")
# marginal effects
library(ggeffects)
ggeffect(model, terms = "wt")
plot(ggeffect(model, terms = c("wt", "hp")))
# R squared variants
library(rsq)
rsq(model, type = "v") # variance-based
rsq(model, type = "kl") # Kullback-LeiblerR Markdown 与报告
R Markdown 基础
R Markdown 结合叙述(Markdown)、代码(R 代码块)和输出(表格/图表)。YAML 头部设置元数据和输出格式。代码块选项:echo=FALSE 隐藏代码,include=FALSE 运行但隐藏所有内容,fig.cap 添加标题。用反引号的内联 r 代码将值插入正文。Knit(Ctrl+Shift+K)渲染。
---
title: "Quarterly Report"
author: "Data Team"
date: "2024-12-31"
output: html_document
---
## Section
Inline code: the mean is `r mean(x)`.
```{r setup, include=FALSE}
knitr::opts_chunk$set(echo = FALSE, warning = FALSE, message = FALSE)
library(dplyr)
library(ggplot2)
data <- read_csv("data.csv")
```
```{r plot, fig.cap="Sales over time"}
ggplot(data, aes(date, sales)) + geom_line()
```
```{r table}
library(knitr)
kable(head(data), caption = "First 6 rows")
```代码块选项
代码块选项控制代码执行和输出。cache=TRUE 加速重新 knit 但可能隐藏更改——设置 dependson 用于缓存失效。R Markdown 通过 knitr 引擎支持多种语言:python、bash、sql、javascript 等。对于 Python,使用 reticulate 包在 R 和 Python 之间共享对象。
```{r chunk-name, options}
# code here
```
# Common options:
# echo=FALSE hide code, show output
# eval=FALSE show code, don't run
# include=FALSE run, hide code and output
# results="hide" show code, hide text output
# warning=FALSE hide warnings
# message=FALSE hide messages
# fig.width=6 figure width (inches)
# fig.height=4 figure height
# fig.cap="..." figure caption
# fig.path="figs/" where to save figures
# cache=TRUE cache results (faster re-knits)
# dependson="..." cache dependencies
# dev="svg" output device
# global options
knitr::opts_chunk$set(
echo = TRUE,
fig.width = 6,
fig.height = 4,
fig.path = "figures/",
cache = TRUE
)
# language engines
```{python}
# Python code
```
```{bash}
# Shell commands
```
```{sql, connection=db}
# SQL queries
```输出格式
html_document 最灵活(交互式、code_folding、分页表格)。pdf_document 需要 LaTeX(通过 tinytex::install_tinytex() 安装 TinyTeX)。word_document 使用参考 docx 生成 Word 文件用于样式。对于演示文稿,使用 ioslides(内置)或 revealjs(更精致)。可以同时指定多个输出。
---
output:
html_document:
toc: true
toc_float: true
toc_depth: 3
number_sections: true
theme: flatly
highlight: tango
code_folding: hide
df_print: paged
---
---
output:
pdf_document:
toc: true
number_sections: true
fig_caption: true
latex_engine: xelatex
---
---
output:
word_document:
toc: true
reference_docx: template.docx
---
---
output:
ioslides_presentation:
widescreen: true
---
---
output:
revealjs::revealjs_presentation:
theme: solarized
transition: slide
---
# Multiple formats
---
output:
html_document: default
pdf_document: default
---用 kable 和 gt 制作表格
kable + kableExtra 生成出版质量的表格,支持分组、条件格式和样式。gt 是较新的、更图形语法风格的替代方案。DT 创建交互式 HTML 表格,支持排序、筛选和分页——非常适合 HTML 报告。根据输出格式选择(kable 适用于所有格式;DT 仅适用于 HTML)。
library(knitr)
library(kableExtra)
library(gt)
# basic kable
kable(head(mtcars))
# styled kable
kable(head(mtcars), format = "html", caption = "Top cars") %>%
kable_styling(bootstrap_options = c("striped", "hover", "condensed"),
full_width = FALSE) %>%
add_header_above(c(" " = 1, "Specs" = 3, "Performance" = 7))
# conditional formatting
kable(df) %>%
cell_spec(value, color = ifelse(value > 0, "green", "red")) %>%
row_spec(1, bold = TRUE, background = "yellow")
# gt (modern alternative)
mtcars %>%
head() %>%
gt() %>%
tab_header(title = "MT Cars", subtitle = "First 6 rows") %>%
cols_label(mpg = "MPG", cyl = "Cylinders") %>%
tab_options(table.width = pct(80))
# DT for interactive tables
library(DT)
datatable(mtcars, filter = "top", options = list(pageLength = 5))参数与自动化
params 使报告可重用——在 YAML 中定义,通过 params$<name> 访问。通过 rmarkdown::render() 用自定义参数渲染。循环参数值生成多个报告(每个地区、每个季度一个等)。这是自动化报告管道的基础。结合 cron/定时 R 用于周期性报告。
---
title: "Regional Sales Report"
output: html_document
params:
region: "West"
year: 2024
data_file: "sales.csv"
---
## Report for `r params$region` in `r params$year`
```{r}
data <- read_csv(params$data_file) %>%
filter(region == params$region, year == params$year)
```
# Render from R
rmarkdown::render("report.Rmd",
params = list(region = "East", year = 2024),
output_file = "east_2024.html")
# Render from command line
# Rscript -e 'rmarkdown::render("report.Rmd", params = list(region = "East"))'
# Loop over parameters
for (r in c("West", "East", "North")) {
rmarkdown::render("report.Rmd",
params = list(region = r),
output_file = paste0(r, "_report.html"))
}purrr 函数式编程
map 家族
map 是 tidyverse 的 lapply——始终返回列表。map_dbl/chr/int/lgl 返回类型化向量(比 map 更安全)。map2 和 pmap 并行迭代多个参数。walk 用于副作用(打印、保存)。.x 代词指当前元素;在 pmap 中,使用 ..1、..2 等。map_dfr 按行绑定数据框。
library(purrr)
# map: list output
map(1:5, ~ .x^2) # list(1, 4, 9, 16, 25)
map_dbl(1:5, ~ .x^2) # numeric vector
map_chr(1:5, ~ paste0("n", .x)) # character vector
map_int(1:5, ~ .x * 2L) # integer vector
map_lgl(1:5, ~ .x > 3) # logical vector
map_dfr(1:3, ~ data.frame(id = .x, val = .x^2)) # row-bound df
map_dfc(1:3, ~ data.frame(x = .x)) # col-bound df
# multiple arguments
map2(1:3, 4:6, ~ .x + .y)
pmap(list(1:3, 4:6, 7:9), ~ ..1 + ..2 + ..3)
# walk (for side effects)
walk(1:5, ~ print(.x))
walk(files, ~ process(.x))
walk2(plots, filenames, ~ ggsave(.y, .x))
# in pipelines
mtcars %>%
split(.$cyl) %>%
map(~ lm(mpg ~ wt, data = .x)) %>%
map(summary) %>%
map_dbl(~ .x$r.squared)Safely 与 quietly
safely 包装函数以返回 (result, error) 而非抛出异常——对于一次失败不应停止一切的批处理至关重要。possibly 返回默认值。quietly 捕获消息/警告。transpose 将成对列表翻转为列表对。insistently 带退避重试——非常适合不稳定的 API。
library(purrr)
# safely: capture errors without stopping
safe_log <- safely(log)
safe_log(10) # list(result = 2.3, error = NULL)
safe_log("a") # list(result = NULL, error = <error>)
# process many, keep going on error
results <- map(values, safely(my_function))
successes <- map(results, "result") %>% compact()
errors <- map(results, "error") %>% compact()
# quietly: capture messages/warnings
quiet_log <- quietly(log)
quiet_log(10) # list(result, output, warnings, messages)
# possibly: return default on error
safe_log <- possibly(log, otherwise = NA_real_)
map(values, safe_log) # never errors
# transpose for cleaner output
results <- map(values, safely(fun)) %>% transpose()
results$result # all results
results$error # all errors
# insistently: retry on failure
slow_fn <- insistently(api_call, rate = rate_backoff())Reduce 与 accumulate
reduce 成对组合元素(类似 foldl);accumulate 保留中间结果。用于连接多个数据框或构建累积计算。detect/find 查找第一个匹配;keep/discard 筛选。every/some 测试谓词。这些用简洁、可组合的操作替代循环。
library(purrr)
# reduce: combine pairwise
reduce(c(1, 2, 3, 4), ) # 10
reduce(c(1, 2, 3, 4), ~ .x * .y) # 24
reduce(list(df1, df2, df3), full_join, by = "id")
# accumulate: keep intermediate results
accumulate(c(1, 2, 3, 4), ) # 1 3 6 10
accumulate(c(2, 3, 4), ~ .x * .y) # 2 6 24
# right reduce
reduce(c(1, 2, 3, 4), , .dir = "backward")
# with init
reduce(c(1, 2, 3), ~ c(.x, .y), .init = numeric(0))
# detect
detect(1:10, ~ .x > 5) # 6 (first match)
detect_index(1:10, ~ .x > 5) # 6 (index)
detect(1:10, ~ .x > 5, .right = TRUE) # 10 (last match)
# keep/discard
keep(1:10, ~ .x %% 2 == 0) # 2 4 6 8 10
discard(1:10, ~ .x %% 2 == 0) # 1 3 5 7 9
every(1:10, ~ .x > 0) # TRUE
some(1:10, ~ .x > 5) # TRUE偏应用与组合
partial 预填充参数——用于从通用函数创建专用函数。compose 链接函数(从右到左)。negate 反转谓词。lift 转换函数以接受参数列表。imap 是以索引为第二个参数的 map2。这些工具使函数式组合干净且可读。
library(purrr)
# partial: pre-fill arguments
add_one <- partial(, 1)
add_one(5) # 6
round2 <- partial(round, digits = 2)
round2(3.14159) # 3.14
# compose: chain functions
clean_string <- compose(
str_trim,
str_to_lower,
~ str_replace_all(.x, "[^a-z]", " ")
)
clean_string(" Hello, World! ") # "hello world "
# %>% (magrittr pipe) vs compose
# pipe: data %>% f %>% g %>% h
# compose: g %>% f creates a new function
# negate
is_missing <- negate(is.na)
is_missing(5) # TRUE
# lift (vectorize)
sum2 <- lift() # takes a list of 2 args
sum2(list(1, 2)) # 3
# walk with index
imap(letters[1:3], ~ paste0(.y, ": ", .x))
# list("1: a", "2: b", "3: c")列表列与嵌套数据
nest + map + unnest 模式是 tidyverse 的拆分-应用-合并。列表列保存任何对象(模型、预测、子数据)。map 在数据框上迭代列。pluck 安全提取嵌套元素。modify_if 更改匹配谓词的元素,保留原始结构。
library(purrr)
library(dplyr)
library(tidyr)
# nest data, fit models, predict
mtcars %>%
group_by(cyl) %>%
nest() %>%
mutate(
model = map(data, ~ lm(mpg ~ wt, data = .x)),
rsq = map_dbl(model, ~ summary(.x)$r.squared),
pred = map2(model, data, predict)
)
# unnest predictions
mtcars %>%
group_by(cyl) %>%
nest() %>%
mutate(model = map(data, ~ lm(mpg ~ wt, .x))) %>%
mutate(pred = map2(model, data, predict)) %>%
select(cyl, data, pred) %>%
unnest(c(data, pred))
# apply function to each column
mtcars %>% map_dbl(mean)
mtcars %>% map_dbl(sd)
# apply to columns of specific type
iris %>%
map_if(is.numeric, mean) %>%
map_dbl(round, 2)
# pluck
list(a = list(b = list(c = 42))) %>% pluck("a", "b", "c")
# 42
# modify (returns same type)
modify_if(iris, is.numeric, ~ .x * 2)
modify_depth(nested_list, 2, ~ .x + 1)Shiny 应用
基本应用结构
Shiny 应用有 ui(布局)和 server(逻辑)。输入来自 input$<id>;输出通过 render* 函数到 output$<id>。fluidPage 是基本布局;sidebarLayout 分为 sidebar(控件)和 main(输出)。保存为 app.R 在自己的文件夹中;文件夹名成为应用名。
library(shiny)
ui <- fluidPage(
titlePanel("Hello Shiny"),
sidebarLayout(
sidebarPanel(
sliderInput("n", "Number of points", 1, 100, 50)
),
mainPanel(
plotOutput("scatter")
)
)
)
server <- function(input, output, session) {
output$scatter <- renderPlot({
plot(1:input$n, rnorm(input$n))
})
}
shinyApp(ui, server)
# Save as app.R in a folder, run with:
# shiny::runApp("path/to/folder")
# Or click "Run App" in RStudio输入与响应性
Shiny 有许多输入控件。observeEvent 在输入更改时运行代码;eventReactive 从事件创建响应值。reactive() 缓存其结果直到输入更改。reactiveVal/reactiveValues 保存可变状态。使用 actionButton + observeEvent 进行显式触发(不要对每次击键都响应)。
library(shiny)
ui <- fluidPage(
numericInput("n", "N", value = 10, min = 1, max = 100),
textInput("label", "Label", value = "Data"),
selectInput("color", "Color", choices = c("red", "blue", "green")),
dateInput("date", "Date"),
dateRangeInput("range", "Range"),
checkboxInput("show", "Show?", value = TRUE),
checkboxGroupInput("vars", "Variables", choices = names(mtcars)),
radioButtons("dist", "Distribution",
choices = c("Normal", "Uniform", "Exponential")),
fileInput("file", "Upload CSV", accept = ".csv"),
actionButton("go", "Go!")
)
server <- function(input, output, session) {
# event-triggered
observeEvent(input$go, {
showNotification(paste("Clicked", input$go))
})
# reactive expression (cached)
data <- reactive({
rnorm(input$n)
})
# reactive value
val <- reactiveVal(0)
observeEvent(input$go, val(val() + 1))
}输出与渲染
每种输出类型都有匹配的 render* 函数:renderPlot 用于图表,renderTable 用于表格,renderPrint 用于控制台输出,renderText 用于字符串,renderUI 用于动态 UI。输出是响应式的——当其输入依赖项更改时重新执行。DT::dataTableOutput 是交互式表格的标准。
library(shiny)
library(ggplot2)
library(DT)
ui <- fluidPage(
plotOutput("plot", click = "plot_click"),
tableOutput("table"),
DT::dataTableOutput("dt"),
verbatimTextOutput("summary"),
textOutput("text"),
uiOutput("dynamic")
)
server <- function(input, output, session) {
output$plot <- renderPlot({
ggplot(mtcars, aes(wt, mpg)) + geom_point()
})
output$table <- renderTable({
head(mtcars)
})
output$dt <- DT::renderDataTable({
datatable(mtcars, filter = "top")
})
output$summary <- renderPrint({
summary(mtcars)
})
output$text <- renderText({
paste("You clicked:", input$plot_click$x)
})
output$dynamic <- renderUI({
if (input$n > 5) strong("Big!") else em("Small")
})
}
# observe vs reactive
# observe: side effects (output$ assignments, updates)
# reactive: returns a value, used by other reactives响应式编程
reactive() 是主力——缓存且惰性(仅在读取时重新计算)。observe() 是急切的(在依赖项更改时立即运行)——用于副作用。eventReactive 等待事件。reactiveValues 保存多个可变值(类似小型响应对象)。isolate 读取值而不创建依赖。debounce 节流快速输入更改。
library(shiny)
server <- function(input, output, session) {
# reactive: cached, lazy
filtered <- reactive({
mtcars %>% filter(cyl == input$cyl)
})
# observe: eager, side effects
observe({
updateSelectInput(session, "model",
choices = unique(filtered()$model))
})
# eventReactive: triggered by event
result <- eventReactive(input$go, {
run_analysis(input$params)
})
# reactiveVal: single mutable value
counter <- reactiveVal(0)
observeEvent(input$add, counter(counter() + 1))
# reactiveValues: multiple values
rv <- reactiveValues(data = NULL, status = "idle")
observeEvent(input$load, {
rv$data <- read.csv(input$file$datapath)
rv$status <- "loaded"
})
# debounce (throttle rapid changes)
search <- reactive({ input$search }) %>% debounce(500)
# isolate (use value without dependency)
output$plot <- renderPlot({
plot(isolate(rv$data)) # not reactive on rv$data
})
}部署
shinyapps.io 是最简单的托管(有免费层)。对于自托管,在 Linux 上安装 Shiny Server 或使用 Docker(rocker/shiny 镜像)。为提高性能:缓存图表,对长任务使用异步(future/promises),避免在 render 函数中重新读取文件。启用 reactlog(Ctrl+F3)可视化响应图用于调试。
# Deploy to shinyapps.io
# 1. Create account at shinyapps.io
# 2. Install rsconnect
install.packages("rsconnect")
# 3. Authorize (paste token from shinyapps.io)
rsconnect::setAccountInfo(name = "<name>", token = "<token>", secret = "<secret>")
# 4. Deploy
rsconnect::deployApp("path/to/app")
# Run on local network
shiny::runApp("app.R", host = "0.0.0.0", port = 3838)
# Shiny Server (self-hosted on Linux)
# Install shiny-server, put apps in /srv/shiny-server/
# Config: /etc/shiny-server/shiny-server.conf
# Access at http://server:3838/appname
# Docker
# FROM rocker/shiny
# COPY app.R /srv/shiny-server/myapp/
# EXPOSE 3838
# Performance tips:
# - Use renderCachedPlot for expensive plots
# - Use future + promises for async work
# - Move heavy compute to reactive({...})
# - Avoid re-reading files in render functions
# Debugging
options(shiny.fullstacktrace = TRUE)
options(shiny.reactlog = TRUE) # press Ctrl+F3 in app性能与分析
用 profvis 进行性能分析
profvis 是现代分析器——生成交互式火焰图显示时间花在哪里。寻找宽条(慢操作)和高栈(深调用链)。用实际数据大小分析;小输入会隐藏 O(n²) 问题。summaryRprof 是基础 R 的替代方案。优化前始终分析——直觉常常是错的。
library(profvis)
# profile a block of code
profvis({
data <- read.csv("large.csv")
result <- lapply(data, function(x) {
x[x > 0]
})
plot(result)
})
# profile a function call
profvis(my_function(arg1, arg2))
# save profile
p <- profvis({ ... })
htmlwidgets::saveWidget(p, "profile.html")
# Rprof (base R)
Rprof("profile.out")
# ... code to profile ...
Rprof(NULL)
summaryRprof("profile.out")
# tips:
# - profile realistic inputs (not too small)
# - run multiple times for stable results
# - look for the widest bars in the flame graph
# - focus on hotspots, not micro-optimizations向量化
向量化是 R 最大的性能杠杆——对整个向量的操作比循环快 10-100 倍,因为它们进入 C。预分配(numeric(n))是第二大的收益——绝不在循环内增长向量。vapply 比 sapply 更安全更快(类型化输出)。rowMeans/colSums 高度优化——优先使用而非 apply。
# BAD: loop with growing result
slow <- function(n) {
result <- numeric(0)
for (i in 1:n) {
result <- c(result, i^2)
}
result
}
# BETTER: preallocate
better <- function(n) {
result <- numeric(n)
for (i in 1:n) {
result[i] <- i^2
}
result
}
# BEST: vectorize
fast <- function(n) {
(1:n)^2
}
# benchmarks
microbenchmark::microbenchmark(
slow = slow(1000),
better = better(1000),
fast = fast(1000),
times = 10
)
# apply family
# sapply/lapply: list apply
# vapply: typed sapply (safer, faster)
# map_dbl: tidyverse, typed
# rowMeans/colSums: faster than apply(x, 1, mean)用于热点的 Rcpp
Rcpp 让你编写可从 R 调用的 C++ 函数——对于无法向量化的循环通常快 10-100 倍。cppFunction 用于单行;sourceCpp 用于文件。Rcpp 'sugar' 提供向量化 C++ 运算符(所以 x*2+1 适用于向量)。用于分析识别的热点,而非所有内容——R 的向量化操作已经是 C。
library(Rcpp)
# inline C++ in R
cppFunction('
double sumC(NumericVector x) {
double s = 0;
for (int i = 0; i < x.size(); i++) {
s += x[i];
}
return s;
}
')
sumC(1:1e6)
# source from file
# file: code.cpp
# #include <Rcpp.h>
# using namespace Rcpp;
# // [[Rcpp::export]]
# double meanC(NumericVector x) {
# return std::accumulate(x.begin(), x.end(), 0.0) / x.size();
# }
sourceCpp("code.cpp")
# use Rcpp sugar (vectorized C++)
cppFunction('
NumericVector funC(NumericVector x) {
return x * 2 + 1; // vectorized via Rcpp sugar
}
')
# data frames
cppFunction('
DataFrame subsetC(DataFrame df, LogicalVector mask) {
return df[mask];
}
')内存与 data.table
data.table 对于大数据(>1M 行)比 dplyr 快得多——通常 5-50 倍。dt[i, j, by] 语法一旦学会就很简洁。:= 原地修改(无复制)——巨大的内存节省。setkey 启用快速查找和连接。fread/fwrite 是 R 中最快的 CSV 读取/写入器。速度重要时使用 data.table;可读性优先时使用 dplyr。
library(data.table)
# data.table is much faster than data.frame for large data
dt <- fread("huge.csv") # fast CSV reader
fwrite(dt, "out.csv") # fast CSV writer
# syntax: dt[i, j, by]
dt[, mean(value), by = group] # group by + summarize
dt[order(-value)] # sort
dt[value > 100, .N, by = group] # filter + count by group
dt[, .(avg = mean(value), n = .N), by = group]
# reference semantics (no copy)
dt[, new_col := value * 2] # add column in place
dt[value < 0, value := 0] # modify in place
# keys for fast lookups
setkey(dt, id)
dt["abc"] # fast lookup
dt["abc", mult = "first"]
# joins
dt1[dt2, on = "id"] # left join
dt1[dt2, on = .(id), nomatch = 0] # inner join
# memory tips
# - gc() to force garbage collection
# - object.size(x) to check size
# - rm() large objects when done
# - read data in chunks for huge files并行计算
parallel(基础 R)可移植但冗长。future + furrr 是现代的、tidyverse 友好的方法——用 plan() 切换后端。multicore 使用 fork(快,仅 Linux/Mac);multisession 使用独立 R 会话(可移植,较慢)。并行有开销——仅对每个耗时 >100ms 的任务值得。始终在前后进行基准测试。
library(parallel)
library(future)
library(furrr)
# parallel lapply
cl <- makeCluster(detectCores() - 1)
result <- parLapply(cl, items, function(x) {
# work on x
})
stopCluster(cl)
# future: modern, simpler
library(future)
plan(multisession) # parallel backend
result <- future_lapply(items, fun)
# or
plan(multicore) # fork (Linux/Mac, faster)
plan(cluster, workers = 4)
# furrr: parallel purrr
library(furrr)
plan(multisession, workers = 4)
result <- future_map(items, fun)
result <- future_map_dbl(items, ~ .x^2)
# foreach
library(foreach)
library(doParallel)
registerDoParallel(4)
result <- foreach(i = 1:10, .combine = c) %dopar% {
i^2
}
# always benchmark — parallel overhead can outweigh gains
# for small tasks
microbenchmark::microbenchmark(
serial = lapply(1:100, slow_fn),
parallel = future_lapply(1:100, slow_fn),
times = 5
)相关 R 代码片段
Copy-paste ready code for common tasks.
Data Frames
Create, inspect, filter, mutate, sort, aggregate, and merge data frames.
Vectors
Build atomic vectors, apply vectorized ops, index, and recycle.
ggplot2
Build layered plots with geoms, facets, and themes using the grammar of graphics.
dplyr
Chain mutate, filter, group_by, summarise, and joins with the native pipe.
Statistics
Compute summaries, run t-tests, linear models, ANOVA, and use distributions.
Apply Family
Apply functions over arrays, lists, and groups with apply, lapply, sapply, tapply.
Data Import/Export
Read and write CSV, TSV, RDS, RData, and text files with base R.
Functions
Define functions with defaults, variadic args, closures, and higher-order use.
这篇内容对您有帮助吗?