apply()族函数- 土豆R语言与数据分析实战

背景:

鸢尾花数据集最初由Edgar Anderson 测量得到,而后在著名的统计学家和生物学家R.A Fisher于1936年发表的文章「The use of multiple measurements in taxonomic problems」中被使用,用其作为线性判别分析(Linear Discriminant Analysis)的一个例子,证明分类的统计方法,从此而被众人所知,尤其是在机器学习这个领域。

数据中的两类鸢尾花记录结果是在加拿大加斯帕半岛上,于同一天的同一个时间段,使用相同的测量仪器,在相同的牧场上由同一个人测量出来的。这是一份有着70年历史的数据,虽然老,但是却很经典,详细数据集可以在UCI数据库中找到。

鸢尾花数据集共收集了三类鸢尾花,即Setosa鸢尾花、Versicolour鸢尾花和Virginica鸢尾花,每一类鸢尾花收集了50条样本记录,共计150条。

1. apply()系列函数

apply()的被分析对象必须且只能是矩阵或数组

apply()对层、行、列、行和列应用函数,根据观测、变量和数据集不同层次的特征决定。语法格式为:

apply(dataset, MARGIN, FUN)

dataset是apply应用的数据集,数据结构是数组、矩阵或数据框。参数MARGIN是apply()应用的维度,MARGIN=1表示矩阵和数组的行,MARGIN=2表示矩阵和数组的列。参数FUN为应用的计算函数f(),可带有f()的参数。FUN函数结果的长度确定apply()的返回值类型,通常为array类型,若返回值的向量长度不等,则返回list对象。

> d<- matrix(1:9, ncol=3)
> d
     [,1] [,2] [,3]
[1,]    1    4    7
[2,]    2    5    8
[3,]    3    6    9
> apply(d, 1, sum)
[1] 12 15 18

使用apply()函数对鸢尾花数据集第1-4列求和,包含所有行。

> data(iris) #调用数据集
> head(iris) 
  Sepal.Length Sepal.Width Petal.Length Petal.Width Species
1          5.1         3.5          1.4         0.2  setosa
2          4.9         3.0          1.4         0.2  setosa
3          4.7         3.2          1.3         0.2  setosa
4          4.6         3.1          1.5         0.2  setosa
5          5.0         3.6          1.4         0.2  setosa
6          5.4         3.9          1.7         0.4  setosa
> apply(iris[, 1:4],2, sum)
Sepal.Length  Sepal.Width Petal.Length  Petal.Width 
       876.5        458.6        563.7        179.9 

2. lapply()函数

lapply()的被分析对象必须且只能是向量或列表

apply系列函数中,lapply()函数以列表形式返回应用函数的结果,这是其最显著特征。使用lapply()函数处理数据框后得到列表,有时可能需要将得到的列表再次转为数据框。转换需要经过如下几个阶段:
1.使用unlist()函数,将列表转换为数据框。
2.使用matrix()函数,将想来那个转换为矩阵。
3.使用as.data.frame()函数,将矩阵转换为数据框。
4.使用names()函数,从列表获取变量名,赋给数据框的各列。
对于数据框的行名和列名,可以分别使用rownames()、colnames()函数指定。

> lapply(iris[, 1:4], sum)
$`Sepal.Length`
[1] 876.5

$Sepal.Width
[1] 458.6

$Petal.Length
[1] 563.7

$Petal.Width
[1] 179.9

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

$Sepal.Width
[1] 3.057333

$Petal.Length
[1] 3.758

$Petal.Width

> lapply(split(iris$Sepal.Length,iris$Species), mean)
$`setosa`
[1] 5.006

$versicolor
[1] 5.936

$virginica
[1] 6.588

> unlist(lapply(split(iris$Sepal.Length,iris$Species), mean))
    setosa versicolor  virginica 
     5.006      5.936      6.588 


3. sapply()函数

sapply()的被分析对象必须且只能是向量或列表

sapply()函数与lapply()函数类似,但以矩阵、向量等数据类型返回结果。下列示例代码中,使用lapply()函数将以列表形式返回各列平均值,而使用sapply()函数则以向量形式返回各列平均值。

sapply(list,g) g为操作函数,返还结果为向量,而lapply返还结果为list形式。常与split结合使用

