apply包

众所周知,当我们利用R语言处理大型数据集时,for循环语句的运算效率非常低。因此在实际应用中,数据处理人员常常利用apply族函数替代for循环以便于在一定程度上提升R语言数据处理速度,除此之外,该方法还可以在一定程度上简化代码。但是,我可能已经习惯了for、while的循环套循环,有时自己也晕了,也难怪大佬会严重鄙视只会写R的码农(中枪),因此这节将介绍apply()族的函数。

1. apply()函数

apply(X, MARGIN, FUN, ...)

X:数组、矩阵、数据框
MARGIN: 参数MARGIN是apply()应用的维度,MARGIN=1表示矩阵和数组的行,MARGIN=2表示矩阵和数组的列,MARGIN=c(1,2)则是对行和列都进行操作。
FUN: 自定义的调用函数,可带有参数

apply函数可以对矩阵、数据框、数组(二维、多维),按行或列进行循环计算,对子元素进行迭代,并把子元素以参数传递的形式给自定义的FUN函数中,并以返回计算结果。因此可以代替很多for的迭代。
先来一个matrix的例子:

# 建立一个五行六列的矩阵x
> x <- matrix(rnorm(30), nrow=5, ncol=6)
> print(x)
       [,1]    [,2]   [,3]    [,4]   [,5]   [,6]
[1,] -0.281 -0.8657 -0.617  0.0553 -2.007  1.811
[2,]  0.812 -0.0599 -0.262 -1.0007  0.768 -2.550
[3,] -0.208  0.0115  0.912 -0.7582  0.406  1.150
[4,]  1.451  0.2691  1.124 -0.6114 -0.832  1.228
[5,]  0.841 -0.5282 -0.604 -1.3167  0.958  0.152
> dimnames(x)[[1]] <- letters[1:5]    #把行的名字改为a~e
> dimnames(x)[[2]] <- letters[1:6]    #把列的名字改为a~f
> apply(x, 2, mean, trim = .2)        #以列为单位计算截尾平均值
      a       b       c       d       e       f 
 0.4820 -0.1922  0.0155 -0.7901  0.1140  0.8434 
> col.sums <- apply(x, 2, sum)        #以列为单位求和
> col.sums
     a      b      c      d      e      f 
 2.616 -1.173  0.553 -3.632 -0.707  1.791 
> row.sums <- apply(x, 1, sum)        #以行为单位求和
> row.sums
     a      b      c      d      e 
-1.905 -2.293  1.514  2.628 -0.497 

之后再来一个data.frame的例子:

> iris_sum <- apply(iris[,1:4],2,sum)
> iris_sum
Sepal.Length  Sepal.Width Petal.Length  Petal.Width 
         876          459          564          180 
> iris_mean <- apply(iris[,1:4],2,mean)
> iris_mean
Sepal.Length  Sepal.Width Petal.Length  Petal.Width 
        5.84         3.06         3.76         1.20 

下面我们引用“优雅R”(吹爆大佬!)的三段代码,用来对比apply()函数和其他方法的区别:

> x <- cbind(x1 = 3, x2 = c(4:1, 2:5))
> myFUN<- function(x, c1, c2) {
+     c(sum(x[c1],1), mean(x[c2])) 
+ }
> apply(x,1,myFUN,c1='x1',c2=c('x1','x2'))
     [,1] [,2] [,3] [,4] [,5] [,6] [,7] [,8]
[1,]  4.0    4  4.0    4  4.0    4  4.0    4
[2,]  3.5    3  2.5    2  2.5    3  3.5    4
> df<-data.frame()
> for(i in 1:nrow(x)){
+      row<-x[i,]                                         
+      df<-rbind(df,rbind(c(sum(row[1],1), mean(row))))   
+    }
> df
  V1  V2
1  4 3.5
2  4 3.0
3  4 2.5
4  4 2.0
5  4 2.5
6  4 3.0
7  4 3.5
8  4 4.0
> data.frame(x1=x[,1]+1,x2=rowMeans(x))
  x1  x2
1  4 3.5
2  4 3.0
3  4 2.5
4  4 2.0
5  4 2.5
6  4 3.0
7  4 3.5
8  4 4.0

上面介绍了三种方法,第一种是用apply函数按行进行循环计算,第二种是用for循环依次迭代赋值,第三种方法是利用了R的特性,通过向量化计算来完成的,三种方法都可以得出相同的结果,但是三者对计算机的性能能耗有区别,贴一段比较一下3种操作上面性能上的消耗:

