写在前面:
在前面两篇文章R语言入门系列之一与R语言入门系列之二中,我分别介绍了R语言中的对象与结构、数据的输入输出及可视化。基于前面的基础,今天我介绍一下R语言中基础的程序结构,来帮助我们完成更复杂的数据处理任务。此外,如果你有大批量数据处理、可视化任务,需要着重学习R脚本在命令行的调用方式以及命令行参数的使用方法。
写好的R语言程序一般保存为R脚本,这样在以后完成相似数据处理任务时可以方便地直接调用。在linux系统命令行,我们可以使用“Rscript”命令来调用运行写好的程序,并添加一些必须的命令行参数;在Windows系统的Rstudio中,可以使用source()函数来调用写好的R脚本。
1重复循环
R中的循环主要有for和while结构。for循环重复执行一个语句,直到value值不再包含在向量vector中为止,for结构的基本语法如下所示:
代码语言:javascript复制for (value in vector) {
statements
}
例如我们想要将群落数据小数值转换成百分值,如下所示:
代码语言:javascript复制data=read.table("phylum.txt", header=TRUE)
rownames(data)=data[, 1]
data=data[, -1]
for (i in 1:nrow(data)) {
for (j in 1:ncol(data)) {
data[i, j]=data[i, j]*100
}
}
while结构重复执行一个语句,直到条件语句不为TRUE为止,while结构基本语法如下:
代码语言:javascript复制while (condition) {
statements
}
例如上面for的程序可以改写为:
代码语言:javascript复制i=1
while (i <= nrow(data)) {
j=1
while (j <= ncol(data)) {
data[i, j]=data[i, j]*100
j=j 1
}
i=i 1
}
虽然while可以执行循环,但是本质上是一个不断判断过程,i=i 1实现了程序的连续运行,与for还是有很大区别,因此判断条件必须要完善。
2条件执行
在条件执行也即选择结构中,语句只有在满足一定条件时才会执行,主要有if-else、ifelse、switch三种。if结构基本语法如下所示:
代码语言:javascript复制if(condition) {
statements
}
#或者
if(condition) {
statement1
} else {
statement2
}
ifelse结构是if-else结构的紧凑版本,如下所示:
代码语言:javascript复制ifelse(condition, statement1, statement2)
举例如下:
代码语言:javascript复制a=5
ifelse (a>=3, a<-TRUE, a<-FALSE)
注意,这时候在括号内部必须使用赋值符号!
switch根据一个表达式的值选择语句执行,如下所示:
代码语言:javascript复制switch(expression, case1, case2, case3....)
举例如下:
代码语言:javascript复制feelings=c("sad", "afraid")
for (i in feelings) {
print(
switch(i,
happy="I'm glad you are happy",
afraid="There's nothing to fear",
sad="Cheer up",
angry="Calm down now")
)
}
repeat-if-break函数是常用的一个循环判断符合结构,基本语法如下所示:
代码语言:javascript复制repeat {
commands
if(condition){
break
}
}
repeat会重复执行一个命令直到if判断条件符合然后使用break终止,举例如下:
代码语言:javascript复制a=c("Hello", "loop")
i=1
repeat{
print(a)
i=i 1
if(i==5){
break
}
}
3高级函数
aggregate()函数
对于向量和矩阵,我们可以方便的使用循环等来进行统计计算,然而对含有因子的数据框,aggregate()函数就会大显威力,其使用语法如下:
代码语言:javascript复制aggregate(object, by, FUN, formula...)
其中by是制定进行统计的类别列表,一般为因子变量,FUN为统计函数,可以随意选择。下面我们以R内置数据mtcars为例展示其使用方法:
代码语言:javascript复制attach(mtcars)
aggregate(mpg, by=list(cyl), FUN=mean) #根据cyl进行分组计算mpg均值
结果如下所示:
其结果以数据框的形式储存。
apply函数家族
apply函数家族主要成员如下:
代码语言:javascript复制apply 对数组行或者列使用函数 apply(X, MARGIN, FUN, ...)
lapply 对列表或者向量使用函数 lapply(X, FUN, ...)
sapply 对列表或者向量使用函数 sapply(X, FUN, ..., simplify = TRUE, USE.NAMES = TRUE)
vapply 对列表或者向量使用函数 vapply(X, FUN, FUN.VALUE, ..., USE.NAMES = TRUE)
tapply 对不规则矩阵使用函数 tapply(X, INDEX, FUN = NULL, ..., simplify = TRUE)
eapply 对环境中的值使用函数 eapply(env, FUN, ..., all.names = FALSE, USE.NAMES = TRUE)
mapply 对多个列表或者向量参数使用函数 mapply(FUN, ..., MoreArgs = NULL, SIMPLIFY = TRUE, USE.NAMES = TRUE)
rapply 运用函数递归产生列表 rapply(object, f, classes = "ANY", deflt = NULL,how = c("unlist", "replace", "list"), ...)
apply()通过对数组或者矩阵的一个维度使用函数生成值得列表或者数组、向量:
代码语言:javascript复制apply(X, MARGIN, FUN, ...)
其中X数组,包括矩阵,MARGIN:1表示矩阵行,2表示矩阵列,也可以是c(1,2),举例如下:
最终以向量或矩阵返回结果。
lapply()通过对x的每一个元素运用函数,生成一个与元素个数相同的值列表:
代码语言:javascript复制lapply(X, FUN, ...)
X表示一个列表对象,其余对象将被通过as.list强制转换为list,举例如下:
sapply()是lapply函数的包装版。sapply(x, f, simplify=FALSE, USE.NAMES=FALSE)返回的值与lapply(x, f)是一致的:
代码语言:javascript复制sapply(X, FUN, ..., simplify = TRUE, USE.NAMES = TRUE)
X表示一个向量或者列表对象,其余对象将被通过as.list强制转换为list,simplify为逻辑值或者字符串,指明结果是否应该被简化为向量、矩阵或者高维数组,默认值TRUE。如果simplify="array",结果将返回一个数组。举例如下:
4自定义函数
用户可以根据需求自定义函数,R函数是通过使用关键字function来创建。R函数的定义基本语法如下:
代码语言:javascript复制function_name=function(arg_1, arg_2, ...) {
Function body
return(object)
}
# function_name:这是函数的实际名称。它被存入R环境作为一个对象使用此名称。
# arg:参数是一个占位符。当调用一个函数,传递一个值到参数。参数是可选的,也就是说,一个函数可以含有任何参数。此外参数可以有默认值。
# Function body:函数体包含定义函数是使用来做什么的语句集合。
# return:一个函数的返回值是在函数体中评估计算最后一个表达式的值。
下面我们自己编写一个计算OTU平均相对丰度并排序、筛选高丰度OTU的函数,如下所示:
代码语言:javascript复制otu_select=function(otu, number=nrow(otu), abund=0, mean=TRUE) {
csum=numeric(ncol(otu))
for (i in 1:ncol(otu)) {
csum[i]= sum(otu[, i])
}
for (i in 1:nrow(otu)) {
for (j in 1:ncol(otu)) {
otu[i, j]=otu[i, j]/csum[j]
}
}
ave_abund=numeric(nrow(otu))
for (i in 1:nrow(otu)) {
ave_abund[i]=mean(otu[i, ])
}
newotu=cbind(otu, ave_abund)
newotu=newotu[order(newotu[, ncol(newotu)], decreasing=TRUE),]
newotu=newotu[1:number, ]
newotu=newotu[newotu[, ncol(newotu)]>=abund,]
ifelse (mean==TRUE, abundotu<-newotu, abundotu<-newotu[, -ncol(newotu)])
return(abundotu)
}
上面自定义函数中提供了两种方法筛选高丰度OTU,一个是根据丰度数值进行筛选,另一种是根据OTU数目,返回结果可以包含均值也可以不包含。我们可以直接在R中运行上面程序然后使用这个函数,也可以保存为R脚本然后使用source()函数调用。例如将上面自定义函数保存为otu_select.R并放到当前R工作路径,调用方法如下所示:
代码语言:javascript复制source("otu_select.R")
data=read.table("sample.subsample.otu_table.txt", header=TRUE)
rownames(data)=data[, 1]
data=as.matrix(data[, -1])
newdata=otu_select(data, abund=0.01, mean=FALSE)
运行结果如下所示:
5命令行参数
当在Linux系统命令行运行R脚本时,可以使用commandArgs()设置命令行参数来增强脚本的适用性,我们可以通过下面脚本来查看R语言命令行参数设置规则:
代码语言:javascript复制Args <- commandArgs()
cat("Args[1]=", Args[1],"n")
cat("Args[2]=", Args[1],"n")
cat("Args[3]=", Args[3],"n")
cat("Args[4]=", Args[4],"n")
cat("Args[5]=", Args[5],"n")
cat("Args[6]=", Args[6],"n")
cat("Args[7]=", Args[7],"n")
将上述程序保存为Args.R,在Linux系统命令行运行结果如下所示:
可以发现,前五个为R内置参数,用户输入参数从第6个开始,R脚本中的命令行参数的使用示例如下所示:
如果想忽略R内置参数,则可以如下设置:
代码语言:javascript复制Args <- commandArgs(TRUE)
这样,Args[1]即为用户输入的第一个位置参数。