> sapply(iris[, 1:4], sum)
Sepal.Length  Sepal.Width Petal.Length  Petal.Width 
       876.5        458.6        563.7        179.9 
> sapply(iris[, 1:4], mean)
Sepal.Length  Sepal.Width Petal.Length  Petal.Width 
    5.843333     3.057333     3.758000     1.199333 

使用as.data.frame()函数可以将sapply()函数返回的向量进一步转换为数据框。此时,需要t(x)函数转置向量的行与列,将无法得到想要的数据框。下列中,先使用sapply()函数对鸢尾花数据集的前四列分别求平均值,得到包含平均值的向量后,再使用as.data.frame()函数将向量转换为数据框。

> x<-sapply(iris[, 1:4], mean)
> as.data.frame(x)
                    x
Sepal.Length 5.843333
Sepal.Width  3.057333
Petal.Length 3.758000
Petal.Width  1.199333
> as.data.frame(t(x))
  Sepal.Length Sepal.Width Petal.Length Petal.Width
1     5.843333    3.057333        3.758    1.199333
> sapply(iris, class)
Sepal.Length  Sepal.Width Petal.Length  Petal.Width      Species 
   "numeric"    "numeric"    "numeric"    "numeric"     "factor" 

> y<-sapply(iris[, 1:4], function(x){x > 3})
> class(y)
[1] "matrix"
> head(y)
     Sepal.Length Sepal.Width Petal.Length Petal.Width
[1,]         TRUE        TRUE        FALSE       FALSE
[2,]         TRUE       FALSE        FALSE       FALSE
[3,]         TRUE        TRUE        FALSE       FALSE
[4,]         TRUE        TRUE        FALSE       FALSE
[5,]         TRUE        TRUE        FALSE       FALSE
[6,]         TRUE        TRUE        FALSE       FALSE

4. tapply()函数

apply系列函数中,使用tapply()函数会先对数据进行分组,然后将函数应用到各组。
参考帖子:http://rsoftware.h.baike.com/article-2015511.html

tapply(x,f,g) x为向量,f为因子列,g为操作函数,相对数据框进行类似操作可以用by函数.
tapply()的被分析对象必须且只能是向量
tapply()根据一个因子向量对数据向量进行分类,得到的分组数据是不等长分组,然后对每个分组应用函数fun()。

tapply()语法格式:

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

data只能是向量,index为因子向量,长度应与data相同。返回值是向量,若simplify=FALSE输出列表。index向量因子有两个形式:1)数据框的变量2)指定的分类向量。可用c()生成不规则的因子,也可用gl()生成等长分类的向量。

同类系列函数有split()和by()。split()函数仅对数据框或一个变量根据另一个变量分组,输出是列表。by()能对数据框和矩阵根据因子分组,可应用多变量函数fun()。

> 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$over40=ifelse(daf1$age>40,1,0) #年龄大于40岁的员工
> daf1$over40
[1] 1 1 0 0 0 0 0
> tapply(daf1$salary,list(daf1$gender,daf1$over40),mean)  #计算平均工资
         0     1
F 67033.33    NA
M 70750.00 71500
> #用list()设置多个因子,将工资salary分成四组,所以有4个答案。
> tapply(daf1$salary,c(daf1$gender,daf1$over40),mean)
Error in tapply(daf1$salary, c(daf1$gender, daf1$over40), mean) : 
  arguments must have same length
# 参数的长度必需相同, 参数长度不同,是因为c()的连接作用。
> 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

5. mapply()函数

mapply()的被分析对象必须是函数

mapply()函数与 sapply()函数类似,但他可以将多个参数传递给指定函数。 mapply()函数的第一个参数是待应用的FUN函数,它接受多个参数。要传递给FUN()函数的参数作为数据保存时,mapply()函数将保存在数据中的值转换为参数,传递给FUN函数,并调用执行FUN函数。

函数mapply()用在函数的参数有多个不同值的时候,参数顺序和sapply()不同。标准格式:

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

data数据类型为向量或列表,函数FUN对元素应用;若参数长度为1,得到的结果和sapply相同;但如果参数长度不等于1,FUN函数将按向量顺序和循环规则(短向量重复)逐个取参数应用到对应数据元素。

