10  R中的函数

10.1 本章学习目标

通过本章学习,你将能够:

  • 理解什么是函数,为什么要写函数
  • 掌握 R 函数的三个组成部分
  • 学会定义带有不同参数类型的函数
  • 理解返回值的工作方式
  • 知道如何处理函数中的错误
  • 能够独立编写解决实际问题的函数

10.2 什么是函数?

你已经用过很多 R 自带的函数:

Code
mean(1:10)
[1] 5.5
Code
sd(1:10)
[1] 3.02765
Code
sum(1:10)
[1] 55

👉 meansdsum 都是 R 已经帮你写好的函数。你只需要调用它们。

但是,如果 R 没有你需要的函数呢?比如你想算”身高的 BMI 指数”,R 没有 calc_bmi() 这个函数。

👉 这时你就需要自己写一个函数


10.3 为什么要写函数?

假设你有三个班级的成绩数据,需要分别计算平均分:

# 不写函数:重复劳动
class1 <- c(80, 85, 90, 78, 92)
mean(class1); sd(class1); median(class1)

class2 <- c(75, 88, 82, 91, 79)
mean(class2); sd(class2); median(class2)

class3 <- c(88, 84, 76, 95, 81)
mean(class3); sd(class3); median(class3)

每次都要把 mean()sd()median() 写一遍,很容易漏掉或写错。


# 写函数:一次定义,反复使用
describe <- function(x) {
  list(mean = mean(x), sd = sd(x), median = median(x))
}
describe(class1)
describe(class2)
describe(class3)

👉 函数的本质:把一段可重复使用的代码封装起来,取一个名字。之后每次需要这段逻辑时,调用函数名即可,不用再重写代码。


10.4 函数的三个组成部分

R 用 function 关键字创建函数。一个函数由三部分组成:

函数名 <- function(参数列表) {
  函数体
}
部分 写在 作用
参数列表 小括号 ( ) 函数的”输入”,告诉函数要处理什么数据
函数体 大括号 { } 函数的”计算过程”,具体怎么处理数据
返回值 函数体的最后一行,或 return() 函数的”输出”,把计算结果还回去

👉 可以用 formals()body()environment() 来查看一个函数的三个组成部分:

Code
f02 <- function(x, y) {
  x + y
}

formals(f02)      # 查看参数
$x


$y
Code
body(f02)         # 查看函数体
{
    x + y
}
Code
environment(f02)  # 查看函数所在环境
<environment: R_GlobalEnv>

10.5 第一个函数:从最简单的开始

Code
# 写一个求平方的函数
square <- function(x) {
  x^2
}

# 调用函数
square(5)    # 25
[1] 25
Code
square(10)   # 100
[1] 100

👉 整个流程:

  1. square ← 给函数取名字
  2. function(x) ← 告诉 R:这个函数接受一个参数 x
  3. { x^2 } ← 函数体:计算 x 的平方
  4. square(5) ← 调用函数时,5 被赋值给 x,然后计算 5^2,返回 25

10.6 练习 1:最基本的函数

写一个函数 `cube()`,输入一个数,返回它的立方(三次方)。
写一个函数 `double_it()`,输入一个数,返回它的两倍。
  • 参考答案
Code
# 立方
cube <- function(x) {
  x^3
}
#cube(3)   # 27
#cube(5)   # 125

# 加倍
double_it <- function(x) {
  2 * x
}
#double_it(7)   # 14
#double_it(10)  # 20

10.7 参数:无参数、有参数、默认值

  • 无参数

最简单的函数,不需要任何输入,每次执行一样的事情:

Code
say_hello <- function() {
  "Hello, World!"
}

say_hello()   # 注意:即使没有参数,括号也必须写
[1] "Hello, World!"

👉 忘记写 () 是新手最常见的错误之一say_hello(不带括号)返回的是函数本身,不是执行结果。


  • 有参数

参数就是函数的”输入”。不同输入 → 不同输出:

Code
greet <- function(name) {
  paste0("你好,", name, "!")
}

greet("小明")
[1] "你好,小明!"

  • 带默认值的参数

给参数设定默认值:调用时如果省略该参数,就自动使用默认值。

Code
add <- function(x, y = 10) {
  x + y
}

add(5)       # 15(y 自动用默认值 10)
[1] 15
Code
add(5, 20)   # 25(y 被指定为 20,覆盖了默认值)
[1] 25

👉 有默认值的参数是可选的;没有默认值的参数是必须提供的


10.8 参数(二):按位置 vs 按名称

调用函数时有两种传参方式:

Code
divide <- function(x, y) {
  x / y
}

# 按位置:第 1 个参数给 x,第 2 个给 y
divide(10, 2)   # 5
[1] 5

👉 常见陷阱:按位置传参时,顺序搞反了结果完全不对:

Code
divide(2, 10)   # 0.2 —— 不是 5!因为 x=2, y=10
[1] 0.2

👉 安全建议:不确定参数顺序时,直接写参数名。


10.9 练习2:参数的使用

写一个 `power()` 函数,计算 `x``n` 次方。`n` 的默认值是 2(默认求平方)
写一个 `calc_bmi()` 函数,输入身高(米)和体重(千克),返回 BMI。公式:BMI = 体重 / 身高的平方。
  • 参考答案
Code
# 2-1 幂函数(n 默认 2)
power <- function(x, n = 2) {
  x^n
}
#power(5)       # 25(默认平方)
#power(5, 3)    # 
# 2-2 BMI 计算
calc_bmi <- function(weight, height) {
  weight / (height^2)
}
#calc_bmi(70, 1.75)   # 22.86

10.10 返回值(一):两种返回方式

