6
R语言基础--循环语句
参考文章
if语句
用法如下所示:
if(条件) 表达式
if(条件) 表达式1 else 表达式2
其中if
语句具体就是包含下面3个元素:
- 关键字
if
;- 条件语句的判断结果,也就是一个逻辑值,是TRUE或FALSE;
- 执行语句,也就是上面提到的
表达式
。
if
语句案例1-简单案例
先看一个简单的案例,代码如下所示:
p <- 0.03 ;{
if (p <= 0.05)
print("p<=0.05!")
else print("p > 0.05!")
}
运行结果如下所示:
p <- 0.03 ;{
+ if (p <= 0.05)
+ print("p<=0.05!")
+ else print("p > 0.05!")
+ }
[1] "p<=0.05!"
if
语句案例2-函数体中添加if语句
现在编写一个函数,命名为priceCalulator()
,这个函数的功能在于按照服务时长计算应该收取的费用,函数接收2个参数,分别是服务时长(hours)与每小时的单价(pph),代码如下所示:
priceCalculator <- function(hours, pph=40){
net.price <- hours*pph
round(net.price)
}
现在如果我们还要添加一个功能,例如有一批大客户,如果他们的服务需求时间长高于100小时,就打9折,也就是说,如果hours大于100,最后的总价乘以0.9,代码更改如下所示:
priceCalculator <- function(hours, pph=40){
net.price <- hours*pph
if(hours > 100){
net.price <- net.price*0.9
}
round(net.price)
}
运行结果如下所示:
priceCalculator(hours = 55)
[1] 2200
priceCalculator(hours = 110)
[1] 3960
if
语句的省略形式
如果if语句的条件只有一行的话,那么就可以省略两旁的大括号,例如前面的服务计费案例可以改为如下形式:
if(hours > 100) net.price <- net.price*0.9
完整的代码如下所示:
priceCalculator <- function(hours, pph=40){
net.price <- hours*pph
if(hours > 100) net.price <- net.price*0.9
round(net.price)
}
ifelse
语句
if...else
是if
条件语句的扩展,它的用法如下:
if(条件语句1){
表达式1
}else{表达式2
}
其中,当
条件语句1
为TRUE
时,执行表达式1
,当条件语句1
为FALSE
时,执行表达式2
。
还以前面的if
语句中的服务费用案例继续说明一下,在一些国家,针对不同类型的客户(公共组织或私有组织),除了就会的服务费外,还会收取不同的税(VAT),假设公共组织需要支付6%的VAT,而私有组织需要支付12%的VAT,此时可以在priceCalculator()
函数中添加一个新的参数public
,用于判断组织是否是公共组织,现在代码更改如下所示:
priceCalculator <- function(hours, pph=40, public = TRUE){
net.price <- hours*pph
if(hours > 100) net.price <- net.price*0.9
if(public){
tot.price <- net.price * 1.06
} else {
tot.price <- net.price * 1.12
}
round(tot.price)
}
例如同样工作25时,对于公共和私有组织客户的收费是不一样的,计算结果如下所示:
priceCalculator(25, public = TRUE)
[1] 1060
priceCalculator(25, public = FALSE)
[1] 1120
上面代码的运行过程就使用到了if...else...
语句,如果参数public
的值为TRUE
,那么税后价是总价乘以1.06,否则要乘以1.12。
对于if...else...
语句的表达式而言,如果只有一行代码,可以省略大括号,如下所示:
if(public) tot.price <- net.price * 1.06 else
tot.price <- net.price * 1.12
其中
else
要放到行末,不能放到下一行的行首,这是因为只要一个命令明显没有结束,R会自动读取多行代码,将它们合成一行,如果else
没有放在行末,那么R会认为第一行代码已经结束,从而在执行下一行代码时报错,只有当条件表达式出现在函数中,并且一次性地Source整个脚本文件时,else才能出现在行首。
if...else...
还能继续简化,我们可以把if
理解为一个函数,它的计算结果是TRUE
或FALSE
,因此可以把if表达式赋予一个对象,或者直接用到计算中,这里可以省略重新计算net.price
的过程,直接将结果赋给tot.price`,如下所示:
tot.price <- net.price * if(public) 1.06 else 1.12
如果这么写,R会先执行if...else
表达式,得到结果后,再乘以net.price
,然后将结果赋给tot.price
。
判断选择的向量化
还以上面的priceCalculator()
函数为例说明一下,这个函数的参数是一个数,而非一个向量,如果要计算一个向量就会出现警告信息,如下所示:
priceCalculator <- function(hours, pph=40, public = TRUE){
net.price <- hours*pph
if(hours > 100) net.price <- net.price*0.9
if(public){
tot.price <- net.price * 1.06
} else {
tot.price <- net.price * 1.12
}
round(tot.price)
}
priceCalculator(c(25, 110))
计算结果如下所示:
priceCalculator(c(25, 110))
[1] 1060 4664
Warning message:
In if (hours > 100) net.price <- net.price * 0.9 :
the condition has length > 1 and only the first element will be used
从警告信息可以看出,R会告诉我们丢失了一些信息,并且反映出了整个计算结果就是错误的,因此第二个客户的最终结果应该是4198,而非4664,如下所示:
priceCalculator(110)
[1] 4198
这是因为警告信息中提示,if
表达式只能处理单个值,而hours > 100
返回的是两个值,如下所示:
c (25, 110) > 100
[1] FALSE TRUE
ifelse()
函数
如果使用ifelse()
函数就能解决上面提到的问题,ifelse()
函数接收3个参数:
- 包含逻辑值的条件向量;
- 包含返回值的向量,它仅在对应条件向量值为TRUE时才被返回;
- 另外一个包含返回值的向量,它仅在对应条件向量为FALSE时才被返回。
ifelse
案例1-简单案例
先看一个简单的ifelse
案例,如下所示:
ifelse(c(1,3) < 2.5, 1:2, 3:4)
[1] 1 4
这行语句的运行过程为:
- 条件表达式
c(1,3) < 2.5
被解析成一个逻辑向量,计算结果为[1] TRUE FALSE
; - 这个逻辑向量的第一个值为
TRUE
,因此1确实小于2.5,所以结果向量的第1个元素为第2个参数的第1个元素,也就是1; - 条件向量的第2个值为
FALSE
,因为3比2.5大,所以ifelse()
函数将取第3个参数的第2个值(也就是4)作为结果向量的第2个元素。 - 把选出的值合成一个向量作为结果返回。
再来看一个简单的案例:
x <- c(1,1,1,0,0,1,1)
ifelse(x != 1, 1, 0)
# 若果x的值不等于1,输出1,否则输出0
# [1] 0 0 0 1 1 0 0
现在我们更改priceCalculator()
函数,假设有2个客户,服务时长分别为25小时和110小时,那么就可以采用下面的代码完成计算:
my.hours <- c(25, 110)
my.hours*40*ifelse(my.hours > 100, 0.9, 1)
[1] 1000 3960
在上面的代码中,ifelse()
函数会计算my.hours > 100
的结果,分别是FALSE
和TRUE
,其中,25对应的是FALSE
,那么结果就是my.hour*40*1
,110对应的是TRUE
,结果就是my.hour*40*0.9
。
现在继续改造priceCalculator()
函数,例之成为真正能处理向量的函数,此时需要改造public
参数,代码如下所示:
priceCalculator <- function(hours, pph=40, public){
net.price <- hours*pph
net.price <- net.price * ifelse(hours > 100, 0.9, 1)
tot.price <- net.price * ifelse(public, 1.06, 1.12)
round(tot.price)
}
clients <- data.frame(
hours = c(25, 110, 125, 40),
public = c(TRUE, TRUE, FALSE, FALSE)
)
with(clients, priceCalculator(hours, public = public))
运行结果如下所示:
with(clients, priceCalculator(hours, public = public))
[1] 1060 4198 5040 1792
ifelse案例2-在函数中添加ifelse
看一个案例,有两个向量,分别为x和y,它们是时间序列,例如它们是每小时收集的气温和气压测量值,我们定义两者的相关性为x和y同时上升或下降次数占总观测数的比例(其实就是Kendall’s tau方法),也就是计算y[i+1]-y[i]1
与x[i+1]-x[i]
符号相同时的次数占总数i
的比例,代码如下所示:
findud <- function(v){
vud <- v[-1]-v[-length(v)]
return(ifelse(vud > 0, 1,-1))
}
udcorr <- function(x,y){
ud <- lapply(list(x,y),findud)
return(mean(ud[[1]] == ud[[2]]))
}
现在看一个案例,如下所示:
x <- c(5,12,13,3,6,0,1,15,16,8,88)
y <- c(4,2,3,23,6,10,11,12,6,3,2)
udcorr(x,y)
[1] 0.4
在这个案例中,x和y在10次中同时上升3次(第一次同时上升是12到13,2到3),并同时下降1次,得到的相关性估计为4/10=0.4。
这个函数的思路就是先把x和y的值编码为1和-1,其中1代表当前观测值较前一个增加,-1表示减少,这个转换过程就是通过vud <- v[-1]-v[-length(v)]
这行代码实现的。
其中,x[-1]-x[-11]
表示的就是第2个元素减去第1个元素,第3个元素减去第2个元素等等,如下所示:
x[-1]-x[-11]
[1] 7 1 -10 3 -6 1 14 1 -8 80
return(ifelse(vud > 0, 1,-1))
这个语句就是把上面的向量再转换为1与-1,如下所示:
ifelse(x[-1]-x[-11] >0, 1, -1)
[1] 1 1 -1 1 -1 1 1 1 -1 1
接着又构建了一个udcorr
这个函数。其中ud <- lapply(list(x,y),findud)
这行的功能就是,使用lapply()
函数对xy构成的这个列表使用findud函数,如下所示:
list(x,y)
[[1]]
[1] 5 12 13 3 6 0 1 15 16 8 88
[[2]]
[1] 4 2 3 23 6 10 11 12 6 3 2
lapply(list(x,y),findud)
[[1]]
[1] 1 1 -1 1 -1 1 1 1 -1 1
[[2]]
[1] -1 1 1 -1 1 1 1 -1 -1 -1
最后使用return(mean(ud[[1]] == ud[[2]]))
这行代码来计算里面的均数,因为ud[[1]] == ud[[2]]
的结果是一个逻辑向量,如下所示:
ud[[1]] == ud[[2]]
[1] FALSE TRUE FALSE FALSE FALSE TRUE TRUE FALSE TRUE FALSE
对于逻辑向量来说,TRUE就是1,FALSE就是0,因此计算这个向量的均值就是计算TRUE占总向量数目的比例,如下所示:
mean(ud[[1]] == ud[[2]])
[1] 0.4
# 逻辑向量也能使用数学函数,如下所示,TRUE就是1
sum(c(TRUE,TRUE,TRUE))
[1] 3
但是在R中,上面的代码可以用diff()
这个函数来进行计算,这个函数可以对向量做“滞后”运算,这个运算的效果如下所示:
x
[1] 5 12 13 3 6 0 1 15 16 8 88
diff(x)
[1] 7 1 -10 3 -6 1 14 1 -8 80
从上面的结果我们可以看出来,这个滞后运算其实就是后一个数减去前一个数。与滞后运算相匹配的函数是sign()
函数,它可以将正值与负值转换为1,-1或0,如下所示:
x
[1] 5 12 13 3 6 0 1 15 16 8 88
diff(x)
[1] 7 1 -10 3 -6 1 14 1 -8 80
sign(diff(x))
[1] 1 1 -1 1 -1 1 1 1 -1 1
因此,我们可以把undcorr()
函数换一种方式写,如下所示:
udcorr <- function(x,y){
mean(sign(diff(x))==sign(diff(y)))
}
ifelse
案例3-转换某个字符串
ifelse()
可以嵌套使用,看一个案例,g
是一个鲍鱼的数据集,性别被编号为M、F或I(I是指Infant,幼虫的意思),现在我们将这些编号转换为1、2或3,如下所示:
g <- c("M","F","F","I","M","M","F")
g
[1] "M" "F" "F" "I" "M" "M" "F"
ifelse(g == "M",1,ifelse(g == "F",2,3))
[1] 1 2 2 3 1 1 2
上面代码的运行过程是这个样子的:
- R先执行外层的
ifelse()
,其中当g=="M"
时,返回1;否则就返回ifelse(g == "F",2,3)
; - 当返回的是
ifelse(g == "F",2,3)
,这是一个表达式,还能计算,那么如果g=="F"
,就返回2,否则返回3。
如果希望按照性别形成子集,那么可以使用which()
函数寻找M、F或I对应元素的编号,如下所示:
m <- which(g == "M")
f <- which(g == "F")
i <- which(g == "I")
m
[1] 1 5 6
f
[1] 2 3 7
i
[1] 4
如果想把这些子集保存在一个列表中,可以按下面的代码操作,如下所示:
grps <- list()
for (gen in c("M","F","I")){
grps[[gen]] <- which(g == gen)
}
grps
结果如下所示:
grps
$M
[1] 1 5 6
$F
[1] 2 3 7
$I
[1] 4
嵌套if...else...
表达式
有的时候需要处理大于2种的选择,此时就需要使用嵌套if...else...
嵌套语句,例如还以前面的priceCalculator()
函数为例说明一下,客户假设说有3种,私有组织,税率为12%,公共组织税率为6%,国外客户为0%,现在改造priceCalculator()
函数,代码如下所示:
priceCalculator <- function(hours, pph=40, client){
net.price <- hours*pph
if(client =='private'){
tot.price <- net.price * 1.12
} else {
if(client == 'public'){
tot.price <- net.price * 1.06
} else {
tot.price <- net.price * 1
}
}
round(tot.price)
}
priceCalculator(hours=50,client="private")
priceCalculator(hours=50,client="public")
priceCalculator(hours=50,client="abroad")
计算结果如下所示:
priceCalculator(hours=50,client="private")
[1] 2240
priceCalculator(hours=50,client="public")
[1] 2120
priceCalculator(hours=50,client="abroad")
[1] 2000
虽然代码已经进行了改造,可以根据不同的客户类型计算不同的收费,但是此时这个函数还只一次只能处理一个值,无法进行向量化逻辑嵌套,对此,可以使用ifelse()
函数进行嵌套,如下所示:
priceCalculator <- function(hours, pph=40, client){
net.price <- hours*pph
VAT <- ifelse(client =='private', 1.12,
ifelse(client=='public', 1.06, 1))
tot.price <- net.price*VAT
round(tot.price)
}
privateData <- data.frame(
hours = c(25, 110, 125, 40),
type = c('private', 'private', 'private', 'private')
)
with(privateData, priceCalculator(hours, client = type))
publicData <- data.frame(
hours = c(25, 110, 125, 40),
type = c('public', 'public', 'public', 'public')
)
with(publicData, priceCalculator(hours, client = type))
abroadData <- data.frame(
hours = c(25, 110, 125, 40),
type = c('abroad', 'abroad', 'abroad', 'abroad')
)
with(abroadData, priceCalculator(hours, client = type))
mixData <- data.frame(
hours = c(25, 110, 125, 40),
type = c('private', 'public', 'abroad', 'abroad')
)
with(mixData, priceCalculator(hours, client = type))
运行结果如下所示:
with(privateData, priceCalculator(hours, client = type))
[1] 1120 4928 5600 1792
publicData <- data.frame(
+ hours = c(25, 110, 125, 40),
+ type = c('public', 'public', 'public', 'public')
+ )
with(publicData, priceCalculator(hours, client = type))
[1] 1060 4664 5300 1696
abroadData <- data.frame(
+ hours = c(25, 110, 125, 40),
+ type = c('abroad', 'abroad', 'abroad', 'abroad')
+ )
with(abroadData, priceCalculator(hours, client = type))
[1] 1000 4400 5000 1600
mixData <- data.frame(
+ hours = c(25, 110, 125, 40),
+ type = c('private', 'public', 'abroad', 'abroad')
+ )
with(mixData, priceCalculator(hours, client = type))
[1] 1120 4664 5000 1600
switch
处理多种选择
if...else...
嵌套语句适用于多种条件的判断,假如判断的条件只有1个就不需要使用if
嵌套,使用switch()
函数即可。
在前面的priceCalculator()
函数的案例中,税率实际上取决于客户的类型,即公共组织、私有组织或者外国客户,我们有3种可能,每种对应一种特定的税率,在这种情况下,就可以使用switch
函数来简化判断,如下所示:
VAT <- switch(client, private=1.12, public=1.06, abroad=1)
完整代码如下所示:
priceCalculator <- function(hours, pph=40, client){
net.price <- hours*pph
VAT <- switch(client, private=1.12, public=1.06, abroad=1)
tot.price <- net.price*VAT
round(tot.price)
}
priceCalculator(25, client='private')
priceCalculator(25, client='public')
priceCalculator(25, pph=40, 'abroad')
priceCalculator(25, pph=40, 'other')
运行结果如下所示:
priceCalculator(25, client='private')
[1] 1120
priceCalculator(25, client='public')
[1] 1060
priceCalculator(25, pph=40, 'abroad')
[1] 1000
priceCalculator(25, pph=40, 'other')
numeric(0)
switch()
函数调用的过程如下:
- 用一个单一值(Single Value)作为第1个参数(这里就是client),需要注意的是,
switch()
无法处理向量化数据,只能处理单一的数据,因此第1个参数不能是向量; - 在第1个参数后,列出所有可能的情况及对应的值。
- 这里需要注意的是,我们只列出了3种情况,其实第3种情况
abroad
对应的是其它情况
,如果使用其它的字符口中,则无法进行计算,后面会提到如何解这个问题。
switch()
函数的第1个参数并不一定是一个特定的值,也可以是某个表达式,只要导出的结果是一个字符向量或数字即可,如下所示:
switch(1, 'some value', 'something else', 'some more')
[1] "some value"
switch(2, 'some value', 'something else', 'some more')
[1] "something else"
switch(3, 'some value', 'something else', 'some more')
[1] "some more"
在switch()
中使用默认值
在switch()
函数的调用中,并不需要列出所有可能的情况,如果对于所有不满足条件的判断值,都返回同一个结果的话,可以把这个结果放在参数列表的末尾,并去掉前面的判断值,因此可以看下面的代码,这与ifelse
调用的结果一样:
VAT <- switch(client, private=1.12, pblic=1.06, 1)
现在就可以使用这个解决前面提出的那个问题了,如下所示:
priceCalculator <- function(hours, pph=40, client){
net.price <- hours*pph
VAT <- switch(client, private=1.12, public=1.06, 1)
tot.price <- net.price*VAT
round(tot.price)
}
priceCalculator(25, client='private')
priceCalculator(25, client='public')
priceCalculator(25, pph=40, 'abroad')
priceCalculator(25, pph=40, 'other')
运行结果如下所示:
priceCalculator(25, client='private')
[1] 1120
priceCalculator(25, client='public')
[1] 1060
priceCalculator(25, pph=40, 'abroad')
[1] 1000
priceCalculator(25, pph=40, 'other')
[1] 1000
在switch()
函数中的第3个参数里,把abroad=1
改为1
则就说明了,这是其他情况,而非仅仅是abroad
这一种情况。
for 循环
for
循环用法如下所示:
for (i in values){
... do someting...
}
for
循环案例1-简单案例
先看一个for循环的最简单案例,如下所示:
for(i in 1:20){
cat(i);
cat(" ");
i=i+3;
}
运行结果如下所示:
for(i in 1:20){
+ cat(i);
+ cat(" ");
+ i=i+3;
+ }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
从上面结果,我们可以看出,循环中处理的对象是1:20这20个数,对每个数进行操作不管循环内部i如何变化。因此,循环体内的i=i+3就不会对循环条件中的变量造成改变,具体执行过程相当于:i=1
->
i=i+3
,此时i=4
;->
第一次循环结束,第二次循环开始,i=2
。尤其注意,i变成4又变成2,所以打印出来的结果是,1,2,3,4….20的连续值。如果想要随意的改变条件中的变量,可以使用while循环,如下所示:
i=1
while(i<=20){
+ cat(i);
+ cat(" ");
+ i=i+3
+ }
1 4 7 10 13 16 19
for
循环案例2-priceCalculator()
函数
现在使用for
循环来改造原来的priceCalculator()
函数,使这个函数可以一次地计算出多个客户类型的结果,如下所示:
priceCalculator <- function(hours, pph=40, client){
net.price <- hours*pph*
ifelse(hours > 100, 0.9, 1)
VAT <- numeric(0)
for (i in client){
VAT <- c(VAT, switch(i, private=1.12, public=1.06, 1))
}
tot.price <- net.price*VAT
round(tot.price)
}
在这段代码中,做了以下修改:
- 创建了一个长度为0的数值向量,即
VAT <- numeric(0)
; - 对向量
client
中的每个值,用switch()
来进行逻辑判断,选出对应的VAT
值; - 每次循环,都将
switch()
的结果放入向量VAT的末尾。
这个函数的计算结果就是向量VAT包含每个客户对应的税率值,看一下计算结果:
clients <- data.frame(
hours = c(25, 110, 125, 40),
type = c('public','abroad','private','abroad')
)
clients
priceCalculator(clients$hours,client = clients$type)
计算结果如下所示:
clients
hours type
1 25 public
2 110 abroad
3 125 private
4 40 abroad
priceCalculator(clients$hours,client = clients$type)
[1] 1060 3960 5040 1600
any()与all()
使用any()
和all()
函数可以对某个变量进行判断,如下所示:
x <- 1:10
x
[1] 1 2 3 4 5 6 7 8 9 10
any(x>8)
[1] TRUE
any(x>88)
[1] FALSE
all(x>88)
[1] FALSE
all(x>0)
[1] TRUE
现在看一个案例,假设一个向量由若干个0或1构成,我们想要找其中连续出现1的游程(在一个0和1组成的序列中,一个由连续的0或1构成的串称为一个游程(run))。例如对于向量x(假设它为1,0,0,1,1,1,0,1,1),从它第4个索引处开始有长度为3的游程,而长度为2的游程分别始于第4,第5和第8索引的位置,因此现在我们构建一个函数,返回结果为(4,5,8),如下所示:
findruns <- function(x,k){
n <- length(x)
runs <- NULL
for (i in 1:(n-k+1)){
if (all(x[i:(i+k-1)]==1))
runs <- c(runs,i)
}
return(runs)
}
现在运行这个函数,如下所示:
x <- c(1,0,0,1,1,1,0,1,1)
findruns(x,3)
[1] 4
findruns(x,2)
[1] 4 5 8
findruns(x,6)
NULL