使用mapply()函数时,如果给定多个数据,则这些数据的第一个元素组成一组,作为参数传递给FUN函数;然后第二个元素组成一组,作为参数传递给FUN函数,以此类推。

> dv1=c(1:4)
> dafr2=mapply(dv1,FUN=rep,times=1:4)
> dafr2
[[1]]
[1] 1

[[2]]
[1] 2 2

[[3]]
[1] 3 3 3

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

下面使用mapply()函数,计算鸢尾花数据集1-4列的平均值与总和。

> 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

image.png

图片来源:https://blog.csdn.net/wzgl__wh/article/details/52207233

6. vapply()函数应用

vapply按变量进行函数操作,vapply类似于sapply函数,但是它的返回值有预定义类型,所以它使用起来会更加安全,有的时候会更快。在vapply函数中总是会进行简化,vapply会检测FUN的所有值是否与FUN.VALUE兼容,以使他们具有相同的长度和类型。类型顺序:逻辑、整型、实数、复数。

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

X表示一个向量或者表达式对象,其余对象将被通过as.list强制转换为list
simplify 逻辑值或者字符串,如果可以,结果应该被简化为向量、矩阵或者高维数组。
必须是命名的,不能是简写。默认值是TRUE,若合适将会返回一个向量或者矩阵。如果simplify=”array”,结果将返回一个阵列。
USE.NAMES 逻辑值,如果为TRUE,且x没有被命名,则对x进行命名。

FUN.VALUE 一个通用型向量,FUN函数返回值得模板。

x<-data.frame(a=rnorm(4,4,4),
              b=rnorm(4,5,3),
              c=rnorm(4,5,3))
vapply(x,mean,c(c=0))
k<-function(x){
    list(mean(x),sd(x))
    }
vapply(x,k,c(list(c=0,b=0)))
  a        b        c       
c 3.495029 2.780169 5.41083 
b 4.671292 4.283595 3.320549

7. eapply 函数table

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

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

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

> require(stats)
> env <- new.env(hash = FALSE) # so the order is fixed
> 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, mean)
$`beta`
[1] 4.535125

$a
[1] 5.5

> unlist(eapply(env, mean, USE.NAMES = FALSE))
[1] 4.535125 5.500000
> eapply(env, quantile, probs = 1:3/4)
$`beta`
      25%       50%       75% 
0.2516074 1.0000000 5.0536690 

$a
 25%  50%  75% 
3.25 5.50 7.75 

> eapply(env, quantile)
$`beta`
         0%         25%         50%         75%        100% 
 0.04978707  0.25160736  1.00000000  5.05366896 20.08553692 

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

8、rapply 函数

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

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)的操作
…: 更多参数,可选
比如,对一个list的数据进行过滤,把所有数字型numeric的数据进行从小到大的排序。


> x=list(a=12,b=1:4,c=c('b','a'))
> y=pi
> z=data.frame(a=rnorm(10),b=1:10)
> a <- list(x=x,y=y,z=z)
> # 进行排序,并替换原list的值
> rapply(a,sort, classes='numeric',how='replace')
$`x`
$`x`$`a`
[1] 12

$`x`$b
[1] 1 2 3 4

$`x`$c
[1] "b" "a"


$y
[1] 3.141593

$z
            a  b
1  -1.8383682  1
2  -1.0000541  2
3  -0.9739195  3
4  -0.9118907  4
5  -0.8726053  5
6  -0.7946537  6
7  -0.6531306  7
8  -0.6397726  8
9  -0.3413240  9
10  1.0768179 10

> class(a$z$b)
[1] "integer"

从结果发现,只有$z$a的数据进行了排序,检查$z$b的类型,发现是integer,是不等于numeric的,所以没有进行排序。

接下来,对字符串类型的数据进行操作,把所有的字符串型加一个字符串’++++’,非字符串类型数据设置为NA。

> rapply(a,function(x) paste(x,'++++'),classes="character",deflt=NA, how = "list")
$`x`
$`x`$`a`
[1] NA

$`x`$b
[1] NA

$`x`$c
[1] "b ++++" "a ++++"


$y
[1] NA

