R语言笔记7:认识循环函数、lapply和sapply
R语言基础系列:
Loop functions
循环是R语言中最强大的函数之一。循环函数背后的思路是,当你想要对一个或一组对象执行循环的时候,使用这种方式可以让你在在很少的空间内执行大量的重复工作,不必向命令行那样做很多输入。
之前我们学习过while
循环和for
循环,除了这些之外还有很多更加简洁的循环函数,他们通常名字里都带着apply
这个词,主要包含:
apply
lapply
sapply
tapply
mapply
lapply
是最最主力的函数。他的主要用途是,对列表(list)对象而言,你想在其内部做一个循环,并对列表中的每一个元素运用函数
sapply
是lapply
的一个变体,简化了lapply
的结果
apply
是一个对数组进行行或列运算的函数。如果你想对矩阵或其他高维数组求和,这个函数会非常好用。
tapply
是table apply()
的缩写,将函数应用于向量的子集。
mapply
是lapply
的多变量版本。
除了这些之外,还有一个函数叫做split()
,它不对对象进行任何操作,但是常常和lapply
、sapply
等结合使用,可以将对象分成子块。
下面先认识一下最常用的lapply
函数
lapply()
lapply
有三个参数:第一个是输入对象,即名叫x的列表;第二个是一个函数名;其余的参数可以传递给参数...
。lapply
函数是这样的:
lapply
function (X, FUN, ...)
{
FUN <- match.fun(FUN)
if (!is.vector(X) || is.object(X))
X <- as.list(X)
.Internal(lapply(X, FUN))
}
如果x不是列表,可能会被as.list(X)
强制转化成列表,如果没有强制转化就会报错。对于lapply
来说,重点要记住的就是他的对象是列表。
PS. ...
常用来给列表中的每个元素做运算的函数传递参数
举例子:
创建一个列表x,包含两个元素,a是1-5数列,b是10以内的随机数字组成的向量。使用lapply()
函数来计算平均值,生成结果输入新列表y中:
x <- list(a = 1:5, b = rnorm(10))
y <- lapply(x, mean)
新生成的列表y的元素,和原列表x有相同的名字,即a和b,在新列表y中显示出了计算的平均值。
> x
$a
[1] 1 2 3 4 5
$b
[1] -1.4445463 2.0582121 -0.7964703 0.8434979 -0.5139175 0.7922571
[7] 1.4374180 0.1635100 0.5597397 0.4719439
> y
$a
[1] 3
$b
[1] 0.3571645
再举个栗子:
创建一个序列赋值给x,然后使用随机数发生器runif
生成符合均匀分布的随机变量。runif()
函数的第一个参数就是你要产生随机数字的个数,这个个数就是从x来的,即1-4。
使用lapply()
函数就会自动将生成的随机数字返回得到一个列表:
> x <- 1:4
> lapply(x, runif)
[[1]]
[1] 0.002001568
[[2]]
[1] 0.04863917 0.63242592
[[3]]
[1] 0.1522060 0.6011618 0.1055978
[[4]]
[1] 0.89643371 0.33845716 0.05518053 0.91004687
上面两个例子中,lapply()
都只填了x
和FUN
两个参数,...
没有设置,都为FUN
的默认值。如果要设置的话,填在后面就好:
例如,上面的runif()
函数,默认是在0-1之间取值,如果修改区间,改成5-10,可以通过...
传递参数,作如下修改:
> x <- 1:4
> lapply(x, runif, min = 5, max = 10)
[[1]]
[1] 5.673941
[[2]]
[1] 8.368024 9.502657
[[3]]
[1] 8.126944 9.363609 5.335993
[[4]]
[1] 6.001419 7.023682 8.916751 9.871137
lapply
和相关函数充分利用了所谓的匿名函数,匿名函数没有函数名,所以我们不用给他们分配函数名,可以直接创建函数
这个例子中,我们创建了一个包含两个矩阵a、b的列表:
> x <- list(a = matrix(1:4, 2, 2), b = matrix(1:6, 3, 2))
> x
$a
[,1] [,2]
[1,] 1 3
[2,] 2 4
$b
[,1] [,2]
[1,] 1 4
[2,] 2 5
[3,] 3 6
我现在想提取每个矩阵的第一列,结果发现没这个函数。
那么就来创造一个函数提取矩阵的第一列:
> lapply(x, function(elt) { elt[,1] })
$a
[1] 1 2
$b
[1] 1 2 3
在我临时创造的函数里面,命名了一个参数叫elt
,用于提取第一行,其实你想叫他什么都可以因为出了lapply()
之后他就毫无意义了。
所以使用lapply()
的时候就知道了,有函数就用,没有就现写
sapply()
sapply()
是lapply()
的变种。它的任务是将lapply()
的结果尽量的简化。
lapply()
总是返回一个列表。就算是所有元素长度都一样,没必要是一个列表的时候,他也给你返回列表。
sapply()
就会更加灵活一些,可以直接返回一个包含所有元素的向量,将它简化。
对比:
先看一下“一根筋”的lapply()
> x <- list(a = 1:4, b = rnorm(10), c = rnorm(20, 1), d = rnorm(100, 5))
> lapply(x, mean)
$a
[1] 2.5
$b
[1] 0.1983592
$c
[1] 0.8517163
$d
[1] 5.145514
再来看一下sapply()
:
> x <- list(a = 1:4, b = rnorm(10), c = rnorm(20, 1), d = rnorm(100, 5))
> sapply(x, mean)
a b c d
2.50000000 -0.02155822 1.22162680 4.96242076
如果数据不适合简化的话,sapply()
还是会返回一个列表的。
apply
apply
函数是另一个循环函数,它可以把一个函数应用在一个数组的各个维度上,应用对象是矩阵的行和列。(矩阵是最常见的数组类型,是二维数组)
在命令行中apply
比for循环相对更好用一些,因为输入较少。
apply
函数长这样:
> str(apply)
function (X, MARGIN, FUN, ...)
第一个参数x
是一个数组,是一个有维度的向量;
第二个参数MARGIN
是一个整数向量,指示哪一行或列需要保留;
第三个重要参数是你要应用到每个行、列的函数命令,这个函数可以是已经存在的函数,也可以是匿名函数。
第四个参数...
是其他你想传递的参数
举例
首先创建一个20行10列的矩阵,矩阵内是已生成的正态随机变量:
> x <- matrix(rnorm(200), 20, 10)
这个矩阵的第一个维度是20行,第二个维度是10列。
如果我们要对每列求平均值,那就保留第二个维度,即设置MARGIN
为2,这样就得到一个长为10的数字向量,每个数字是每一列的平均值:
> apply(x, 2, mean)
[1] 0.65874106 -0.32984283 0.02339061 0.07329736 -0.03052147
[6] -0.07784829 -0.09898670 -0.02616803 -0.62234169 0.21103926
同理,如果我想求每一行的数据之和,那么就指定MARGIN
为1,只保留所有行(第一维度),消掉所有列。得到长度为20 的向量:
> apply(x, 1, sum)
[1] 1.2437406 1.2477988 -0.8286921 5.8799087 -7.1345567
[6] -6.0516633 1.1861021 -4.6233625 1.4116391 4.0984832
[11] 0.6733807 -4.2918003 6.3707265 0.5751055 -4.1153285
[16] -2.4731258 -1.5706162 2.1357355 4.5665875 -2.6848776
对于计算总和、平均值之类的简单操作而言,有一些经过优化的专用函数可以快速实现这些功能,他们就是~~:
rowSums()
=apply(x, 1, sum)
rowMeans()
=apply(x, 1, mean)
colSums()
=apply(x, 2, sum)
colMeans()
=apply(x, 2, mean)
所以上面的对矩阵x的各行求和,可以直接用这些函数:
> rowSums(x)
[1] 1.2437406 1.2477988 -0.8286921 5.8799087 -7.1345567
[6] -6.0516633 1.1861021 -4.6233625 1.4116391 4.0984832
[11] 0.6733807 -4.2918003 6.3707265 0.5751055 -4.1153285
[16] -2.4731258 -1.5706162 2.1357355 4.5665875 -2.6848776
apply()
函数也可以应用在复杂一点的命令上。
还是以刚才的随机数矩阵为例,我们想计算每一行的25%和75%分位数,使用quantile()
函数,...
用来传递其他参数,结果如下(每一行都会有对应的两个返回值):
> apply(x, 1, quantile, probs = c(0.25, 0.75))
[,1] [,2] [,3] [,4] [,5]
25% -0.4279914 -0.7373156 -0.9529599 0.2055977 -1.212583
75% 0.7124124 0.6271725 0.4206316 1.1519559 -0.164044
[,6] [,7] [,8] [,9] [,10]
25% -1.5083933 -0.4973032 -1.40098143 -0.1734262 0.1533103
75% 0.2944026 0.2808402 -0.03580914 0.7878205 1.1799618
[,11] [,12] [,13] [,14] [,15]
25% -0.7556971 -1.3682307 -0.09161202 -0.0714848 -1.170791
75% 0.6127445 0.5407379 1.60144594 0.3402383 0.186875
[,16] [,17] [,18] [,19] [,20]
25% -0.4673410 -0.5190178 -0.4813278 -0.3519997 -1.083816
75% 0.2163925 0.4672890 0.4078020 1.1469154 -0.117494
除了二维矩阵之外,apply
也可以用于多维矩阵,即数组
如,我们先创建一个正态随机变量数组,是一个三维矩阵,然后求每个二维矩阵的和,
设置MARGIN
为c(1, 2)
就可以消除掉第三个维度:
> a <- array(rnorm(2 * 2 * 10), c(2, 2, 10))
> apply(a, c(1, 2), mean)
[,1] [,2]
[1,] 0.4376722 0.1558857
[2,] 0.5024186 -0.2940285
同理,如果使用rowMeans
:
> rowMeans(a, dims = 2)
[,1] [,2]
[1,] 0.4376722 0.1558857
[2,] 0.5024186 -0.2940285
mapply()
mapply()
是lapply()
和sapply()
的多变量版本
也就是说
lapply()
和sapply()
等等都是将参数中的函数应用到某个单一的对象上,而mapply()
的思想是把一个函数并行应用到一组不同的对象上
> str(mapply)
function (FUN, ..., MoreArgs = NULL, SIMPLIFY = TRUE, USE.NAMES = TRUE)
mapply()
的第一个参数是你要应用的函数,
第二个...
用于传递参数
第三个参数是MoreArgs
当你需要给函数传递更多参数是才会用到
tapply
tapply
是一个很有用的函数,它可以将函数应用于某向量的子集。
当我们想计算该向量某部分的概要统计量时,可以使用tapply
。
对于该数值向量中的每一组而言,我们都可以计算一个统计量,这时,我们需要另一个变量或者对象,来识别该数值向量中各元素的分组。
> str(tapply)
function (X, INDEX, FUN = NULL, ..., default = NA, simplify = TRUE)
第一个参数是数值或者其他类型的向量
第二个参数INDEX
是另一个向量,长度和第一个向量相同,用来表明第一个向量中的各个元素分别属于哪一组。
第三个参数FUN
是你想应用的函数。
举例
x是一个数组向量,其中包括10个正态随机数、10个均匀随机数、10个均值是1的正态随机数;这些数字可以分成三组,用函数gl()
创建因子变量,这个因子变量有三个水平,每个水平重复10次:
> x <- c(rnorm(10), runif(10), rnorm(10, 1))
> f <- gl(3, 10)
> x
[1] -1.22192295 1.23396169 -0.74551533 0.10967076 -1.89038874
[6] 1.51580225 0.44769556 0.64438076 0.33981352 1.14463578
[11] 0.19257671 0.66332397 0.98498225 0.64714562 0.63170090
[16] 0.20529137 0.05715295 0.85430414 0.19152730 0.72570007
[21] -0.68530428 1.19698816 0.79573025 1.93891480 1.50044997
[26] 2.72678274 0.44608262 0.96684451 1.59033947 0.19885301
> f
[1] 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3
Levels: 1 2 3
应用tapply()
函数,求每组数据的平均值
> tapply(x, f, mean, simplify = FALSE)
$`1`
[1] 0.1578133
$`2`
[1] 0.5153705
$`3`
[1] 1.067568
如果设置为不简化,那么结果将返回一个列表。
返回观测值的范围,range()
得到每组中的最大值和最小值:
> tapply(x, f, range)
$`1`
[1] -1.890389 1.515802
$`2`
[1] 0.05715295 0.98498225
$`3`
[1] -0.6853043 2.7267827
参考资料:
视频课程 R Programming by Johns Hopkins University:https://www.coursera.org/learn/r-programming/home/welcome
讲义 Programming for Data Science :https://bookdown.org/rdpeng/rprogdatascience/R
欢迎大家跟我一起上车~~~~请关注
猜你喜欢
10000+:肠道细菌 人体上的生命 宝宝与猫狗 梅毒狂想曲 提DNA发Nature 实验分析谁对结果影响大 Cell微生物专刊
文献阅读 热心肠 SemanticScholar Geenmedical
16S功能预测 PICRUSt FAPROTAX Bugbase Tax4Fun
写在后面
为鼓励读者交流、快速解决科研困难,我们建立了“宏基因组”专业讨论群,目前己有国内外150+ PI,1500+ 一线科研人员加入。参与讨论,获得专业解答,欢迎分享此文至朋友圈,并扫码加主编好友带你入群,务必备注“姓名-单位-研究方向-职称/年级”。技术问题寻求帮助,首先阅读《如何优雅的提问》学习解决问题思路,仍末解决群内讨论,问题不私聊,帮助同行。
学习16S扩增子、宏基因组科研思路和分析实战,关注“宏基因组”
点击阅读原文,跳转最新文章目录阅读