# 清空环境变量
> rm(list=ls())

# 封装fun1
> fun1<-function(x){
+   myFUN<- function(x, c1, c2) {
+     c(sum(x[c1],1), mean(x[c2])) 
+   }
+   apply(x,1,myFUN,c1='x1',c2=c('x1','x2'))
+ }

# 封装fun2
> fun2<-function(x){
+   df<-data.frame()
+   for(i in 1:nrow(x)){
+     row<-x[i,]
+     df<-rbind(df,rbind(c(sum(row[1],1), mean(row))))
+   }
+ }

# 封装fun3
> fun3<-function(x){
+   data.frame(x1=x[,1]+1,x2=rowMeans(x))
+ }

# 生成数据集
> x <- cbind(x1=3, x2 = c(400:1, 2:500))

# 分别统计3种方法的CPU耗时。
> system.time(fun1(x))
用户 系统 流逝 
0.01 0.00 0.02 

> system.time(fun2(x))
用户 系统 流逝 
0.19 0.00 0.18 

> system.time(fun3(x))
用户 系统 流逝 
   0    0    0 

从CPU的耗时来看,用for循环实现的计算是耗时最长的,apply实现的循环耗时很短,而直接使用R语言内置的向量计算的操作几乎不耗时。通过上面的测试,对同一个计算来说,优先考虑R语言内置的向量计算,必须要用到循环时则使用apply函数,应该尽量避免使用for,while等操作方法。

2. lapply()函数

lapply(X, FUN, ...)

X: 表示列表或者向量
FUN:在行或者列上进行运算的函数。
...:运算函数的参数

lapply函数用来对list、data.frame数据集进行循环,并返回和X长度同样的list结构作为结果集。apply()函数以列表形式返回应用函数的结果,这是其最显著特征,需要注意。

# example 1
> lapply(iris[, 1:4], sum)
$Sepal.Length
[1] 876

$Sepal.Width
[1] 459

$Petal.Length
[1] 564

$Petal.Width
[1] 180

> lapply(iris[, 1:4], mean)
$Sepal.Length
[1] 5.84

$Sepal.Width
[1] 3.06

$Petal.Length
[1] 3.76

$Petal.Width
[1] 1.2
# example 2
> A <- matrix(c(1:9), nrow=3, ncol=3)
> B <- matrix(c(4:15), nrow=4, ncol=3)
> C <- matrix(rep(seq(8,10),2), nrow=3)
> data <- list(A=A, B=B, C=C)
> lapply(data, "[",,2)       #提取列表中每一部分第二列的元素
$A
[1] 4 5 6

$B
[1]  8  9 10 11

$C
[1]  8  9 10

> lapply(data, "[", 1,)      #提取列表中每一部分第一行的元素
$A
[1] 1 4 7

$B
[1]  4  8 12

$C
[1] 8 8

> lapply(data, sum)          #对列表每一部分求和
$A
[1] 45

$B
[1] 114

$C
[1] 54

lapply函数需要注意的有两点:

  1. lapply处理的对象只能是列表或者是向量,他是对块进行运算,而不是对列或者行进行运算,如果要对数据框的行或者列运算,可以将其先转化为列表,若直接计算:
> x <- cbind(x1 = 3, x2 = c(4:1, 2:5))
> x <- data.frame(x)
> x
  x1 x2
1  3  4
2  3  3
3  3  2
4  3  1
5  3  2
6  3  3
7  3  4
8  3  5
> class(x)
[1] "data.frame"
> lapply(x,sum)
$x1
[1] 24

$x2
[1] 24
  1. lapply结果返回的是list,有时可能需要将得到的列表再次转为数据框。转换需要经过如下几个阶段:

1.使用unlist()函数,将列表转换为数据框。
2.使用matrix()函数,将向量转换为矩阵。
3.使用as.data.frame()函数,将矩阵转换为数据框。
4.使用names()函数,从列表获取变量名,赋给数据框的各列。

3. sapply函数

sapply(X, FUN, ..., simplify = TRUE, USE.NAMES = TRUE)

X: 表示列表或者向量,与lapply一样
FUN:在行或者列上进行运算的函数。
...:运算函数的参数
simplify:逻辑值或者字符,用来确定返回值的类型:TRUE表示返回向量或矩阵;simply = "array"返回数组
USE.NAMES:确定结果的名字。