$z
$z$`a`
[1] NA

$z$b
[1] NA

只有$x$c为字符串向量,都合并了一个新字符串。那么,有了rapply就可以对list类型的数据进行方便的数据过滤了。

9. 函数table(求因子出现的频数)

使用格式为:

table(..., exclude = if (useNA == "no") c(NA, NaN), useNA = c("no", "ifany", "always"), dnn = list.names(...), deparse.level = 1)

其中参数exclude表示哪些因子不计算。
示例代码:

> d <- factor(rep(c("A","B","C"), 5), levels=c("A","B","C","D","E"))
> d
 [1] A B C A B C A B C A B C A B C
Levels: A B C D E
> table(d)
d
A B C D E 
5 5 5 0 0 
> table(d, exclude="B")
d
A C D E 
5 5 0 0 

常用apply族函数对比

image.png

lapply()和sapply()只能应用在二维数据结构,例如列表的元素,数据框的变量,而且并不需要指定维度。lappy()是最基本的原型函数,不妨知道它是R语言最简单的泛函,仅此而已。lapply(),sapply()和vapply()的两个主要参数是data和f()。data的数据类型是列表或向量,函数对所有列表元素、数据框变量应用f()函数。 lapply()返回的结果是列表,长度与data相同,sapply()返回的结果是向量,矩阵或数组,结果需要做预测。而vapply()函数将对返回结果的值进行类型检查,参数FUN.VALUE设置返回值类型,因此vapply()是结果可预测的sapply()版。所以不可在函数内部用sapply(),而应使用vapply()。lapply()和sapply()可实现数据结构操作的大多数功能,包括创建数据结构、取子集等,然而这并不是它们的优势。访问操作与"["相同,"["可提取数据结构的分量。

参考:

【1】R语言与数据分析实战 徐珉久,武传海著,人民邮电出版社
【2】第三章第一节 apply族函数 http://rsoftware.h.baike.com/article-2015511.html
【3】R语言中的lapply sapply apply tapply mapply http://iccm.cc/lapply-sapply-apply-tapply-mapply-in-r-language/
【4】R中的apply族函数 https://zhuanlan.zhihu.com/p/26466130
【5】R语言︱数据分组统计函数族——apply族用法与心得: https://blog.csdn.net/sinat_26917383/article/details/51086663
【6】掌握R语言中的apply函数族 http://blog.fens.me/r-apply/

生信技能树公益视频合辑:学习顺序是linux,r,软件安装,geo,小技巧,ngs组学!
B站链接:https://m.bilibili.com/space/338686099
YouTube链接:https://m.youtube.com/channel/UC67sImqK7V8tSWHMG8azIVA/playlists
生信工程师入门最佳指南:https://mp.weixin.qq.com/s/vaX4ttaLIa19MefD86WfUA
学徒培养:https://mp.weixin.qq.com/s/3jw3_PgZXYd7FomxEMxFmw
生信技能树 - 简书 https://www.jianshu.com/u/d645f768d2d5

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,390评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,821评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,632评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,170评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,033评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,098评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,511评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,204评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,479评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,572评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,341评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,893评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,171评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,486评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,676评论 2 335

推荐阅读更多精彩内容

  • 循环对于代码运行来说是非常消耗时间和资源的,在R中,要尽量少使用for while循环,用apply函数族的话对于...
    willnight阅读 3,421评论 0 2
  • R语言中的以apply()函数为首的apply()家族,提供了强大而方便的循环功能,这些函数说起来简单,用起来可能...
    大数据技术派阅读 1,280评论 0 1
  • 1 、 apply函数 数据量比较大的时候,R 语言for循环非常的慢,apply函数是最常用的代替for循环的函...
    一刀YiDao阅读 814评论 0 2
  • 一切事物都是有味道的 初夏的阳光有温暖的香气 过去的时光有灰尘的味道 母亲和父亲的怀抱味道绝对是不同的 我用鼻尖去...
    落花时节未逢君阅读 274评论 8 3
  • 昂首信步慢轻云, 翱翔宇寰竞自由。 天道昭昭变者通, 把酒当歌千里游。 *2018年3月13日辛时,情爱千秋。
    情爱千秋阅读 523评论 41 56