R语言笔记之循环语句

if语句

用法如下所示:

1
2
if(条件) 表达式
if(条件) 表达式1 else 表达式2

其中if语句具体就是包含下面3个元素:

  1. 关键字if
  2. 条件语句的判断结果,也就是一个逻辑值,是TRUE或FALSE;
  3. 执行语句,也就是上面提到的表达式

if语句案例1-简单案例

先看一个简单的案例,代码如下所示:

1
2
3
4
5
p <- 0.03 ;{
if (p <= 0.05)
print("p<=0.05!")
else print("p > 0.05!")
}

运行结果如下所示:

1
2
3
4
5
6
> 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),代码如下所示:

1
2
3
4
priceCalculator <- function(hours, pph=40){
net.price <- hours*pph
round(net.price)
}

现在如果我们还要添加一个功能,例如有一批大客户,如果他们的服务需求时间长高于100小时,就打9折,也就是说,如果hours大于100,最后的总价乘以0.9,代码更改如下所示:

1
2
3
4
5
6
7
priceCalculator <- function(hours, pph=40){
net.price <- hours*pph
if(hours > 100){
net.price <- net.price*0.9
}
round(net.price)
}

运行结果如下所示:

1
2
3
4
> priceCalculator(hours = 55)
[1] 2200
> priceCalculator(hours = 110)
[1] 3960

if语句的省略形式

如果if语句的条件只有一行的话,那么就可以省略两旁的大括号,例如前面的服务计费案例可以改为如下形式:

1
if(hours > 100) net.price <- net.price*0.9

完整的代码如下所示:

1
2
3
4
5
priceCalculator <- function(hours, pph=40){
net.price <- hours*pph
if(hours > 100) net.price <- net.price*0.9
round(net.price)
}

ifelse语句

if...elseif条件语句的扩展,它的用法如下:

1
2
3
4
if(条件语句1){
表达式1
}else{表达式2
}

其中,当条件语句1TRUE时,执行表达式1,当条件语句1FALSE时,执行表达式2

还以前面的if语句中的服务费用案例继续说明一下,在一些国家,针对不同类型的客户(公共组织或私有组织),除了就会的服务费外,还会收取不同的税(VAT),假设公共组织需要支付6%的VAT,而私有组织需要支付12%的VAT,此时可以在priceCalculator()函数中添加一个新的参数public,用于判断组织是否是公共组织,现在代码更改如下所示:

1
2
3
4
5
6
7
8
9
10
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时,对于公共和私有组织客户的收费是不一样的,计算结果如下所示:

1
2
3
4
> priceCalculator(25, public = TRUE)
[1] 1060
> priceCalculator(25, public = FALSE)
[1] 1120

上面代码的运行过程就使用到了if...else...语句,如果参数public的值为TRUE,那么税后价是总价乘以1.06,否则要乘以1.12。

对于if...else...语句的表达式而言,如果只有一行代码,可以省略大括号,如下所示:

1
2
if(public) tot.price <- net.price * 1.06 else
tot.price <- net.price * 1.12

其中else要放到行末,不能放到下一行的行首,这是因为只要一个命令明显没有结束,R会自动读取多行代码,将它们合成一行,如果else没有放在行末,那么R会认为第一行代码已经结束,从而在执行下一行代码时报错,只有当条件表达式出现在函数中,并且一次性地Source整个脚本文件时,else才能出现在行首。