sapply函数是一个简化版的lapplylapplysapply函数可以用于处理列表数据和向量数据。lapply函数得到处理得到的数据类型是列表,而sapply函数得到处理的数据类型是向量。这两个函数除了在返回值类型不同外,其他方面基本完全一样。

> sapply(iris[, 1:4], sum)
Sepal.Length  Sepal.Width Petal.Length  Petal.Width 
         876          459          564          180 
> sapply(iris[, 1:4], mean)
Sepal.Length  Sepal.Width Petal.Length  Petal.Width 
        5.84         3.06         3.76         1.20 
> x <- sapply(iris[,1:4],mean)
> class(x)
[1] "numeric"

4. vapply()函数

vapply(X, FUN, FUN.VALUE, ..., USE.NAMES = TRUE)

X: 表示列表或者向量
FUN:在行或者列上进行运算的函数。
FUN.VALUE:指定返回值的类型和名称
USE.NAMES:确定结果的名字。

vapply函数类似于sapply函数,其主要区别为vapply函数可预先指定返回值的类型和名称。在vapply函数中总是会进行简化,vapply会检测FUN的所有值是否与FUN.VALUE兼容,以使他们具有相同的长度和类型。类型顺序:逻辑、整型、实数、复数。

# example 1
> x <- data.frame(x)
> 
> x<-data.frame(a=rnorm(4,4,4),
+               b=rnorm(4,5,3),
+               c=rnorm(4,5,3))
> x
     a    b    c
1 4.44 6.75 6.73
2 6.86 3.24 2.23
3 5.02 9.34 4.25
4 8.92 6.33 1.33
> vapply(x,mean,c(c=0))
   a    b    c 
6.31 6.41 3.64 
# example 2
> k<-function(x){
+   list(mean(x),sd(x))
+ }
> vapply(x,k,c(list(c=0,b=0)))
  a    b    c   
c 6.31 6.41 3.64
b 2.02 2.5  2.4 
example 3
> i39 <- sapply(3:9, seq) # list of vectors
> vapply(i39, fivenum,
+                c(Min. = 0, "1st Qu." = 0, Median = 0, "3rd Qu." = 0, Max. = 0))
        [,1] [,2] [,3] [,4] [,5] [,6] [,7]
Min.     1.0  1.0    1  1.0  1.0  1.0    1
1st Qu.  1.5  1.5    2  2.0  2.5  2.5    3
Median   2.0  2.5    3  3.5  4.0  4.5    5
3rd Qu.  2.5  3.5    4  5.0  5.5  6.5    7
Max.     3.0  4.0    5  6.0  7.0  8.0    9

5. tapply函数

tapply(data, index, FUN = NULL, ..., simplify = TRUE)

data只能是向量
index为因子向量,长度应与data相同。
FUN:分组计算的函数。
...:函数的额外参数
Simplify默认输出为向量,若simplify=FALSE输出列表。
index向量因子有两个形式:1)数据框的变量2)指定的分类向量。可用c()生成不规则的因子,也可用gl()生成等长分类的向量。

tapply用于分组的循环计算,通过INDEX参数可以把数据集X进行分组,相当于group by的操作。INDEX 一个或多个因子的列表,每个因子的长度都与x相同。

# example 1
> daf1<-data.frame(gender=c("M","M","F","M","F","F","M"),
+                                   age=c(47,59,21,32,40,24,25),
+                                  salary=c(55000,88000,32450,76500,123000,45650,65000)
+                                   )
> daf1
  gender age salary over40
1      M  47  55000      1
2      M  59  88000      1
3      F  21  32450      0
4      M  32  76500      0
5      F  40 123000      0
6      F  24  45650      0
7      M  25  65000      0
> daf1$over40=ifelse(daf1$age>40,1,0)
> daf1$over40
[1] 1 1 0 0 0 0 0
> tapply(daf1$salary,list(daf1$gender,daf1$over40),mean)
      0     1
F 67033    NA
M 70750 71500
# example 2
> df <- data.frame(year=kronecker(2001:2003, rep(1,4)), 
+                                    loc=c('beijing','beijing','shanghai','shanghai'), 
+                                    type=rep(c('A','B'),6),
+                                    sale=rep(1:12))
> df
   year      loc type sale
1  2001  beijing    A    1
2  2001  beijing    B    2
3  2001 shanghai    A    3
4  2001 shanghai    B    4
5  2002  beijing    A    5
6  2002  beijing    B    6
7  2002 shanghai    A    7
8  2002 shanghai    B    8
9  2003  beijing    A    9
10 2003  beijing    B   10
11 2003 shanghai    A   11
12 2003 shanghai    B   12
> tapply(df$sale, df[,c('year','loc')], sum)
      loc
