9.4 遍历数组

lapply和它的小伙伴vapplysapply都可用于矩阵和数组上,但它们的行为往往不是我们想要的。这三个函数把矩阵和数组看作向量,将目标函数作用于每个元素上(沿列往下移动)。而更为常见的是,当要把函数作用于一个数组时,我们希望能按行或列应用它们。下面的例子使用matlab包,提供了对手语言所具备的功能。

要运行下例,首先需要安装matlab程序包:

  1. install.packages("matlab")
  2. library(matlab)
  3. ## Attaching package: 'matlab'
  4. ## The following object is masked from 'package:stats':
  5. ##
  6. ## reshape
  7. ## The following object is masked from 'package:utils':
  8. ##
  9. ## find, fix
  10. ## The following object is masked from 'package:base':
  11. ##
  12. ## sum

注意

当你装载matlab包时,它会覆盖一些在basestatsutils包中的函数,把它们的行为重载成MATLAB中相应函数的行为。当使用完matlab包,你可能会想恢复以前的行为,这可以通过调用detach("package:matlab")来完成。

magic函数将创建一个f方阵:n×n的、从1排到n^2的数字矩阵,其行数和列数相等:

  1. (magic4 <- magic(4))
  2. ## [,1] [,2] [,3] [,4]
  3. ## [1,] 16 2 3 13
  4. ## [2,] 5 11 10 8
  5. ## [3,] 9 7 6 12
  6. ## [4,] 4 14 15 1

一个需要我们把函数应用到每行上的经典问题——计算行的总数,可以由在第五章中简要介绍的rowSums函数来实现:

  1. rowSums(magic4)
  2. ## [1] 34 34 34 34

然而,如果要计算每行的其他统计值,该如何实现?要提供一个函数来实现所有可能的功能将是非常麻烦的3。apply函数提供了类似的按行/列计算的等效函数,它以一个矩阵、维数和函数作为参数。维数为1代表把函数应用于每一行;2代表把函数应用于每一列(或更大的数字代表更高维的数组):

3 尽管matrixStats包试图这样做。

  1. apply(magic4, 1, sum) #与rowSums相同
  2. ## [1] 34 34 34 34
  3. apply(magic4, 1, toString)
  4. ## [1] "16, 2, 3, 13" "5, 11, 10, 8" "9, 7, 6, 12" "4, 14, 15, 1"
  5. apply(magic4, 2, toString)
  6. ## [1] "16, 5, 9, 4" "2, 11, 7, 14" "3, 10, 6, 15" "13, 8, 12, 1"

apply也可用于数据框,尽管对于这种混合的数据类型来说不太常见(例如,如果其中有一列是字符类型的,很显然你不能在它上面计算总和或乘积):

  1. (baldwins <- data.frame(
  2. name = c("Alec", "Daniel", "Billy", "Stephen"),
  3. date_of_birth = c(
  4. "1958-Apr-03", "1960-Oct-05", "1963-Feb-21", "1966-May-12"
  5. ),
  6. n_spouses = c(2, 3, 1, 1),
  7. n_children = c(1, 5, 3, 2),
  8. stringsAsFactors = FALSE
  9. ))
  10. ## name date_of_birth n_spouses n_children
  11. ## 1 Alec 1958-Apr-03 2 1
  12. ## 2 Daniel 1960-Oct-05 3 5
  13. ## 3 Billy 1963-Feb-21 1 3
  14. ## 4 Stephen 1966-May-12 1 2
  15. apply(baldwins, 1, toString)
  16. ## [1] "Alec, 1958-Apr-03, 2, 1" "Daniel, 1960-Oct-05, 3, 5"
  17. ## [3] "Billy, 1963-Feb-21, 1, 3" "Stephen, 1966-May-12, 1, 2"
  18. apply(baldwins, 2, toString)
  19. ## name
  20. ## "Alec, Daniel, Billy, Stephen"
  21. ## date_of_birth
  22. ## "1958-Apr-03, 1960-Oct-05, 1963-Feb-21, 1966-May-12"
  23. ## n_spouses
  24. ## "2, 3, 1, 1"
  25. ## n_children
  26. ## "1, 5, 3, 2"

当你把函数按列地应用于数据框上,applysapply的行为相同(记住,数据框可被认为是非嵌套的列表,其中的元素都具有相同的长度):

  1. sapply(baldwins, toString)
  2. ## name
  3. ## "Alec, Daniel, Billy, Stephen"
  4. ## date_of_birth
  5. ## "1958-Apr-03, 1960-Oct-05, 1963-Feb-21, 1966-May-12"
  6. ## n_spouses
  7. ## "2, 3, 1, 1"
  8. ## n_children
  9. ## "1, 5, 3, 2"

当然,如果仅仅打印不同的形式数据集,你无法充分感受到它的趣味性。另一方面,将sapplyrange结合使用能非常迅速地确定你的数据范围:

  1. sapply(baldwins, range)
  2. ## name date_of_birth n_spouses n_children
  3. ## [1,] "Alec" "1958-Apr-03" "1" "1"
  4. ## [2,] "Stephen" "1966-May-12" "3" "5"