13.5 函数式编程

如LISP和Haskell的函数式编程语言的概念已被引入到R中。你无须了解任何函数式编程就可以使用它们7。你只需要知道,这些功能在数据操作时很有用。

7如有兴趣可以参考wordIQ.com上的一个很好的介绍。

Negate函数接受一个谓词(predicate,即一个返回逻辑向量的函数),并返回另一个刚好相反的谓词8。当输入是FLASE时,它将返回TRUE;输入是FALSE,它返回TRUE

8从技术上讲,谓词predicate是一个返回单个逻辑值的函数,我们在这里滥用了这个词,尽管这个词的矢量版本还没有被创造出来。我比较喜欢施瓦辛格式的predicator,但在它流行之前还是沿用predicate吧。

  1. ct2 <- deer_endocranial_volume$VolCT2 # 为了方便输入
  2. isnt.na <- Negate(is.na)
  3. identical(isnt.na(ct2), !is.na(ct2))
  4. ## [1] TRUE

Filter可以接受两个参数:第一个参数为返回一个逻辑向量的函数;其次为一个输入向量,它只有在函数返回为TRUE时才返回那些值:

  1. Filter(isnt.na, ct2)
  2. ## [1] 346 303 375 413 408 394 417 345 354

Position函数的行为有点像第4.2节的which函数。它将返回第一个把谓词应用于矢量上而返回TRUE的索引下标:

  1. Position(isnt.na, ct2)
  2. ## [1] 7

FindPosition类似,但它返回的是第一个值而不是第一个索引:

  1. Find(isnt.na, ct2)
  2. ## [1] 346

Map函数将一个函数应用于输入参数中的每个元素上。它只是使用了SIMPLIFY = FALSE选项的mapply函数的封装函数。在下例中,我们将使用以上各种方法取得马鹿数据集中每头鹿测量的平均值。首先,我们需要一个用于传递给Map的函数,它能查找出每一个鹿头骨的体积:

  1. get_volume <- function(ct, bead, lwh, finarelli, ct2, bead2, lwh2)
  2. {
  3. # 如果有第二次测量,取平均值 
  4. if(!is.na(ct2))
  5. {
  6. ct <- (ct + ct2) / 2
  7. bead <- (bead + bead2) / 2
  8. lwh <- (lwh + lwh2) / 2
  9. }
  10. # 把lwh除以4,使它与其他测量结果看齐
  11. c(ct = ct, bead = bead, lwh.4 = lwh / 4, finarelli = finarelli)
  12. }

然后,Map的行为就和mapply一样:它接收一个函数作为第一个参数,然后其他所有的参数会逐个传递给它:

  1. measurements_by_deer <- with(
  2. deer_endocranial_volume,
  3. Map(
  4. get_volume,
  5. VolCT,
  6. VolBead,
  7. VolLWH,
  8. VolFinarelli,
  9. VolCT2,
  10. VolBead2,
  11. VolLWH2
  12. )
  13. )
  14. head(measurements_by_deer)
  15. ## [[1]]
  16. ## ct bead lwh.4 finarelli
  17. ## 389 375 371 337
  18. ##
  19. ## [[2]]
  20. ## ct bead lwh.4 finarelli
  21. ## 389.0 370.0 430.5 377.0
  22. ##
  23. ## [[3]]
  24. ## ct bead lwh.4 finarelli
  25. ## 352.0 345.0 373.8 328.0
  26. ##
  27. ## [[4]]
  28. ## ct bead lwh.4 finarelli
  29. ## 388.0 370.0 420.8 377.0
  30. ##
  31. ## [[5]]
  32. ## ct bead lwh.4 finarelli
  33. ## 375.0 355.0 364.5 328.0
  34. ##
  35. ## [[6]]
  36. ## ct bead lwh.4 finarelli
  37. ## 325.0 320.0 340.8 291.0

Reduce函数能把一个二元函数转变为一个接受多个输入的函数。例如,+运算符能计算两个数字的总和,但sum函数能计算出多个输入的总和。sum(a, b, c, d, e)(大致)相当于Reduce("+", list(a, b, c, d, e))

我们可以定义一个简单的二元函数来(并行地)计算出两个输入值中的最大值:

  1. pmax2 <- function(x, y) ifelse(x >= y, x, y)

如果对这个函数进行reduce操作,那么它能接受一系列的输入(正如在R基础包中的pmax函数一样):

  1. Reduce(pmax2, measurements_by_deer)
  2. ## ct bead lwh.4 finarelli
  3. ## 418.00 405.00 463.75 419.00

Reduce的一个限制条件是,它将对其输入成对地反复调用二元函数,也就是说:

  1. Reduce("+", list(a, b, c, d, e))

下面的用法也是一样的:

  1. ((((a + b) + c) + d) + e)

这意味着,它不能用于例如求平均值这样的计算,因为:

  1. mean(mean(mean(mean(a, b), c), d), e) != mean(a, b, c, d, e)