Skip to content

R 速查表

统计计算和图形语言。

01

向量与基础

变量、类型与赋值

R 使用 <- 作为首选赋值运算符(= 有效但在函数参数中可能有歧义)。R 中的一切都是向量——即使是单个数字也是长度为 1 的向量。核心类型是 character、numeric(double)、integer、logical 和 complex。NA 表示缺失数据并通过运算传播(使用 na.rm=TRUE 跳过)。NULL 是值的缺失(空对象),与 NA 不同。分析前始终检查 NA。

r
# 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'))。

r
# 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 在向量运算上很快。

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。

r
# 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 是向量化三元运算符——对于从连续变量创建分类变量极其有用。

r
# 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"
02

数据结构

列表(异构容器)

列表是 R 最灵活的数据结构——它们可以保存任何类型和大小的元素(类似 Python 字典或 JavaScript 对象)。$ 运算符是命名访问的便捷快捷方式。关键区别:[ ] 返回子列表(仍然是列表),而 [[ ]] 提取实际元素。这是 R 初学者困惑的头号来源。当你想要值本身时使用 [[ ]],当你想要子集时使用 [ ]。lapply/sapply 遍历列表元素应用函数。

r
# 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+,这是默认值)以将字符串保持为字符而非因子。

r
# 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 维。对于数据分析,优先使用数据框;线性代数使用矩阵。

r
# 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() 生成频数计数。

r
# 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。

r
# 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")
03

控制流与函数

If / Else 与 Switch

R 的 if/else 对于多行体需要花括号,且 else 必须与右花括号在同一行(否则 R 认为 if 已完成)。ifelse(test, yes, no) 是向量化的——它一次应用于整个向量,返回结果向量。对于多个条件,dplyr 的 case_when 比嵌套 ifelse 干净得多。switch 根据字符串(或数值位置)分派——是长 if-else 链的干净替代方案。

r
# 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 家族而非循环。

r
# 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 使用惰性求值:参数仅在首次使用时求值,因此未使用的参数不会导致错误。通过将多个值打包到列表中来返回多个值。

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() 家族。

r
# 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 是你的工作区,后跟附加的包。

r
# 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)
04

数据操作(dplyr 与 tidyr)

dplyr:筛选、选择与排列

dplyr(tidyverse 的一部分)提供镜像 SQL 操作的直观数据操作动词。filter 按条件选择行;select 选择列;arrange 排序。%in% 运算符测试成员资格。辅助函数如 starts_with、ends_with、contains 使列选择灵活。rename() 在不复制的情况下更改列名。这些动词与管道(%>%)组合形成可读的数据管道。dplyr 对大数据比基础 R 快得多,因为它内部使用 C++。

r
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 columns

dplyr:Mutate、Summarize 与 Group By

mutate 添加或修改列(向量化)。summarize 将每组减少为单个汇总行。功能来自 group_by + summarize——R 中 SQL GROUP BY 的等价物。n() 计数行;across() 将函数应用于多列(dplyr 1.0 新增)。窗口函数(rank、cumsum、lag、lead)在组内操作,实现'部门内排名'等计算。管道 %>% 从左到右链接操作,使复杂管道可读。

r
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+ 有原生管道 |>,但灵活性较低(没有点占位符)。管道使数据整理代码可读性大幅提升。

r
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() 快得多。始终在连接前后检查行数以捕获意外重复。

r
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 组合形成强大的数据管道。

r
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")
05

统计与建模

描述性统计

summary() 是获取任何数据概览的最快方式——它显示最小值、四分位数、中位数、平均值和最大值。sd() 和 var() 计算样本(n-1)统计量。cor() 衡量线性关联(Pearson)或等级关联(Spearman)。对包含 NA 的真实世界数据始终使用 na.rm=TRUE。对于数据框,summary(df) 给出每列的统计信息。psych 和 Hmisc 包提供扩展的描述性统计。

r
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
# 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,你可以以编程方式提取。

r
# 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 图)以验证模型假设。

r
# 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(正则化)。