year   beijing shanghai
  2001       3        7
  2002      11       15
  2003      19       23
> tapply(df$sale, df[,c('year','type')], sum)
      type
year    A  B
  2001  4  6
  2002 12 14
  2003 20 22
# example 3
> install.packages("HSAUR2")
trying URL 'https://mirror-hk.koddos.net/CRAN/bin/macosx/el-capitan/contrib/3.6/HSAUR2_1.1-17.tgz'
Content type 'application/x-gzip' length 2804517 bytes (2.7 MB)
==================================================
downloaded 2.7 MB


The downloaded binary packages are in
    /var/folders/fl/rylb7v3171970p_spz6sj2hc0000gn/T//RtmpHnOov7/downloaded_packages
> library(tools)
> library(HSAUR2)
> data("CHFLS")
> head(CHFLS)
    R_region R_age              R_edu R_income  R_health R_height        R_happy A_height
2  Northeast    54 Senior high school      900      Good      165 Somewhat happy      172
3  Northeast    46 Senior high school      500      Fair      156 Somewhat happy      170
10 Northeast    48 Senior high school      800      Good      163 Somewhat happy      172
11 Northeast    46 Junior high school      300      Fair      164 Somewhat happy      174
22 Northeast    45 Junior high school      300      Fair      162 Somewhat happy      172
23 Northeast    36 Senior high school      500 Excellent      161 Somewhat happy      180
                A_edu A_income
2  Senior high school      500
3  Senior high school      800
10 Junior high school      700
11  Elementary school      700
22 Junior high school      400
23     Junior college      900
> str(CHFLS)
'data.frame':   1534 obs. of  10 variables:
 $ R_region: Factor w/ 6 levels "Coastal South",..: 5 5 5 5 5 5 5 5 5 5 ...
 $ R_age   : num  54 46 48 46 45 36 48 36 20 30 ...
 $ R_edu   : Ord.factor w/ 6 levels "Never attended school"<..: 4 4 4 3 3 4 3 3 3 4 ...
 $ R_income: num  900 500 800 300 300 500 0 100 200 400 ...
 $ R_health: Ord.factor w/ 5 levels "Poor"<"Not good"<..: 4 3 4 3 3 5 2 4 3 4 ...
 $ R_height: num  165 156 163 164 162 161 167 156 158 160 ...
 $ R_happy : Ord.factor w/ 4 levels "Very unhappy"<..: 3 3 3 3 3 3 4 2 2 3 ...
 $ A_height: num  172 170 172 174 172 180 168 173 178 176 ...
 $ A_edu   : Ord.factor w/ 6 levels "Never attended school"<..: 4 4 3 2 3 5 3 4 5 4 ...
 $ A_income: num  500 800 700 700 400 900 300 800 200 600 ...
> tapply(CHFLS$R_income, CHFLS$R_health, mean)
     Poor  Not good      Fair      Good Excellent 
      338       419       649       645       614 
> tapply(CHFLS$R_income, CHFLS$R_edu, mean)
Never attended school     Elementary school    Junior high school    Senior high school 
                  210                   416                   535                   667 
       Junior college            University 
                 1170                  1707 

6.mapply()函数

mapply(FUN, ..., MoreArgs = NULL, SIMPLIFY = TRUE,   USE.NAMES = TRUE)

FUN:在行或者列上进行运算的函数。
MoreArgs:FUN函数的其它参数。
simplify:逻辑值或者字符,用来确定返回值的类型。
USE.NAMES:确定结果的名字。

mapply也是sapply的变形函数,类似多变量的sapply,但是参数定义有些变化。 mapply()函数的第一个参数是待应用的FUN函数,它接受多个参数。要传递给FUN()函数的参数作为数据保存时,mapply()函数将保存在数据中的值转换为参数,传递给FUN函数,并调用执行FUN函数。

> mapply(rep, 1:4, 4:1)
[[1]]
[1] 1 1 1 1

[[2]]
[1] 2 2 2

[[3]]
[1] 3 3

[[4]]
[1] 4

> mapply(rep, times = 1:4, x = 4:1)
[[1]]
[1] 4

[[2]]
[1] 3 3

[[3]]
[1] 2 2 2

[[4]]
[1] 1 1 1 1

> mapply(rep, times = 1:4, MoreArgs = list(x = 42))
[[1]]
[1] 42

