R语言中的apply函数族

2020-04-13 13:01:42 浏览数 (1)

前言

apply函数族是R语言中数据处理的一组核心函数,通过使用apply函数,我们可以实现对数据的循环、分组、过滤、类型控制等操作。但是,由于在R语言中apply函数与其他语言循环体的处理思路是完全不一样的,所以apply函数族一直是初学者玩不转的一类核心函数。很多R语言新手,写了很多的for循环代码,也不愿意多花点时间把apply函数的使用方法了解清楚,最后把R代码写的跟C似得。

简介

由于R语言的apply家族函数是用C写的,所以使用apply进行遍历的执行效率远远高于自己编写的循环语句。为了面向不同的数据类型,不同的返回值,apply函数组成了一个函数族,包括了8个功能类似的函数,具体如下表所示。下面我们一个一个来介绍。

apply函数

apply函数是最常用的代替for循环的函数。apply函数可以对矩阵、数据框、数组(二维、多维),按行或列进行循环计算,对子元素进行迭代,并把子元素以参数传递的形式给自定义的FUN函数中,并返回计算结果。调用格式如下:

代码语言:javascript复制
apply(X, MARGIN, FUN, ...)

X: 是一个数组(array),也就是说输入必须都是相同类型的数据,要么都是数值型,要么都是字符型。如果是一个混合数据类型的data.frame,那么就会尝试用as.matrix强制转换数据。 MARGIN:表示对行(1)或者是对列(2)应用函数。 FUN: 可是R自带函数,如mean,sum等。也可以是自己编写的函数。 ... :FUN中的额外参数。

现在假设我们需要对一个矩阵的每一行求和,那么用apply怎么实现呢?

代码语言:javascript复制
x<-matrix(1:12, ncol=3)apply(x, 1, sum)[1] 15 18 21 24

一行代码搞定!当然你说可以使用 rowSums(x)也一样能得到结果,但是如果稍微复杂点,rowSums函数就不行了。比如说让数据框的x1列加1,并计算出x1,x2列的均值,这个时候就需要利用apply调用自定义函数了,可以说这才是apply强大的真正原因。

第一步,生成matrix

代码语言:javascript复制
x <- cbind(x1 = 10, x2 = c(1:4, 2:5)); x     x1 x2[1,] 10  1[2,] 10  2[3,] 10  3[4,] 10  4[5,] 10  2[6,] 10  3[7,] 10  4[8,] 10  5

第二步,自定义函数myFUN,第一个参数x为数据, 第二、三个参数为自定义参数,可以通过apply的'...'进行传入。

代码语言:javascript复制
myFUN <- function(x, c1, c2) {  c(sum(x[c1], 1), mean(x[c2])) }

第三步,通过apply调用上面自定义的函数

代码语言:javascript复制
apply(x, 1, myFUN, c1='x1', c2=c('x1', 'x2'))     [,1] [,2] [,3] [,4] [,5] [,6] [,7] [,8][1,] 11.0   11 11.0   11   11 11.0   11 11.0[2,]  5.5    6  6.5    7    6  6.5    7  7.5

是不是很简单,通过for循环的方式,也可以很容易的实现上面计算过程,但是需要一些额外的操作,比如构建循环体、定义结果数据集、合并每次循环的结果到结果数据集。

lapply函数

lapply函数是一个最基础循环操作函数之一,用来对list、data.frame数据集进行循环,并返回和X长度同样的list结构作为结果集,通过lapply的开头的第一个字母’l’就可以判断返回结果集的类型。

下面以计算list中的每个元素对应数据的分位数为例,展示该函数的特性。

代码语言:javascript复制
# 构建一个list数据集x,分别包括a,b,c 三个KEY值。x <- list(a = 5:20, b = rnorm(6,10,5), c = c(FALSE,FALSE,TRUE,TRUE,TRUE,TRUE))# 分别计算每个KEY对应数据的分位数。lapply(x, fivenum)$`a`[1]  5.0  8.5 12.5 16.5 20.0
$b[1]  3.772836 11.750169 14.611470 17.395703 23.898115
$c[1] 0 0 1 1 1