r
# 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
06

绘图(基础 R 与 ggplot2)

基础 R:plot、hist 与 boxplot

基础 R 图形快速且足以进行探索性分析。plot() 是泛型的——它根据输入类型分派(两个向量为散点图,公式为箱线图)。type 控制点/线样式;pch 设置点符号;lty 设置线型。par(mfrow=c(r,c)) 在网格中排列多个图。带 freq=FALSE 的 hist() 显示密度(因此你可以叠加密度曲线)。对于出版质量的图形,改用 ggplot2。基础 R 图形是命令式的——你逐步构建它们。

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))   # reset

ggplot2:图形语法

ggplot2(tidyverse)实现图形语法:图形由层(数据、美学、几何、比例、分面、主题)用 + 组合而成。aes() 将数据列映射到视觉属性(x、y、color、size)。每个 geom_* 添加一层:geom_point(散点)、geom_line、geom_bar、geom_histogram、geom_boxplot、geom_smooth(趋势线)。这种分层方法意味着你可以增量构建复杂图形。与基础 R 不同,ggplot2 是声明式的——你描述想要什么,而不是如何绘制它。

r
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 labels

ggplot2:几何对象与美学

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)。

r
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。

r
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。

r
# 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")
07

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') 查看类的方法。

r
# 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。

r
# 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 中最接近带私有字段的类的等价物。

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 公式语法简洁地创建匿名函数。函数式编程使代码更具声明性且更易推理,特别是对于数据转换管道。

r
# 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 代码中的问题至关重要。

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
08

时间序列与预测

创建时间序列对象(ts)

ts() 是基础 R 的时间序列类——带时间属性(start、frequency)的向量。frequency 编码周期:12 为月度,4 为季度,52 为周度。window() 按时间范围子集化。diff() 计算差分(用于使序列平稳)。lag() 移动值。aggregate() 转换为较低频率(例如,月度到季度)。ts 适用于规则的固定频率数据。对于不规则时间戳(例如,有间隔的股票价格),改用 xts/zoo 包。

r
# 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() 用于带置信区间的预测。建模前始终绘制分解以理解结构。当季节振幅随趋势增长时,乘法分解是合适的。

r
# 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 version

ACF、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 是时间序列预测的主力。

r
# 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 residuals

xts 与 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 包提供现代替代方案。

r
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() 用预测区间可视化预测。朴素预测(最后值)是关键基线——你的模型必须超越它才有用。永远不要对时间序列使用随机训练/测试分割(它们会泄漏未来信息);始终按时间顺序分割。

r
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)
09

dplyr 深入

核心动词:filter、select、mutate、summarize

dplyr 的五个核心动词涵盖大多数数据操作:filter(按条件筛选行)、select(按名称选择列)、mutate(添加/转换列)、summarize(折叠为汇总统计)和 arrange(排序)。管道 %>%(或原生 |>)从左到右链接操作,使代码可读。starts_with、ends_with、contains 和 everything() 等辅助函数使列选择简洁。summarize 前始终 group_by 以获得每组统计;n() 计算每组行数。

r
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 并排粘贴而不检查键——通常你需要的是连接。连接是微妙数据错误的最常见来源,因此在前后验证行数。

r
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 窗口函数一样强大。

r
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 对多列批量操作高度表达。

r
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 注入。

r
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)
10

ggplot2 深入

图形语法与分层绘图

ggplot2 建立在图形语法之上:每个图形都是数据、美学映射(aes)、几何对象(geom_*)、统计(stat_*)、比例、坐标系和分面的组合。层用 + 添加。美学将数据列映射到视觉属性(x、y、color、size、shape);固定值放在 aes() 外。facet_wrap 和 facet_grid 创建小倍数——最强大的探索工具之一。大多数 geom 有默认 stat(例如,geom_bar 使用 stat_count),但你可以用 stat_summary 覆盖以进行自定义聚合。

r
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() 添加独立于数据的固定元素(文本、矩形、线段)。

r
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。

r
# 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 部署。