if...else...还能继续简化,我们可以把if理解为一个函数,它的计算结果是TRUEFALSE,因此可以把if表达式赋予一个对象,或者直接用到计算中,这里可以省略重新计算net.price的过程,直接将结果赋给tot.price`,如下所示:

1
tot.price <- net.price * if(public) 1.06 else 1.12

如果这么写,R会先执行if...else表达式,得到结果后,再乘以net.price,然后将结果赋给tot.price

判断选择的向量化

还以上面的priceCalculator()函数为例说明一下,这个函数的参数是一个数,而非一个向量,如果要计算一个向量就会出现警告信息,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
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))

计算结果如下所示:

1
2
3
4
5
> 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,如下所示:

1
2
> priceCalculator(110)
[1] 4198

这是因为警告信息中提示,if表达式只能处理单个值,而hours > 100返回的是两个值,如下所示:

1
2
> c (25, 110) > 100
[1] FALSE TRUE

ifelse()函数

如果使用ifelse()函数就能解决上面提到的问题,ifelse()函数接收3个参数:

  1. 包含逻辑值的条件向量;
  2. 包含返回值的向量,它仅在对应条件向量值为TRUE时才被返回;
  3. 另外一个包含返回值的向量,它仅在对应条件向量为FALSE时才被返回。

ifelse案例1-简单案例

先看一个简单的ifelse案例,如下所示:

1
2
> ifelse(c(1,3) < 2.5, 1:2, 3:4)
[1] 1 4

这行语句的运行过程为:

  1. 条件表达式c(1,3) < 2.5被解析成一个逻辑向量,计算结果为[1] TRUE FALSE
  2. 这个逻辑向量的第一个值为TRUE,因此1确实小于2.5,所以结果向量的第1个元素为第2个参数的第1个元素,也就是1;
  3. 条件向量的第2个值为FALSE,因为3比2.5大,所以ifelse()函数将取第3个参数的第2个值(也就是4)作为结果向量的第2个元素。
  4. 把选出的值合成一个向量作为结果返回。

再来看一个简单的案例:

1
2
3
4
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小时,那么就可以采用下面的代码完成计算:

1
2
3
> my.hours <- c(25, 110)
> my.hours*40*ifelse(my.hours > 100, 0.9, 1)
[1] 1000 3960

在上面的代码中,ifelse()函数会计算my.hours > 100的结果,分别是FALSETRUE,其中,25对应的是FALSE,那么结果就是my.hour*40*1,110对应的是TRUE,结果就是my.hour*40*0.9

现在继续改造priceCalculator()函数,例之成为真正能处理向量的函数,此时需要改造public参数,代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
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))

运行结果如下所示:

1
2
> 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]1x[i+1]-x[i]符号相同时的次数占总数i的比例,代码如下所示:

1
2
3
4
5
6
7
8
9
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]]))
}

现在看一个案例,如下所示:

1
2
3
4
> 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为例说明一下,看下面的示意图就行了:

mark

其中,x[-1]-x[-11]表示的就是第2个元素减去第1个元素,第3个元素减去第2个元素等等,如下所示:

1
2
> x[-1]-x[-11]
[1] 7 1 -10 3 -6 1 14 1 -8 80

return(ifelse(vud > 0, 1,-1))这个语句就是把上面的向量再转换为1与-1,如下所示:

1
2
> 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函数,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
> 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]]的结果是一个逻辑向量,如下所示:

1
2
> ud[[1]] == ud[[2]]
[1] FALSE TRUE FALSE FALSE FALSE TRUE TRUE FALSE TRUE FALSE

对于逻辑向量来说,TRUE就是1,FALSE就是0,因此计算这个向量的均值就是计算TRUE占总向量数目的比例,如下所示:

1
2
3
4
5
6
> mean(ud[[1]] == ud[[2]])
[1] 0.4
# 逻辑向量也能使用数学函数,如下所示,TRUE就是1
> sum(c(TRUE,TRUE,TRUE))
[1] 3

但是在R中,上面的代码可以用diff()这个函数来进行计算,这个函数可以对向量做”滞后”运算,这个运算的效果如下所示:

1
2
3
4
> 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,如下所示:

1
2
3
4
5
6
> 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()函数换一种方式写,如下所示:

1
2
3
udcorr <- function(x,y){
mean(sign(diff(x))==sign(diff(y)))
}

ifelse案例3-转换某个字符串

ifelse()可以嵌套使用,看一个案例,g是一个鲍鱼的数据集,性别被编号为M、F或I(I是指Infant,幼虫的意思),现在我们将这些编号转换为1、2或3,如下所示:

1
2
3
4
5
> 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

上面代码的运行过程是这个样子的:

  1. R先执行外层的ifelse(),其中当g=="M"时,返回1;否则就返回ifelse(g == "F",2,3)
  2. 当返回的是ifelse(g == "F",2,3),这是一个表达式,还能计算,那么如果g=="F",就返回2,否则返回3。

如果希望按照性别形成子集,那么可以使用which()函数寻找M、F或I对应元素的编号,如下所示:

1
2
3
4
5
6
7
8
9
> 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

如果想把这些子集保存在一个列表中,可以按下面的代码操作,如下所示:

1
2
3
4
5
grps <- list()
for (gen in c("M","F","I")){
grps[[gen]] <- which(g == gen)
}
grps

结果如下所示:

1
2
3
4
5
6
7
> 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()函数,代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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")

计算结果如下所示:

1
2
3
4
5
6
> priceCalculator(hours=50,client="private")
[1] 2240
> priceCalculator(hours=50,client="public")
[1] 2120
> priceCalculator(hours=50,client="abroad")
[1] 2000

虽然代码已经进行了改造,可以根据不同的客户类型计算不同的收费,但是此时这个函数还只一次只能处理一个值,无法进行向量化逻辑嵌套,对此,可以使用ifelse()函数进行嵌套,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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))

运行结果如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
> 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函数来简化判断,如下所示:

1
VAT <- switch(client, private=1.12, public=1.06, abroad=1)

完整代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
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')

运行结果如下所示:

1
2
3
4
5
6
7
8
> 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()函数调用的过程如下:

  1. 用一个单一值(Single Value)作为第1个参数(这里就是client),需要注意的是,switch()无法处理向量化数据,只能处理单一的数据,因此第1个参数不能是向量;
  2. 在第1个参数后,列出所有可能的情况及对应的值。
  3. 这里需要注意的是,我们只列出了3种情况,其实第3种情况abroad对应的是其它情况,如果使用其它的字符口中,则无法进行计算,后面会提到如何解这个问题。

switch()函数的第1个参数并不一定是一个特定的值,也可以是某个表达式,只要导出的结果是一个字符向量或数字即可,如下所示:

1
2
3
4
5
6
> 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调用的结果一样:

1
VAT <- switch(client, private=1.12, pblic=1.06, 1)

现在就可以使用这个解决前面提出的那个问题了,如下所示:

1
2
3
4
5
6
7
8
9
10
11
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')

运行结果如下所示:

1
2
3
4
5
6
7
8
> 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循环用法如下所示:

1
2
3
for (i in values){
... do someting...
}

for循环案例1-简单案例

先看一个for循环的最简单案例,如下所示:

1
2
3
4
5
for(i in 1:20){
cat(i);
cat(" ");
i=i+3;
}

运行结果如下所示:

1
2
3
4
5
6
> 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循环,如下所示:

1
2
3
4
5
6
7
> i=1
> while(i<=20){
+ cat(i);
+ cat(" ");
+ i=i+3
+ }
1 4 7 10 13 16 19

for循环案例2-priceCalculator()函数

现在使用for循环来改造原来的priceCalculator()函数,使这个函数可以一次地计算出多个客户类型的结果,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
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)
}

在这段代码中,做了以下修改:

  1. 创建了一个长度为0的数值向量,即VAT <- numeric(0)
  2. 对向量client中的每个值,用switch()来进行逻辑判断,选出对应的VAT值;
  3. 每次循环,都将switch()的结果放入向量VAT的末尾。

这个函数的计算结果就是向量VAT包含每个客户对应的税率值,看一下计算结果:

1
2
3
4
5
6
clients <- data.frame(
hours = c(25, 110, 125, 40),
type = c('public','abroad','private','abroad')
)
clients
priceCalculator(clients$hours,client = clients$type)

计算结果如下所示:

1
2
3
4
5
6
7
8
> 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()函数可以对某个变量进行判断,如下所示:

1
2
3
4
5
6
7
8
9
10
11
> 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),如下所示:

1
2
3
4
5
6
7
8
9
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)
}

现在运行这个函数,如下所示:

1
2
3
4
5
6
7
> 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

参考资料