[[2]]
[1] 42 42

[[3]]
[1] 42 42 42

[[4]]
[1] 42 42 42 42

> mapply(function(x, y) seq_len(x) + y,
+        c(a =  1, b = 2, c = 3),  # names from first
+        c(A = 10, B = 0, C = -10))
$`a`
[1] 11

$b
[1] 1 2

$c
[1] -9 -8 -7

> word <- function(C, k) paste(rep.int(C, k), collapse = "")
> word
function(C, k) paste(rep.int(C, k), collapse = "")
> utils::str(mapply(word, LETTERS[1:6], 6:1, SIMPLIFY = FALSE))
List of 6
 $ A: chr "AAAAAA"
 $ B: chr "BBBBB"
 $ C: chr "CCCC"
 $ D: chr "DDD"
 $ E: chr "EE"
 $ F: chr "F"
> data("iris")
> mapply(mean,iris[, 1:4])
Sepal.Length  Sepal.Width Petal.Length  Petal.Width 
    5.843333     3.057333     3.758000     1.199333 

> mapply(sum,iris[, 1:4])
Sepal.Length  Sepal.Width Petal.Length  Petal.Width 
       876.5        458.6        563.7        179.9

7. rapply函数

rapply(object, f, classes = "ANY", deflt = NULL, how = c("unlist", "replace", "list"), ...)

object: list形式;
f: 自定义的调用函数;
classes : 匹配类型, ANY为所有类型;
deflt: 非匹配类型的默认值;
how: 3种操作方式,当为replace时,则用调用f后的结果替换原list中原来的元素;当为list时,新建一个list,类型匹配调用f函数,不匹配赋值为deflt;当为unlist时,会执行一次unlist(recursive = TRUE)的操作;

rapply是一个递归版本的lapply,它只处理list类型数据,对list的每个元素进行递归遍历,如果list包括子元素则继续遍历。

> X <- list(list(a = pi, b = list(c = 1:1)), d = "a test")
> X
[[1]]
[[1]]$a
[1] 3.14

[[1]]$b
[[1]]$b$c
[1] 1



$d
[1] "a test"
> rapply(X, sqrt, classes = "numeric", how = "replace")
[[1]]
[[1]]$a
[1] 1.77

[[1]]$b
[[1]]$b$c
[1] 1



$d
[1] "a test"
> rapply(X, nchar, classes = "character",
+        deflt = as.integer(NA), how = "list")
[[1]]
[[1]]$a
[1] NA

[[1]]$b
[[1]]$b$c
[1] NA



$d
[1] 6
> rapply(X, nchar, classes = "character",
+        deflt = as.integer(NA), how = "unlist")
  a b.c   d 
 NA  NA   6 
> rapply(X, nchar, classes = "character", how = "unlist")
d 
6 
> rapply(X, log, classes = "numeric", how = "replace", base = 2)
[[1]]
[[1]]$a
[1] 1.65

[[1]]$b
[[1]]$b$c
[1] 1



$d
[1] "a test"

8. eapply函数

eapply(env, FUN, ..., all.names = FALSE, USE.NAMES = TRUE)

env 将被使用的环境
all.names 逻辑值,指示是否对所有值使用该函数
USE.NAMES 逻辑值,指示返回的列表结果是否包含命名

eapply函数通过对environment中命名值进行FUN计算后返回一个列表值,用户可以请求所有使用过的命名对象。

> require(stats)
> env <- new.env(hash = FALSE)
> env$a <- 1:10
> env$beta <- exp(-3:3)
> utils::ls.str(env)
a :  int [1:10] 1 2 3 4 5 6 7 8 9 10
beta :  num [1:7] 0.0498 0.1353 0.3679 1 2.7183 ...
> eapply(env, quantile, probs = 1:3/4)
$beta
  25%   50%   75% 
0.252 1.000 5.054 

$a
 25%  50%  75% 
3.25 5.50 7.75 

> eapply(env, quantile)
$beta
     0%     25%     50%     75%    100% 
 0.0498  0.2516  1.0000  5.0537 20.0855 

$a
   0%   25%   50%   75%  100% 
 1.00  3.25  5.50  7.75 10.00 
image

本文非原创,仅为个人学习笔记总结,侵删。
本文参考以下文献:
apply()族函数- 土豆R语言与数据分析实战
apply族函数应用指南
R语言进阶路上||遇见apply函数家族
【r<-高级|理论】apply,lapply,sapply用法探索

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容