r
# 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 应用和自动报告管道至关重要。

r
# 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)
}
11

tidyr 数据整理

Pivot Longer 与 Wider

整洁数据每次观察一行,每个变量一列——大多数分析函数期望此格式。pivot_longer 将宽格式转为长格式(将列收集为键值对);pivot_wider 做相反的操作。names_to 中的 .value 哨兵将列名的一部分保留为单独的列(例如,'a_1' 变为 a=1, b=1)。绘图或建模前始终重塑:ggplot2 需要长格式用于分组美学;某些建模函数需要宽格式。names_pattern 参数用正则表达式处理更复杂的列名结构。

r
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     7

Separate、Unite 与 Extract

separate 按分隔符将一列拆分为多列;unite 将多列合并为一列。extract 使用正则表达式捕获组进行更灵活的拆分。separate_rows 将分隔字符串展开为多行——当单元格包含列表(例如,标签、类别)时至关重要。convert = TRUE 选项自动转换类型(数字、日期)。这些函数清理杂乱的真实世界数据:拆分全名、解析日期、规范化分隔字段。与 pivot_* 结合,它们处理几乎任何重塑任务。

r
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 包。

r
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 的函数式编程的基础。

r
# 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 数据库和配置文件至关重要——现代数据工程师的日常任务。

r
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-flatten
12

purrr 函数式编程

map 家族与类型安全变体

purrr 的 map 家族用一致、类型安全的变体替代 lapply/sapply。map_dbl/chr/int/lgl 返回有类型向量(不匹配时报错)——比静默强制转换的 sapply 安全得多。map2 和 pmap 并行遍历多个向量。imap 同时提供值和索引。walk 用于副作用(打印、写文件)而不需要返回值。~ .x 简写创建匿名函数;.x 是第一个参数,.y 是第二个。在生产代码中始终优先使用 map_* 而非 sapply 以避免类型不稳定。

r
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 的 [[ 提取变得笨拙。

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")  # error

Reduce、Accumulate 与谓词函数

reduce() 成对组合元素(左折叠)——非常适合合并多个数据框或计算乘积。accumulate() 保留中间结果,对运行总计很有用。keep/discard 按谓词筛选元素(类似 dplyr::filter 但用于向量/列表)。some/every 测试是否有任何/所有元素满足条件。detect 找到第一个匹配元素。这些高阶函数用简洁、声明式代码替代许多循环。negate() 反转谓词函数——用于组合条件很方便。它们一起使 purrr 成为完整的函数式编程工具包。

r
# 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 3

Safely、Possibly 与错误处理

safely() 包装函数以始终返回带 'result' 和 'error' 的列表——从不抛出。这对批量操作至关重要,其中一个失败不应停止整个运行。possibly() 在出错时返回默认值(当你不需要错误详情时更干净)。quietly() 捕获警告和消息。transpose() 将 {result, error} 对列表翻转为单独的列表——便于将成功与失败分开。在处理可能单独失败的许多项目(API 调用、文件读取、模型拟合)时使用这些。

r
# 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 既适合交互式探索也适合生产管道。

r
# 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 50
13

stringr 与 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 家族一致得多。

r
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 的分散字符串函数相比可预测且可读。

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 包以可读方式构建正则表达式。

r
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 假设。

r
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)表示带固定端点的跨度。使用周期进行人类尺度算术(调度),使用持续时间进行经过时间测量。

r
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")
14

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。

r
---
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 对象在代码块内可用于筛选数据。

r
---
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)并添加标题以提供上下文。好的表格与好的图形一样重要,用于传达结果。

r
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)、网站和书籍。

r
---
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 中商业智能和自动化数据产品的骨干。

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)
15

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 上。

r
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() 调试依赖图。

r
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 应用的关键:将应用拆分为小的、可测试的模块(图表模块、筛选模块、数据上传模块)并组合它们。

r
# 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 对复杂视觉效果更快)。将输入与响应式表达式结合以构建复杂、响应迅速的仪表板。

r
# 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() 分析慢应用以找到瓶颈。