R 函数有两种方式返回结果:

  • 隐式返回(最常用)

不写 return(),函数自动把最后一个表达式的值返回:

Code
add <- function(x, y) {
  x + y       # 最后一个表达式,自动返回它的值
}
add(3, 7)     # 10
[1] 10
  • 显式返回

return() 明确指定返回什么。一旦执行到 return(),函数立即退出:

Code
check <- function(score) {
  if (score >= 60) {
    return("及格")   
  } else
  return("不及格")
}

check(75)   # "及格"
[1] "及格"
Code
check(55)   # "不及格"
[1] "不及格"

👉 return() 的两个作用

  1. 返回值
  2. 立即终止函数(后面的代码都不会运行)

👉 什么时候必须用 return()

  • 需要在函数中间就退出(条件判断后提前返回)
  • 不需要提前退出时,放最后一行就更简洁

10.11 返回值(二):返回多个值

R 函数一次只能返回一个东西。如果要返回多个结果,用 list() 把它们打包在一起:

Code
describe <- function(x) {
  list(
    n      = length(x),
    mean   = mean(x),
    median = median(x),
    sd     = sd(x)
  )
}

result <- describe(c(1, 2, 3, 4, 5, 6, 7, 8, 9, 10))
result         # 查看全部
$n
[1] 10

$mean
[1] 5.5

$median
[1] 5.5

$sd
[1] 3.02765
Code
result$mean    # 单独取出均值
[1] 5.5
Code
result$sd      # 单独取出标准差
[1] 3.02765

👉 $ 符号用于从 list 中取出单个元素。


10.12 练习 3:返回值

写一个 `is_even()` 函数,判断一个数是不是偶数,返回 `TRUE``FALSE`。提示:用 `%%` 取余数。
  • 参考答案
Code
# 3-1 判断偶数
is_even <- function(x) {
  x %% 2 == 0
}
is_even(4)     # TRUE
[1] TRUE
Code
is_even(7)     # FALSE
[1] FALSE
Code
is_even(1:10)  # 对向量也有效
 [1] FALSE  TRUE FALSE  TRUE FALSE  TRUE FALSE  TRUE FALSE  TRUE

10.13 错误处理

当用户传入了不合法的参数时,函数应该报错提醒,而不是悄悄算出错误结果。

  • stop():报告错误并停止
Code
safe_log <- function(x) {
  if (!is.numeric(x)) stop("x 必须是数值类型")
  if (x <= 0)         stop("x 必须是正数")
  log(x)
}

safe_log(10)    # 正常计算
[1] 2.302585
Code
# safe_log(-5)  # 报错:x 必须是正数
# safe_log("a") # 报错:x 必须是数值类型

👉 stop() 就像紧急刹车。一旦执行到它,函数立刻停止,并打印红色报错信息。这样可以”早发现、早解决”,防止错误数据悄悄传播下去。


10.14 练习 4:错误处理

写一个 `safe_sqrt()` 函数,如果输入是负数,用 `stop()` 报错:"不能对负数开平方根"

  • 参考答案
Code
#安全开根号
safe_sqrt <- function(x) {
  if (x < 0) stop("不能对负数开平方根")
  sqrt(x)
}
safe_sqrt(9)    # 3
[1] 3
Code
# safe_sqrt(-4) # 报错

10.15 原函数(Primitive Functions)

R 中有些函数是用 C 语言直接写的,运行速度非常快。可以用 is.primitive() 检查:

Code
sum                # 查看 sum 函数
function (..., na.rm = FALSE)  .Primitive("sum")
Code
is.primitive(sum)  # TRUE —— 底层 C 语言编写
[1] TRUE
Code
mean
function (x, ...) 
UseMethod("mean")
<bytecode: 0x0000021c68b33038>
<environment: namespace:base>
Code
is.primitive(mean) # FALSE —— 用 R 语言编写
[1] FALSE

常见的原函数:sum+-*/c[ 等。它们在 R 启动时就已经加载好了。


10.16 匿名函数

不需要给函数取名字,可以直接在调用处定义。常用在 integrate()sapply() 等函数中:

Code
# 不给函数取名,直接写在 integrate() 里面
integrate(function(x) sin(x)^2, 0, pi)
1.570796 with absolute error < 1.7e-14

等价于:

Code
f <- function(x) sin(x)^2
integrate(f, 0, pi)
1.570796 with absolute error < 1.7e-14

👉 匿名函数适合只用一次的情况,没必要单独命名。


10.17 常见错误总结

错误 原因 正确做法
调用时忘记写 () 只写了函数名 say_hello() 而不是 say_hello
参数顺序搞反 按位置传参时搞错对应关系 用参数名:divide(y=2, x=10)
做了计算但没有返回 结果不是最后一个表达式,也没用 return() 结果放最后一行,或用 return(结果)
大括号不匹配 少写或多写 {} RStudio 中按 Ctrl + I 自动对齐检查
忘记函数只能返回一个值 写了多个独立的值 list() 打包:list(a=1, b=2)

10.18 本章总结

  • 定义函数函数名 <- function(参数) { 函数体 }
  • 参数:可以没有参数、有参数、设默认值;调用时可以按位置或按名称传参
  • 返回值:最后一行自动隐式返回;需要提前退出时用 return();多个值用 list() 打包
  • 错误处理stop() 报错并停止;try() 尝试执行不中断
  • 匿名函数:只用一次时直接写 function(x) { ... },不必取名
  • 原函数sum+ 等底层函数,运行速度快

👉 学函数的关键不是看懂,而是写熟。 每学一个知识点,自己打开 RStudio 动手写一遍。