R基础包base
的apply族函数采用向量化运算实现批量计算,相较于for循环语句其代码更加简洁、运行速度也更快,恰当地运用这些函数能够极大提高代码质量。本篇主要介绍apply族函数的apply()
,lapply()
,sapply()
,mapply()
和tapply()
函数。
1 apply()函数
官方文档给出的apply()
函数的语法结构如下:
apply(X, MARGIN, FUN, ...)
apply()
函数采用的是函数式编程(functional programming, FP)
虽然它表面上只有三个参数,但由于它的第三个参数FUN本身也是个函数,使得它能够继承函数FUN所有的参数,这些参数在语法结构中以三个点...
表示。
参数含义及注意事项:
X以数组(array)或矩阵(matrix)的形式储存可作为函数FUN第一个参数的多组数据;
X也可以为数据框(data.frame),但其各列数据的格式必须为同一性质(如均为数值型或均为文本型等),
apply()
函数会将其强制转化为数组再进行运算,否则会报错;X不能是向量(vector)、列表(list);
MARGIN表示维度。对于矩阵而言,MARGIN = 1时,矩阵以每行为一组,作为函数FUN的第一个参数;MARGIN = 2时,表示以每列为一组,作为函数FUN的第一个参数,MARGIN = c(1,2)时,表示以每个元素为一组,作为函数FUN的第一个参数;
对于数组而言,其可能存在更高的维度,MARGIN = n,表示以它的第n维为分组依据;MARGIN为向量时,表示以多个维度作为分组依据,如MARGIN = c(2:4),表示以第2、3、4三个维度为联合分组依据;
...
为函数FUN除第一个参数外的其他参数,在调用时一般要指明参数名称。
比如我们想计算mtcars每列变量的平均值(mtcars虽然是数据框,但是其每列变量均是数值类型),可以使用如下for循环语句:
n <- length(mtcars) # 计算变量个数,用于确定循环次数
cmean <- c(1:n) # 定义储存结果的变量
for(i in (1 : n)) {
cmean[i] <- mean(mtcars[,i])
}
使用apply()
语句如下:
cmean2 <- apply(mtcars, 2, mean)
相比于for循环,apply()
不用事先确定循环次数,也不需要引入过多的变量,运算速度也更快。
apply()
也可以在自身参数位置上调用其他函数的参数
比如我们将mtcars的部分数据改为缺失值,在计算每列的平均值时需要先删除缺失值:
dta <- mtcars
dta[1,3] <- NA
# 使用for循环
n = length(dta)
cmean <- c(1:n)
for(i in (1 : n)) {
cmean[i] <- mean(dta[,i], na.rm = TRUE) # 在mean函数内调用na.rm参数
}
# 使用apply函数
cmean2 <- apply(dta, 2, mean, na.rm = TRUE) # 在apply函数内调用mean函数的na.rm参数
需要注意的是,apply()
只能实现单参数(且为第一个参数)的向量化运算,FUN的其他参数均是全局参数
比较下面代码中a、b、c和d的区别:
X <- matrix(c(1 : 9), ncol = 3, byrow = TRUE)
Y <- matrix(c(1 : 9), ncol = 3)
a <- X * Y
b <- Y * X
c <- apply(X, 2, "*", Y)
d <- apply(Y, 2, "*", X)
“*”用于两个同型矩阵相乘表示的是Hadamard乘积,即X和Y对应位置的数值相乘,这种矩阵乘法满足交换律,因此a和b的结果一致。
c中X为分组参数且按列分组,Y为全局参数,相当于X每列构成的行向量(R中只有行向量)分别与Y相乘;d则恰好相反,是由Y每列构成的行向量与X相乘。
需要注意的是,在apply()
输出结果中各组按是按列排序的(即输出矩阵的列数与分组组数相同),因此各组相乘后的矩阵会被强制转成一列,但这与预期目的是不一致的。
由上面的例子也可以发现,apply()
的输出结果在类型上不具备一致性(可以为向量、矩阵,还可以为列表,但是不能人为控制),这也是很多情况下使用apply()
函数不能达到预期目的的原因,因此当每组输出结果不是单个数值或向量时并不推荐使用它。
2 lapply()函数
lapply()
函数的语法结构如下:
lapply(X, FUN, ...)
lapply()
函数要求X必须为list类型,或者可以转化为list的矩阵、数据框;
lapply()
函数没有MARGIN参数,以list的每个元素作为一组;当X为数据框时,数据框的每个变量(即每列)会被转化为list的一个元素;当X为矩阵时,矩阵的每个元素会被转为list的一个元素;
lapply()
函数的输出结果的类型具有一致性,是与输入参数X同样长度的list,每组结果对应list的一个元素。
X <- matrix(c(1 : 9), ncol = 3, byrow = TRUE)
Y <- matrix(c(1 : 9), ncol = 3)
# 将X按列转为list
Z <- as.list(as.data.frame(X))
c2 <- lapply(Z, "*", Y)
class(c2)
c2
# 部分输出结果
> class(c2)
[1] "list"
> c2
$V1
[,1] [,2] [,3]
[1,] 1 4 7
[2,] 8 20 32
[3,] 21 42 63
$V2
[,1] [,2] [,3]
[1,] 2 8 14
[2,] 10 25 40
[3,] 24 48 72
$V3
[,1] [,2] [,3]
[1,] 3 12 21
[2,] 12 30 48
[3,] 27 54 81
c2是一个含3个元素的列表,每个元素均为一个3*3的矩阵,分别记录每组的计算结果。相对于apply()
函数,lapply()
在计算结果较复杂时更有优势。
3 sapply()函数
sapply()
函数的语法结构如下:
sapply(X, FUN, ..., simplify = TRUE, USE.NAMES = TRUE)
sapply()
函数对输入参数的要求与lapply()
完全一致;
sapply()
函数是lapply()
的简化版,参数simplify控制是否对输出结果进行简化,默认为TRUE,即将输出结果转化为矩阵;而当simplify = FASLE时,sapply()
和lapply()
功能完全一致。
X <- matrix(c(1 : 9), ncol = 3, byrow = TRUE)
Y <- matrix(c(1 : 9), ncol = 3)
# 将X按列转为list
Z <- as.list(as.data.frame(X))
# 结果不转化为矩阵,与lapply()结果一样
c3 <- sapply(Z, "*", Y, simplify = FALSE)
# 结果转化为矩阵
c4 <- sapply(Z, "*", Y)
class(c4)
c4
# 部分输出结果
> class(c4)
[1] "matrix"
> c4
V1 V2 V3
[1,] 1 2 3
[2,] 8 10 12
[3,] 21 24 27
[4,] 4 8 12
[5,] 20 25 30
[6,] 42 48 54
[7,] 7 14 21
[8,] 32 40 48
[9,] 63 72 81
apply()
、lapply()
和sapply()
在语法和功能上都具有很大的相似性,在实际选用时可以根据输入参数类型和想要的输出参数类型进行选择。
但这三个函数都只能针对单参数进行向量化运算,要想实现多参数的向量化运算需要使用mapply()
函数。
4 mapply()函数
mapply()
函数的语法结构如下:
mapply(FUN, ..., MoreArgs = NULL, SIMPLIFY = TRUE,
USE.NAMES = TRUE)
mapply()
与前三个函数用法基本相似,主要区别有以下几点:
函数FUN是第一个参数;
...
表示的是参与向量化运算的参数,而前三个函数表示的都是全局参数;全局参数以list的形式放置在参数MoreArgs中。
X <- matrix(c(1 : 9), ncol = 3, byrow = TRUE)
fun <- function(x, y, z) (y + z)^x
mapply(fun, X[,1], X[,2], X[,3])
mapply(fun, X[,1], X[,2], MoreArgs = list(z = 2)) # z作为全局参数
mapply(fun, X[,1], MoreArgs = list(y = 2, z = 2)) # y和z作为全局参数
5 tapply()函数
tapply()
函数主要用于分组计算,语法结构如下:
tapply(X, INDEX, FUN = NULL, ..., default = NA, simplify = TRUE)
X为参与计算的指标,数据格式为向量或者数据框的某一列;
INDEX为分组指标,数据格式为向量、数据框某一列或列表。
tapply()
可以实现常规的分组计算
比如我们按mtcars的cyl分组对mpg求平均值:
tapply(mtcars$mpg, mtcars$cyl, mean)
当分组指标为多个时,要写成list的形式
比如我们按mtcars的cyl和gear两列分组对mpg求平均值:
tapply(mtcars$mpg, list(mtcars$cyl, mtcars$gear), mean)