前言
在上一节中,我们主要介绍了 purrr
包提供的工具函数来减少 for
循环的使用,使代码更加的简洁,便于阅读。
但是,使用 R
原生的 apply
函数家族也能够极大减少 for
循环的使用。
下面我们主要介绍 apply
函数的使用。
apply
针对不同的数据类型,会有不同的变形,共同组成了 apply
函数家族。包括
apply
, lapply
, sapply
, vapply
, tapply
, mapply
, rapply
, eapply
1. apply
1.1 描述
通过对数组或矩阵的 MARGIN
应用函数获得的向量、数组或数值列表
1.2 用法
apply(X, MARGIN, FUN, ...)
1.3 参数
-
X
: 数组、矩阵、数据框,数据至少是二维的 -
MARGIN
: 按行计算或按列计算,1
表示按行,2
表示按列 -
FUN
: 自定义的调用函数 -
...
:FUN
的可选参数
1.4 示例
现在,我们有下面一个简单矩阵
> (my.matrx <- matrix(c(1:10, 11:20, 21:30), nrow = 10, ncol = 3))
[,1] [,2] [,3]
[1,] 1 11 21
[2,] 2 12 22
[3,] 3 13 23
[4,] 4 14 24
[5,] 5 15 25
[6,] 6 16 26
[7,] 7 17 27
[8,] 8 18 28
[9,] 9 19 29
[10,] 10 20 30
我们可以对每一行求和
> apply(my.matrx, 1, sum)
[1] 33 36 39 42 45 48 51 54 57 60
计算每一列的长度
> apply(my.matrx, 2, length)
[1] 10 10 10
传递自定义匿名函数
> apply(my.matrx, 2, function (x) length(x)-1)
[1] 9 9 9
使用定义在外部的函数
> st.err <- function(x){
+ sd(x)/sqrt(length(x))
+ }
> apply(my.matrx,2, st.err)
[1] 0.9574271 0.9574271 0.9574271
转换数据
现在来点不一样的,在前面的示例中,我们使用 apply
对行或列应用函数。
其实,还可以对矩阵的单元格元素应用函数,如果您将 MARGIN
设置为 1:2
,那么该函数将对每个单元格进行操作
> (my.matrx2 <- apply(my.matrx, 1:2, function(x) x+3))
[,1] [,2] [,3]
[1,] 4 14 24
[2,] 5 15 25
[3,] 6 16 26
[4,] 7 17 27
[5,] 8 18 28
[6,] 9 19 29
[7,] 10 20 30
[8,] 11 21 31
[9,] 12 22 32
[10,] 13 23 33
2. lappy
2.1 描述
将函数应用于输入变量的每一个元素,并返回与输入变量长度相同的 list
2.2 用法
apply(X, MARGIN, FUN, ...)
2.3 参数
-
X
:list
、data.frame
、vector
等 -
FUN
: 自定义的调用函数 -
...
:FUN
的可选参数
2.4 示例
假设有下面的变量
> (vec <- c(1:10))
[1] 1 2 3 4 5 6 7 8 9 10
对每个元素求和
> str(lapply(vec, sum))
List of 10
$ : int 1
$ : int 2
$ : int 3
$ : int 4
$ : int 5
$ : int 6
$ : int 7
$ : int 8
$ : int 9
$ : int 10
返回一个长度为 10
的 list
,它并没有像我们期望的一样,返回一个 1-10
之和。
因为,lapply
会将向量视为列表,并将函数应用于每个元素上。
让我们将输入变为一个列表
> A<-c(1:9)
> B<-c(1:12)
> C<-c(1:15)
> my.lst<-list(A,B,C)
> lapply(my.lst, sum)
[[1]]
[1] 45
[[2]]
[1] 78
[[3]]
[1] 120
函数对列表中的每个向量求和,并返回一个长度为 3
的列表
如果输入的是一个矩阵
> (x <- matrix(1:12, nrow = 3))
[,1] [,2] [,3] [,4]
[1,] 1 4 7 10
[2,] 2 5 8 11
[3,] 3 6 9 12
> str(lapply(x, sum))
List of 12
$ : int 1
$ : int 2
$ : int 3
$ : int 4
$ : int 5
$ : int 6
$ : int 7
$ : int 8
$ : int 9
$ : int 10
$ : int 11
$ : int 12
lapply
会循环矩阵中的每个值,而不是按行或按列进行分组计算。
那如果对一个 data.frame
求和呢?
> str(lapply(as.data.frame(x), sum))
List of 4
$ V1: int 6
$ V2: int 15
$ V3: int 24
$ V4: int 33
可以看到,lapply
会自动对 data.frame
的列分组求和
3. sapply
3.1 描述
sapply
的工作原理和 lapply
一样,但是如果可能的话,它会简化输出。
这意味着,如果数据可以简化,它将返回一个向量,而不是像 lappy
那样总是返回一个列表。
3.2 用法
sapply(X, FUN, ..., simplify = TRUE, USE.NAMES = TRUE)
3.3 参数
-
X
: 数组、矩阵、数据框 -
FUN
: 自定义的调用函数 -
...
:FUN
的可选参数 -
simplify
: 是否简化为向量、矩阵或高维数组,当值array
时,输出结果按数组进行分组 -
USE.NAMES
: 如果X
为字符串且未设置名称,当该值为TRUE
是设置字符串为数据名称,FALSE
不设置
3.4 示例
对向量使用 sapply
求和,返回一个长度相等的向量
> vec
[1] 1 2 3 4 5 6 7 8 9 10
> sapply(vec, sum)
[1] 1 2 3 4 5 6 7 8 9 10
对列表求和
> my.lst
[[1]]
[1] 1 2 3 4 5 6 7 8 9
[[2]]
[1] 1 2 3 4 5 6 7 8 9 10 11 12
[[3]]
[1] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
> sapply(my.lst, sum)
[1] 45 78 120
返回的还是一个向量
传入矩阵和 data.frame
> sapply(data.frame(x), sum)
X1 X2 X3 X4
6 15 24 33
> sapply(x, sum)
[1] 1 2 3 4 5 6 7 8 9 10 11 12
如果同时设置了 simplify=FALSE
和 USE.NAMES=FALSE
,那么 sapply
和 lapply
将是一样的
> str(sapply(data.frame(x), sum, simplify=FALSE, USE.NAMES=FALSE))
List of 4
$ X1: int 6
$ X2: int 15
$ X3: int 24
$ X4: int 33
使用 simplify = 'array'
构建一个三维数组
> (v <- structure(10*(5:7), names = LETTERS[1:3]))
A B C
50 60 70
> sapply(v, function(x){outer(rep(x, 3), 2*(1:2))}, simplify = 'array')
, , A
[,1] [,2]
[1,] 100 200
[2,] 100 200
[3,] 100 200
, , B
[,1] [,2]
[1,] 120 240
[2,] 120 240
[3,] 120 240
, , C
[,1] [,2]
[1,] 140 280
[2,] 140 280
[3,] 140 280
还可以对字符串向量自动生成数据名
> (val<-head(letters))
[1] "a" "b" "c" "d" "e" "f"
> sapply(val,paste,USE.NAMES=TRUE)
a b c d e f
"a" "b" "c" "d" "e" "f"
> sapply(val,paste,USE.NAMES=FALSE)
[1] "a" "b" "c" "d" "e" "f"
4. vapply
4.1 描述
类似于 sapply
,多了一个 FUN.VALUE
参数,用于指定返回值的行名
4.2 用法
vapply(X, FUN, FUN.VALUE, ..., USE.NAMES = TRUE)
4.3 参数
-
X
: 数组、矩阵、数据框 -
FUN
: 自定义的调用函数 -
FUN.VALUE
: 定义函数返回值的模板 -
...
:FUN
的可选参数 -
USE.NAMES
: 如果X
为字符串且未设置名称,当该值为TRUE
时设置字符串为数据名称,FALSE
不设置
4.4 示例
例如,对数据框求累积和,并设置返回值的模板,为返回值设置行名
> x
V1 V2 V3 V4
1 1 4 7 10
2 2 5 8 11
3 3 6 9 12
> vapply(as.data.frame(x), cumsum, FUN.VALUE = c('a'=1, 'b'=1, 'c'=1))
V1 V2 V3 V4
a 1 4 7 10
b 3 9 15 21
c 6 15 24 33
那如何从 lapply
, sapply
, vapply
这三个函数中进行选择呢?通常尽量使用 sapply
,如果不需要简化输出,则应该使用 lapply
,如果要指定输出类型,则使用 vapply
5. mapply:
5.1 描述
mapply
也是 sapply
的变形,类似多变量 sapply
5.2 用法
mapply(FUN, ..., MoreArgs = NULL, SIMPLIFY = TRUE,USE.NAMES = TRUE)
5.3 参数
-
FUN
: 自定义的调用函数 -
...
: 接受多个数据 -
MoreArgs
: 参数列表 -
simplify
: 是否简化为向量、矩阵或高维数组,当值array
时,输出结果按数组进行分组 -
USE.NAMES
: 如果X
为字符串且未设置名称,当该值为TRUE
时设置字符串为数据名称,FALSE
不设置
5.4 示例
取三个向量对应位置的最大值
> x <- -4:5
> y <- seq(-9, 10, 2)
> z<-round(runif(10,-5,5))
> mapply(max,x,y,z)
[1] 4 0 -1 3 0 2 4 5 7 9
6. tapply
6.1 描述
用于对数据进行分组应用函数,其中 INDEX
参数可以把数据集进行分组,相当于 group by
的操作。
6.2 用法
tapply(X, INDEX, FUN = NULL, ..., simplify = TRUE)
6.3 参数
-
X
: 向量 -
INDEX
: 用于分组的索引 -
FUN
: 自定义的调用函数 -
...
: 接受多个数据 -
simplify
: 是否简化为向量、矩阵或高维数组,当值array
时,输出结果按数组进行分组
6.4 示例
首先,让我们创建一个因子数据作为索引
> (tdata <- as.data.frame(cbind(c(1,1,1,1,1,2,2,2,2,2), my.matrx)))
V1 V2 V3 V4
1 1 1 11 21
2 1 2 12 22
3 1 3 13 23
4 1 4 14 24
5 1 5 15 25
6 2 6 16 26
7 2 7 17 27
8 2 8 18 28
9 2 9 19 29
10 2 10 20 30
> colnames(tdata)
[1] "V1" "V2" "V3" "V4"
然后把第 1
列作为索引,并计算第 2
列的均值
> tapply(tdata$V2, tdata$V1, mean)
1 2
3 8
7. rapply
7.1 描述
rapply
是一个递归版本的 lapply
,只针对 list
类型的数据,对 list
的每个元素进行递归遍历,如果 list
的子元素还包含数据则继续遍历。
7.2 用法
rapply(object, f, classes = "ANY", deflt = NULL, how = c("unlist", "replace", "list"), ...)
7.3 参数
object
:list
类型数据f
: 自定义函数classes
: 匹配类型,ANY
为所有类型deflt
: 非匹配类型的默认值-
how
: 包含3
种方式:- 如果为
replace
,则用调用函数f
后的结果来替换原list
中的元素; - 当为
list
时,创建一个新的list
并调用f
函数,不匹配赋值为deflt
; - 当为
unlist
时,会执行一次unlist(recursive = TRUE)
的操作
- 如果为
...
: 可选参数
7.4 示例
假设我们有如下列表
> l
$x
$x$x
[1] 1 2 3 4 5
$x$y
[1] "a" "b" "c" "d" "e" "f" "g"
$y
[1] 7 8 9 10 11 12 13
$z
[1] "t" "u" "v" "w" "x" "y" "z"
> str(l)
List of 3
$ x:List of 2
..$ x: int [1:5] 1 2 3 4 5
..$ y: chr [1:7] "a" "b" "c" "d" ...
$ y: int [1:7] 7 8 9 10 11 12 13
$ z: chr [1:7] "t" "u" "v" "w" ...
我们想对数字进行降序排序
> rapply(l, function(x) {sort(x, decreasing = TRUE)}, classes='integer',how='replace')
$x
$x$x
[1] 5 4 3 2 1
$x$y
[1] "a" "b" "c" "d" "e" "f" "g"
$y
[1] 13 12 11 10 9 8 7
$z
[1] "t" "u" "v" "w" "x" "y" "z"
我们为字符串添加星号,并将非字符串负值为 NA
> rapply(l, function(x) paste('*', x, '*'), classes='character',how='list', deflt = NA)
$x
$x$x
[1] NA
$x$y
[1] "* a *" "* b *" "* c *" "* d *" "* e *" "* f *" "* g *"
$y
[1] NA
$z
[1] "* t *" "* u *" "* v *" "* w *" "* x *" "* y *" "* z *"
8. eapply
8.1 描述
将函数应用于环境中的命名变量中,并将结果作为列表返回。
用户可以请求使用所有命名对象(通常以点开头的名称不被使用),不会对输出进行排序,也不会搜索封闭环境
8.2 用法
eapply(env, FUN, ..., all.names = FALSE, USE.NAMES = TRUE)
8.3 参数
-
env
: 使用的环境空间 -
FUN
: 自定义函数 -
...
:FUN
的可选参数 -
all.names
: 指示是否将函数应用于所有值 -
USE.NAMES
: 如果 X 为字符串且未设置名称,当该值为TRUE
时设置字符串为数据名称,FALSE
不设置
8.4 示例
我们新建一个环境空间,然后在空间内新建 3
个对象,最后用 eapply
对所有环境空间内的变量求均值
# 新建一个环境空间
> env <- new.env(hash = FALSE)
# 为环境空间添加变量
> env$a <- 1:10
> env$beta <- exp(-3:3)
> env$logic <- c(TRUE, FALSE, FALSE, TRUE)
# 对 env 环境空间的所有变量求均值
> eapply(env, mean)
$logic
[1] 0.5
$beta
[1] 4.535125
$a
[1] 5.5
我们可以使用 ls()
函数获取环境空间内的所有变量或对象
# 获取当前环境空间内的变量或对象
> ls() %>% head()
[1] "a" "A" "a2" "args1" "args2" "B"
# 通过传入环境空间,获取该环境空间内的变量
> ls(env)
[1] "a" "beta" "logic"
获取环境空间变量占用内存大小
> eapply(env, object.size)
$logic
64 bytes
$beta
112 bytes
$a
96 bytes
一般你很少用到 eapply
,但是对于 R
包开发者来说,环境空间是很重要的,需要掌握