r
# 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
16

统计检验与推断

假设检验框架

假设检验评估观察到的数据是否与零假设一致。t 检验比较均值(参数,假设正态性);Wilcoxon 是非参数替代方案。始终报告效应量和置信区间,而不仅仅是 p 值——p 取决于样本量,而 CI 显示实际显著性。解释前检查假设:正态性(Shapiro-Wilk)、等方差(Levene)。功效分析(pwr 包)在收集数据前确定所需样本量。0.05 阈值是常规的,不是神奇的——考虑效应量和上下文。

r
# 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 effect

ANOVA 与多重比较

ANOVA 检验 3+ 组之间均值是否不同。F 检验告诉你是否存在任何差异;事后检验(Tukey HSD、带校正的 pairwise.t.test)识别哪些组不同。始终检查假设(正态性、方差齐性),如果违反则使用非参数替代方案(Kruskal-Wallis)。对于重复测量或层次数据,使用混合效应模型(lme4::lmer)正确处理受试者内相关性。多重比较校正(Bonferroni、FDR)在运行许多检验时防止假阳性。afex 包简化了重复测量 ANOVA。

r
# 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 值前始终可视化。

r
# 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(可视化分类数据)包提供全面工具。

r
# 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 通过交叉验证预测准确性比较模型。层次模型自然处理分组数据。贝叶斯方法在小样本、复杂模型和需要不确定性量化时表现出色。

r
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)
17

使用 caret 的机器学习

数据分割与预处理

正确的数据分割和预处理是 ML 成功的 80%。createDataPartition 进行分层抽样(保留类平衡)。trainControl 配置重采样(CV、bootstrap、重复 CV)。preProcess 处理标准化、变换、PCA 和插补——始终只在训练数据上拟合并应用于测试以避免泄漏。nzv 移除无信息列。对于时间序列,使用 createTimeSlices 而非随机 CV。caret 的统一接口意味着相同的预处理适用于所有模型类型。

r
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 规则')。

r
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)。报告性能的置信区间,而不仅仅是点估计。

r
# 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)将预测归因于特征。始终在交叉验证内进行特征选择以避免选择偏差。具有较少特征的更简单模型通常泛化更好。

r
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)通过平均自助模型减少方差。基础模型的多样性比其各自准确度更重要——组合犯不同错误的模型。

r
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)
18

Apply 家族与性能

apply、lapply、sapply、vapply

apply 家族是基础 R 的函数式编程工具包。apply 作用于数组(用 margin 1 为行,2 为列),但内置的 rowSums/colSums 更快。lapply 始终返回列表;sapply 尝试简化为向量(方便但类型不稳定)。vapply 是安全版本——你指定输出模板,因此不匹配时报错而不是静默强制转换。在生产代码中使用 vapply,交互式使用 sapply。replicate 对模拟很方便。mapply(或 Map)并行遍历多个参数。对于现代代码,为一致性优先使用 purrr 的 map 家族。

r
# 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 太粗糙。

r
# 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 相比可读性较差。

r
# 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)时才有帮助。基准测试以验证加速;阿姆达尔定律限制收益。

r
# 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 缓存函数结果——对于以相同参数重复调用的昂贵纯函数很好。始终在前后测量以确认改进。

r
# 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)
19

dplyr 深入

核心动词

dplyr 的五个核心动词:filter(按条件筛选行)、select(列)、mutate(新列)、arrange(排序)、summarize(聚合)。用 %>% 链接。group_by + summarize 是聚合的主力。na.rm = TRUE 至关重要——否则数据中的任何 NA 都会使汇总变为 NA。

r
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 非常适合基于另一个表进行筛选而不引入其列。

r
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 以在组内计算。

r
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 是操作多列的现代、一致方式。

r
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 的简写。

r
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))
20

ggplot2 高级

层与美学

ggplot 用 + 连接层构建图形。aes() 将数据列映射到视觉属性。geom_* 定义几何;scale_* 控制轴/颜色;labs 标记一切;theme_* 设置非数据元素样式。facet_wrap 按一个变量拆分;facet_grid 制作两个变量的 2D 网格。

