R: R 函数:自定义函数与函数式编程

最后更新:2026-08-26

前面 6 课我们都在用 R 内置函数sum() mean() c())。但真正让 R 强大的,是你能自己写函数。这一课我们学 R 的"自定义函数"——把重复代码封装成可复用的工具。

R 是函数式编程语言,函数是"第一公民"(first-class citizen)。这意味着函数可以赋值给变量、作为参数传递、作为返回值。这一课我们要掌握 R 函数的所有特性。

1. 你将学到



2. 一个代码复用的故事

(1) 痛点:重复代码

小陈是数据分析师,每天要做 10 次"把销售数据转成报表"。每次他都重复写:

R
# 重复 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) 函数的解法

R
# 写一个函数(只写 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) 函数定义

R
# 最简单的函数
greet <- function() {
  cat("Hello, World!\n")
}

# 调用
greet()
# Output:Hello, World!

(2) 带参数的函数

R
# 单个参数
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
# R 的特殊:最后一行自动返回(不需要 return)
add <- function(a, b) {
  a + b   # 最后一行自动返回
}

result <- add(3, 4)
result
# [1] 7


4. 4 种参数

(1) 位置参数 vs 命名参数

R
# 函数定义: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) 默认参数

R
# 第二个参数有默认值
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 的参数匹配规则:

100%
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
  1. 完全匹配exp = 3(最明确)
  2. 前缀匹配e = 3exp(少打字)
  3. 位置匹配:第一个未命名参数 → base(最不安全)
💡 提示生产代码用完全匹配exp = 3),少打字省不了多少时间,但能避免歧义。

(4) 可变参数 ...

... 表示"任意数量参数",常用于包装函数:

R
# 用 ... 接受任意参数
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 的函数最后一行表达式会自动返回:

R
add <- function(a, b) {
  a + b   # 最后一行,自动返回
}

add(3, 4)
# [1] 7

(2) 显式 return()

R
# 用 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() 让函数不自动打印返回值(但仍可赋值):

R
# 普通函数
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 支持匿名函数(没有名字的函数):

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
💡 提示匿名函数在 apply 系列函数中极其常用——第 8 课会深入学。



7. 函数作为对象(R 函数式编程核心)

R 的函数是"第一公民",意味着函数可以:

(1) 赋值给变量

R
f <- function(x) x * 2
g <- f  # 同一个函数有两个名字

f(5)  # [1] 10
g(5)  # [1] 10  ← 等价

(2) 作为参数传递

R
# 接受函数作为参数的函数
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) 作为返回值

R
# 返回函数的函数(闭包)
make_multiplier <- function(k) {
  function(x) x * k
}

# 创建不同倍率的函数
double <- make_multiplier(2)
triple <- make_multiplier(3)

double(5)  # [1] 10
triple(5)  # [1] 15
💡 提示:函数作为返回值是 R "函数式编程"的核心概念,tidyverse 的很多函数(map() reduce())就用这个。



8. do.call():动态调用

do.call()列表/向量展开成函数参数:

R
# 普通调用
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):

R
# 全局变量
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 没变
⚠️ 注意:R 函数默认不能修改全局变量(不像 Python 的 global)。要修改用 <<-(但不推荐)。



10. 完整示例:销售报告工具函数

下面是一个完整工作流示例,把本课所有函数特性串起来。

▶ 示例:销售报告工具函数

R 📖 仅展示
# ============================================
# 销售报告工具函数
# 功能:封装常用分析逻辑,重复使用
# ============================================

# 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)
逻辑代码 44 行(超过 40 行限制,仅展示)

预期输出(节选):

TEXT 📖 仅展示
=================================
销售报告: 北京
=================================
样本数: 5
总销售: 725
平均销售: 145
中位数: 150
标准差: 47.7
最小: 80 / 最大: 200

分类汇总:
levels
中 高 
3  2 

=================================
销售报告: 上海
=================================
...

❓ 常见问题

Q 匿名函数什么时候用?
Alapply() sapply() map() 等函数里最常用

📖 小节


📝 作业

  1. 基础题:写一个函数 celsius_to_fahrenheit(c),把摄氏温度转华氏温度(公式:F = C × 9/5 + 32)。测试输入 0, 25, 100,验证输出 32, 77, 212。

  2. 基础题:写一个函数 circle_area(r),计算圆面积(pi * r^2),保留 2 位小数。测试 r = 1, 2, 5。

  3. 基础题:写一个带默认参数的函数 greet(name = "World", greeting = "Hello"),输出 "Hello, World!" 或 "Hi, Alice!",测试 3 种调用方式。

  4. 进阶题:写一个函数 analyze_scores(scores),接受一个数值向量,返回一个 list(包含 mean median max min pass_rate 及格率),用 invisible() 防止自动打印。调用后用 result$pass_rate 访问及格率。

  5. 挑战题:写一个返回函数的函数 make_power(n),返回 function(x) x^n。用 make_power(2) 创建 square,用 make_power(3) 创建 cube,测试 square(5) = 25cube(3) = 27。截图保存。

Web-Tutorial.com

Web-Tutorial 技术团队

由多位开发者共同维护的编程教程平台。每篇教程由对应领域的开发者编写和审核,确保内容准确可靠。如发现任何问题,欢迎向我们反馈。

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