9.4 遍历数组
lapply
和它的小伙伴vapply
与sapply
都可用于矩阵和数组上,但它们的行为往往不是我们想要的。这三个函数把矩阵和数组看作向量,将目标函数作用于每个元素上(沿列往下移动)。而更为常见的是,当要把函数作用于一个数组时,我们希望能按行或列应用它们。下面的例子使用matlab
包,提供了对手语言所具备的功能。
要运行下例,首先需要安装matlab
程序包:
install.packages("matlab")
library(matlab)
## Attaching package: 'matlab'
## The following object is masked from 'package:stats':
##
## reshape
## The following object is masked from 'package:utils':
##
## find, fix
## The following object is masked from 'package:base':
##
## sum
注意
当你装载
matlab
包时,它会覆盖一些在base
、stats
和utils
包中的函数,把它们的行为重载成MATLAB中相应函数的行为。当使用完matlab
包,你可能会想恢复以前的行为,这可以通过调用detach("package:matlab")
来完成。
magic
函数将创建一个f
方阵:n
×n
的、从1
排到n
^2
的数字矩阵,其行数和列数相等:
(magic4 <- magic(4))
## [,1] [,2] [,3] [,4]
## [1,] 16 2 3 13
## [2,] 5 11 10 8
## [3,] 9 7 6 12
## [4,] 4 14 15 1
一个需要我们把函数应用到每行上的经典问题——计算行的总数,可以由在第五章中简要介绍的rowSums
函数来实现:
rowSums(magic4)
## [1] 34 34 34 34
然而,如果要计算每行的其他统计值,该如何实现?要提供一个函数来实现所有可能的功能将是非常麻烦的3。apply
函数提供了类似的按行/列计算的等效函数,它以一个矩阵、维数和函数作为参数。维数为1
代表把函数应用于每一行;2
代表把函数应用于每一列(或更大的数字代表更高维的数组):
3 尽管matrixStats
包试图这样做。
apply(magic4, 1, sum) #与rowSums相同
## [1] 34 34 34 34
apply(magic4, 1, toString)
## [1] "16, 2, 3, 13" "5, 11, 10, 8" "9, 7, 6, 12" "4, 14, 15, 1"
apply(magic4, 2, toString)
## [1] "16, 5, 9, 4" "2, 11, 7, 14" "3, 10, 6, 15" "13, 8, 12, 1"
apply
也可用于数据框,尽管对于这种混合的数据类型来说不太常见(例如,如果其中有一列是字符类型的,很显然你不能在它上面计算总和或乘积):
(baldwins <- data.frame(
name = c("Alec", "Daniel", "Billy", "Stephen"),
date_of_birth = c(
"1958-Apr-03", "1960-Oct-05", "1963-Feb-21", "1966-May-12"
),
n_spouses = c(2, 3, 1, 1),
n_children = c(1, 5, 3, 2),
stringsAsFactors = FALSE
))
## name date_of_birth n_spouses n_children
## 1 Alec 1958-Apr-03 2 1
## 2 Daniel 1960-Oct-05 3 5
## 3 Billy 1963-Feb-21 1 3
## 4 Stephen 1966-May-12 1 2
apply(baldwins, 1, toString)
## [1] "Alec, 1958-Apr-03, 2, 1" "Daniel, 1960-Oct-05, 3, 5"
## [3] "Billy, 1963-Feb-21, 1, 3" "Stephen, 1966-May-12, 1, 2"
apply(baldwins, 2, toString)
## name
## "Alec, Daniel, Billy, Stephen"
## date_of_birth
## "1958-Apr-03, 1960-Oct-05, 1963-Feb-21, 1966-May-12"
## n_spouses
## "2, 3, 1, 1"
## n_children
## "1, 5, 3, 2"
当你把函数按列地应用于数据框上,apply
与sapply
的行为相同(记住,数据框可被认为是非嵌套的列表,其中的元素都具有相同的长度):
sapply(baldwins, toString)
## name
## "Alec, Daniel, Billy, Stephen"
## date_of_birth
## "1958-Apr-03, 1960-Oct-05, 1963-Feb-21, 1966-May-12"
## n_spouses
## "2, 3, 1, 1"
## n_children
## "1, 5, 3, 2"
当然,如果仅仅打印不同的形式数据集,你无法充分感受到它的趣味性。另一方面,将sapply
与range
结合使用能非常迅速地确定你的数据范围:
sapply(baldwins, range)
## name date_of_birth n_spouses n_children
## [1,] "Alec" "1958-Apr-03" "1" "1"
## [2,] "Stephen" "1966-May-12" "3" "5"