R: R 函数:自定义函数与函数式编程
最后更新:2026-08-26
前面 6 课我们都在用 R 内置函数(
sum()mean()c())。但真正让 R 强大的,是你能自己写函数。这一课我们学 R 的"自定义函数"——把重复代码封装成可复用的工具。
R 是函数式编程语言,函数是"第一公民"(first-class citizen)。这意味着函数可以赋值给变量、作为参数传递、作为返回值。这一课我们要掌握 R 函数的所有特性。
1. 你将学到
- function(){} 基本语法
- 4 种参数(位置、命名、默认、可变参数 ...)
- 3 种返回值(return / 最后一行 / invisible)
- 匿名函数(lambda)
- 函数作为对象(赋值、传参、返回)
- do.call() 批量调用
- 实战:写一个完整工具函数
2. 一个代码复用的故事
(1) 痛点:重复代码
小陈是数据分析师,每天要做 10 次"把销售数据转成报表"。每次他都重复写:
# 重复 10 次的代码
sales <- c(120, 150, 80, 200, 175)
total <- sum(sales)
avg <- mean(sales)
top <- max(sales)
cat("总销售:", total, "平均:", avg, "最高:", top, "\n")
10 次复制粘贴 = 200 行重复代码。改一处逻辑要改 10 处,迟早会漏。
(2) 函数的解法
# 写一个函数(只写 1 次)
report <- function(x) {
cat("总销售:", sum(x), " 平均:", mean(x), " 最高:", max(x), "\n")
}
# 调用 10 次(每次 1 行)
report(c(120, 150, 80, 200, 175))
report(c(80, 90, 100))
report(c(1000, 2000, 3000))
5 行函数 + 10 行调用 = 15 行代码。改逻辑只改 1 处。这就是函数的威力。
3. function(){}:基本语法
(1) 函数定义
# 最简单的函数
greet <- function() {
cat("Hello, World!\n")
}
# 调用
greet()
# Output:Hello, World!
(2) 带参数的函数
# 单个参数
greet <- function(name) {
cat("Hello,", name, "!\n")
}
greet("Alice")
# Output:Hello, Alice !
# 多个参数
greet_full <- function(name, age) {
cat("我是", name, ",今年", age, "岁\n")
}
greet_full("Bob", 25)
# Output:我是 Bob ,今年 25 岁
(3) R vs Python 函数差异
| 特性 | R | Python |
|---|---|---|
| 定义 | function(x) { ... } |
def func(x): |
| 代码块 | {} |
缩进 |
| 返回值 | 最后一行自动返回 | 需要 return |
| 文档 | #' 开头(roxygen2) |
docstring |
| 类型声明 | 无(动态类型) | 可选(Python 3.5+) |
# R 的特殊:最后一行自动返回(不需要 return)
add <- function(a, b) {
a + b # 最后一行自动返回
}
result <- add(3, 4)
result
# [1] 7
4. 4 种参数
(1) 位置参数 vs 命名参数
# 函数定义:3 个参数
power <- function(base, exp, mod) {
result <- (base ^ exp) %% mod
return(result)
}
# 位置参数(按顺序)
power(2, 10, 1000)
# [1] 24 ← 2^10=1024, 1024 %% 1000 = 24
# 命名参数(按名字,不按顺序)
power(mod = 1000, base = 2, exp = 10)
# [1] 24 ← 同样结果
(2) 默认参数
# 第二个参数有默认值
power <- function(base, exp = 2, mod = 1000) {
result <- (base ^ exp) %% mod
return(result)
}
# 不传第二个参数,用默认 exp = 2
power(10)
# [1] 100 ← 10^2=100, 100 %% 1000 = 100
# 部分传参
power(10, mod = 100)
# [1] 0 ← 10^2=100, 100 %% 100 = 0
(3) 参数顺序规则
R 的参数匹配规则:
graph TB
A[函数调用 power 10 exp=3] --> B[1. 完全匹配命名参数]
B --> C[2. 前缀匹配]
C --> D[3. 位置匹配]
style A fill:#fff3cd
style B fill:#d4edda
style C fill:#d4edda
style D fill:#d4edda
- 完全匹配:
exp = 3(最明确) - 前缀匹配:
e = 3→exp(少打字) - 位置匹配:第一个未命名参数 →
base(最不安全)
exp = 3),少打字省不了多少时间,但能避免歧义。
(4) 可变参数 ...
... 表示"任意数量参数",常用于包装函数:
# 用 ... 接受任意参数
my_sum <- function(...) {
args <- list(...)
result <- sum(unlist(args))
cat("求和了", length(args), "个参数:", result, "\n")
return(result)
}
my_sum(1, 2, 3)
# 求和了 3 个参数: 6
my_sum(10, 20, 30, 40, 50)
# 求和了 5 个参数: 150
... 在包装其他函数时非常常用(如自定义的 my_mean() 包装 mean() 的部分参数)。
5. 3 种返回值
(1) 最后一行自动返回
R 的函数最后一行表达式会自动返回:
add <- function(a, b) {
a + b # 最后一行,自动返回
}
add(3, 4)
# [1] 7
(2) 显式 return()
# 用 return() 提前返回
check_age <- function(age) {
if (age < 0) {
return("无效年龄") # 提前返回
}
if (age >= 18) {
return("成年")
}
"未成年" # 隐式返回
}
check_age(-5) # [1] "无效年龄"
check_age(20) # [1] "成年"
check_age(15) # [1] "未成年"
(3) invisible():隐藏返回值
invisible() 让函数不自动打印返回值(但仍可赋值):
# 普通函数
add_print <- function(a, b) {
a + b # 会自动打印
}
add_print(3, 4)
# [1] 7 ← 自动打印
# invisible 函数
add_silent <- function(a, b) {
invisible(a + b) # 不自动打印
}
add_silent(3, 4)
# 没有输出 ← 不打印
# 但仍可赋值
result <- add_silent(3, 4)
result
# [1] 7 ← 赋值后能看到
<- 赋值就是用 invisible()——x <- 5 不打印结果。
6. 匿名函数:lambda 表达式
R 支持匿名函数(没有名字的函数):
# 普通函数
square <- function(x) x ^ 2
square(5)
# [1] 25
# 匿名函数(直接用)
(function(x) x ^ 2)(5)
# [1] 25
# 匿名函数的常见场景:lapply/sapply
numbers <- list(1, 2, 3, 4, 5)
result <- lapply(numbers, function(x) x ^ 2)
result
# [[1]] 1
# [[2]] 4
# [[3]] 9
# [[4]] 16
# [[5]] 25
7. 函数作为对象(R 函数式编程核心)
R 的函数是"第一公民",意味着函数可以:
(1) 赋值给变量
f <- function(x) x * 2
g <- f # 同一个函数有两个名字
f(5) # [1] 10
g(5) # [1] 10 ← 等价
(2) 作为参数传递
# 接受函数作为参数的函数
apply_twice <- function(f, x) {
f(f(x))
}
# 传不同函数
apply_twice(function(x) x + 1, 5) # 5+1+1=7
apply_twice(function(x) x * 2, 5) # 5*2*2=20
(3) 作为返回值
# 返回函数的函数(闭包)
make_multiplier <- function(k) {
function(x) x * k
}
# 创建不同倍率的函数
double <- make_multiplier(2)
triple <- make_multiplier(3)
double(5) # [1] 10
triple(5) # [1] 15
map() reduce())就用这个。
8. do.call():动态调用
do.call() 把列表/向量展开成函数参数:
# 普通调用
sum(1, 2, 3, 4, 5)
# [1] 15
# do.call 调用(参数是列表)
args <- list(1, 2, 3, 4, 5)
do.call(sum, args)
# [1] 15
# 实战:动态创建 data.frame
col_names <- c("name", "age", "score")
col1 <- c("Alice", "Bob", "Charlie")
col2 <- c(25, 30, 35)
col3 <- c(95, 88, 92)
df <- do.call(data.frame, list(
name = col1,
age = col2,
score = col3
))
print(df)
# name age score
# 1 Alice 25 95
# 2 Bob 30 88
# 3 Charlie 35 92
9. 作用域:变量在哪生效?
R 用词法作用域(lexical scoping):
# 全局变量
x <- 10
# 函数内可以读全局变量
read_x <- function() {
cat("x =", x, "\n") # 读全局 x
}
read_x()
# x = 10
# 但函数内的局部变量不影响全局
set_local <- function() {
x <- 999 # 局部变量
cat("函数内 x =", x, "\n")
}
set_local()
# 函数内 x = 999
cat("全局 x =", x, "\n")
# 全局 x = 10 ← 全局 x 没变
global)。要修改用 <<-(但不推荐)。
10. 完整示例:销售报告工具函数
下面是一个完整工作流示例,把本课所有函数特性串起来。
▶ 示例:销售报告工具函数
# ============================================
# 销售报告工具函数
# 功能:封装常用分析逻辑,重复使用
# ============================================
# 1. 基础统计函数(返回列表)
basic_stats <- function(x) {
list(
n = length(x),
sum = sum(x),
mean = round(mean(x), 2),
median = median(x),
sd = round(sd(x), 2),
min = min(x),
max = max(x)
)
}
# 2. 格式化输出函数(invisible 返回)
print_report <- function(name, x) {
stats <- basic_stats(x)
cat("=================================\n")
cat("销售报告:", name, "\n")
cat("=================================\n")
cat("样本数:", stats$n, "\n")
cat("总销售:", stats$sum, "\n")
cat("平均销售:", stats$mean, "\n")
cat("中位数:", stats$median, "\n")
cat("标准差:", stats$sd, "\n")
cat("最小:", stats$min, " / 最大:", stats$max, "\n")
invisible(stats) # 静默返回
}
# 3. 分类函数(向量化)
classify_sales <- function(x) {
ifelse(x < 100, "低",
ifelse(x < 200, "中", "高"))
}
# 4. 主程序
sales_data <- list(
"北京" = c(120, 150, 80, 200, 175),
"上海" = c(95, 110, 130, 145, 160),
"深圳" = c(200, 220, 180, 250, 300)
)
# 对每个分店生成报告
for (city in names(sales_data)) {
sales <- sales_data[[city]]
print_report(city, sales)
# 用匿名函数批量分类
levels <- sapply(sales, function(x) classify_sales(x))
cat("\n分类汇总:\n")
print(table(levels))
cat("\n")
}
# 5. 用 do.call 合并所有分店
all_sales <- do.call(c, sales_data)
cat("=== 全公司汇总 ===\n")
print_report("全公司", all_sales)
预期输出(节选):
=================================
销售报告: 北京
=================================
样本数: 5
总销售: 725
平均销售: 145
中位数: 150
标准差: 47.7
最小: 80 / 最大: 200
分类汇总:
levels
中 高
3 2
=================================
销售报告: 上海
=================================
...
❓ 常见问题
lapply() sapply() map() 等函数里最常用📖 小节
- R 函数定义
function(参数) { 代码 },最后一行自动返回 - 4 种参数:位置、命名(
name = value)、默认(name = default)、可变(...) - 3 种返回值:最后一行(自动)、
return()(显式)、invisible()(静默不打印) - 匿名函数
function(x) x^2在lapplysapply中极其常用 - R 是函数式语言,函数是"第一公民":可赋值、可传参、可返回(闭包)
do.call(func, list)把列表展开成函数参数- R 用词法作用域,函数内默认不能修改全局变量(用
<<-但不推荐)
📝 作业
-
基础题:写一个函数
celsius_to_fahrenheit(c),把摄氏温度转华氏温度(公式:F = C × 9/5 + 32)。测试输入 0, 25, 100,验证输出 32, 77, 212。 -
基础题:写一个函数
circle_area(r),计算圆面积(pi * r^2),保留 2 位小数。测试 r = 1, 2, 5。 -
基础题:写一个带默认参数的函数
greet(name = "World", greeting = "Hello"),输出 "Hello, World!" 或 "Hi, Alice!",测试 3 种调用方式。 -
进阶题:写一个函数
analyze_scores(scores),接受一个数值向量,返回一个 list(包含meanmedianmaxminpass_rate及格率),用invisible()防止自动打印。调用后用result$pass_rate访问及格率。 -
挑战题:写一个返回函数的函数
make_power(n),返回function(x) x^n。用make_power(2)创建square,用make_power(3)创建cube,测试square(5) = 25,cube(3) = 27。截图保存。