r
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 格式化日期轴。

r
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。

r
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 在箱线图旁显示完整分布形状。

r
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 用于地图。

r
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()
21

tidyr 数据整理

Pivot longer 与 wider

pivot_longer/pivot_wider(替代 gather/spread)重塑数据。长格式最适合 ggplot 和 dplyr 聚合;宽格式更适合人类阅读。带 .value 的 names_pattern 让你根据列名结构拆分为多列。始终显式指定 cols、names_to 和 values_to。

r
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 自动转换类型。

r
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 扩展到指定列的所有组合(类似笛卡尔积)——用于确保缺失组出现在汇总中。

r
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 内保存任意对象(模型、数据框、向量)。

r
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。

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
22

统计检验

t 检验

t 检验比较均值。单样本(与一个值比较)、双样本(组间)、配对(受试者内)。默认是 Welch(不等方差)——通常是你想要的。始终检查假设:正态性(Shapiro)和等方差(var.test)。报告效应量(Cohen's d),而不仅仅是 p 值。

r
# 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。

r
# 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 作为效应量。

r
# 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' 处理缺失数据而不丢弃整行。

r
# 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 值旁报告效应量。

r
# 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?
23

回归分析

线性回归

lm 拟合线性模型。summary 显示系数、标准误、t 值、p 值、R² 和 F 统计量。始终检查诊断:残差图用于线性/方差齐性、VIF 用于多重共线性(>5 令人担忧)。I() 保护公式中的算术;poly(x, 2) 给出正交多项式。

r
# 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。

r
# 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(可将系数归零——特征选择)。

r
# 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 默认不计算它们)。

r
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 计算边际效应用于可视化。

r
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-Leibler
24

R Markdown 与报告

R Markdown 基础

R Markdown 结合叙述(Markdown)、代码(R 代码块)和输出(表格/图表)。YAML 头部设置元数据和输出格式。代码块选项:echo=FALSE 隐藏代码,include=FALSE 运行但隐藏所有内容,fig.cap 添加标题。用反引号的内联 r 代码将值插入正文。Knit(Ctrl+Shift+K)渲染。

r
---
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
```{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(更精致)。可以同时指定多个输出。

r
---
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)。

r
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 用于周期性报告。

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"))
}
25

purrr 函数式编程

map 家族

map 是 tidyverse 的 lapply——始终返回列表。map_dbl/chr/int/lgl 返回类型化向量(比 map 更安全)。map2 和 pmap 并行迭代多个参数。walk 用于副作用(打印、保存)。.x 代词指当前元素;在 pmap 中,使用 ..1、..2 等。map_dfr 按行绑定数据框。

r
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。

r
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 测试谓词。这些用简洁、可组合的操作替代循环。

r
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。这些工具使函数式组合干净且可读。

r
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 更改匹配谓词的元素,保留原始结构。

r
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)
26

Shiny 应用

基本应用结构

Shiny 应用有 ui(布局)和 server(逻辑)。输入来自 input$<id>;输出通过 render* 函数到 output$<id>。fluidPage 是基本布局;sidebarLayout 分为 sidebar(控件)和 main(输出)。保存为 app.R 在自己的文件夹中;文件夹名成为应用名。

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 进行显式触发(不要对每次击键都响应)。

r
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 是交互式表格的标准。

r
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 节流快速输入更改。

r
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)可视化响应图用于调试。

r
# 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
27

性能与分析

用 profvis 进行性能分析

profvis 是现代分析器——生成交互式火焰图显示时间花在哪里。寻找宽条(慢操作)和高栈(深调用链)。用实际数据大小分析;小输入会隐藏 O(n²) 问题。summaryRprof 是基础 R 的替代方案。优化前始终分析——直觉常常是错的。

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。

r
# 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。

r
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。

r
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 的任务值得。始终在前后进行基准测试。

r
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
)

这篇内容对您有帮助吗?