13.5 函数式编程
如LISP和Haskell的函数式编程语言的概念已被引入到R中。你无须了解任何函数式编程就可以使用它们7。你只需要知道,这些功能在数据操作时很有用。
7如有兴趣可以参考wordIQ.com上的一个很好的介绍。
Negate
函数接受一个谓词(predicate,即一个返回逻辑向量的函数),并返回另一个刚好相反的谓词8。当输入是FLASE
时,它将返回TRUE
;输入是FALSE
,它返回TRUE
:
8从技术上讲,谓词predicate是一个返回单个逻辑值的函数,我们在这里滥用了这个词,尽管这个词的矢量版本还没有被创造出来。我比较喜欢施瓦辛格式的predicator,但在它流行之前还是沿用predicate吧。
ct2 <- deer_endocranial_volume$VolCT2 # 为了方便输入
isnt.na <- Negate(is.na)
identical(isnt.na(ct2), !is.na(ct2))
## [1] TRUE
Filter
可以接受两个参数:第一个参数为返回一个逻辑向量的函数;其次为一个输入向量,它只有在函数返回为TRUE
时才返回那些值:
Filter(isnt.na, ct2)
## [1] 346 303 375 413 408 394 417 345 354
Position
函数的行为有点像第4.2节的which
函数。它将返回第一个把谓词应用于矢量上而返回TRUE
的索引下标:
Position(isnt.na, ct2)
## [1] 7
Find
与Position
类似,但它返回的是第一个值而不是第一个索引:
Find(isnt.na, ct2)
## [1] 346
Map
函数将一个函数应用于输入参数中的每个元素上。它只是使用了SIMPLIFY = FALSE
选项的mapply
函数的封装函数。在下例中,我们将使用以上各种方法取得马鹿数据集中每头鹿测量的平均值。首先,我们需要一个用于传递给Map
的函数,它能查找出每一个鹿头骨的体积:
get_volume <- function(ct, bead, lwh, finarelli, ct2, bead2, lwh2)
{
# 如果有第二次测量,取平均值
if(!is.na(ct2))
{
ct <- (ct + ct2) / 2
bead <- (bead + bead2) / 2
lwh <- (lwh + lwh2) / 2
}
# 把lwh除以4,使它与其他测量结果看齐
c(ct = ct, bead = bead, lwh.4 = lwh / 4, finarelli = finarelli)
}
然后,Map
的行为就和mapply
一样:它接收一个函数作为第一个参数,然后其他所有的参数会逐个传递给它:
measurements_by_deer <- with(
deer_endocranial_volume,
Map(
get_volume,
VolCT,
VolBead,
VolLWH,
VolFinarelli,
VolCT2,
VolBead2,
VolLWH2
)
)
head(measurements_by_deer)
## [[1]]
## ct bead lwh.4 finarelli
## 389 375 371 337
##
## [[2]]
## ct bead lwh.4 finarelli
## 389.0 370.0 430.5 377.0
##
## [[3]]
## ct bead lwh.4 finarelli
## 352.0 345.0 373.8 328.0
##
## [[4]]
## ct bead lwh.4 finarelli
## 388.0 370.0 420.8 377.0
##
## [[5]]
## ct bead lwh.4 finarelli
## 375.0 355.0 364.5 328.0
##
## [[6]]
## ct bead lwh.4 finarelli
## 325.0 320.0 340.8 291.0
Reduce
函数能把一个二元函数转变为一个接受多个输入的函数。例如,+
运算符能计算两个数字的总和,但sum
函数能计算出多个输入的总和。sum(a, b, c, d, e)
(大致)相当于Reduce("+", list(a, b, c, d, e))
。
我们可以定义一个简单的二元函数来(并行地)计算出两个输入值中的最大值:
pmax2 <- function(x, y) ifelse(x >= y, x, y)
如果对这个函数进行reduce操作,那么它能接受一系列的输入(正如在R基础包中的pmax
函数一样):
Reduce(pmax2, measurements_by_deer)
## ct bead lwh.4 finarelli
## 418.00 405.00 463.75 419.00
Reduce
的一个限制条件是,它将对其输入成对地反复调用二元函数,也就是说:
Reduce("+", list(a, b, c, d, e))
下面的用法也是一样的:
((((a + b) + c) + d) + e)
这意味着,它不能用于例如求平均值这样的计算,因为:
mean(mean(mean(mean(a, b), c), d), e) != mean(a, b, c, d, e)