可以看到,lapply很方便地把list数据集进行循环操作了,此外,它还可以对data.frame数据集按列进行循环,但如果传入的数据集是一个向量或矩阵对象,那么直接使用lapply就不能达到想要的效果了,lapply会分别循环矩阵中的每个值,而不是按行或按列进行分组计算。

代码语言:javascript复制
x <- cbind(x=3, y=c(2:1, 4:5))lapply(data.frame(x), sum)$x[1] 12
$y[1] 12

sapply函数

sapply函数是一个简化版的lapply,sapply增加了2个参数simplify和USE.NAMES,主要就是让输出看起来更友好,返回值为向量,而不是list对象。

vapply函数

vapply类似于sapply,提供了FUN.VALUE参数,用来控制返回值的行名,这样可以让程序更健壮。

mapply函数

mapply是sapply的变形函数,类似多变量的sapply,但是参数定义有些变化。第一参数为自定义的FUN函数,第二个参数’…’可以接收多个数据,作为FUN函数的参数调用。比如,比较3个向量大小,按索引顺序取较大的值。

代码语言:javascript复制
# 定义3个向量x <- 4:10y <- 10:4z <- round(runif(7, -5, 5))
# 按索引顺序取较大的值。mapply(max, x, y, z)[1] 10  9  8  7  8  9 10

又比如想生成4个符合正态分布的数据集,分别对应的均值和方差为c(1,10,100,1000)。

代码语言:javascript复制
# m为均值,v为方差m <- v <- c(1, 10, 100, 1000)
# 生成4组数据,按列分组mapply(rnorm, rep(4,4), m, v)[,1]      [,2]      [,3]      [,4][1,] 1.1321892 11.119809 -12.65840  525.4851[2,] 3.3765492  8.032937 -44.84486  700.5648[3,] 0.1849429  6.717259 157.55199 1430.6167[4,] 1.8121539 15.392016  32.86186 1621.0613

tapply函数

tapply用于分组的循环计算,通过INDEX参数可以把数据集X进行分组,相当于group by的操作。例如,计算不同品种的鸢尾花的花瓣(iris)长度的均值。

代码语言:javascript复制
# 通过iris$Species品种进行分组tapply(iris$Petal.Length, iris$Species, mean)    setosa versicolor  virginica      1.462      4.260      5.552

rapply函数

rapply是一个递归版本的lapply,它只处理list类型数据,对list的每个元素进行递归遍历,如果list包括子元素则继续遍历。例如,对一个list的数据进行过滤,把所有数字型的数据进行从小到大的排序。

代码语言:javascript复制
x <- list(a=12, b=1:4, c=c('b', 'a'))y <- data.frame(a=rnorm(10), b=1:10)lst <- list(x=x, y=y)rapply(lst, sort, classes='numeric', how='replace')$`x`$`x`$`a`[1] 12
$`x`$b[1] 1 2 3 4
$`x`$c[1] "b" "a"

$ya  b1  -2.28818912  12  -1.11604471  23  -0.71061407  34  -0.47929464  45  -0.45064871  56  -0.32539516  67  -0.09147763  78   0.32032781  89   1.67678507  910  1.71896910 10

eapply函数

对一个环境空间中的所有变量进行遍历。eapply函数平时很难被用到,但对于R包开发来说,环境空间的使用是必须要掌握的。特别是当R要做为工业化的工具时,对变量的精确控制和管理是非常必要的。

总结

从上面这8个函数的参数定义,我们可以发现它们都接收一个函数作为它的参数,在编程的世界里,这种把函数作为参数传入的函数称为高阶函数,函数式编程就是指这种高度抽象的编程范式。这种编程范式与面向对象的范式的差异如下图

0 人点赞