R语言基础--循环语句

6

R语言基础--循环语句

参考文章

if语句

用法如下所示:
if(条件) 表达式
if(条件) 表达式1 else 表达式2

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

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

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...elseif条件语句的扩展,它的用法如下:

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

其中,当条件语句1TRUE时,执行表达式1,当条件语句1FALSE时,执行表达式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理解为一个函数,它的计算结果是TRUEFALSE,因此可以把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个参数:

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

ifelse案例1-简单案例

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

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. 把选出的值合成一个向量作为结果返回。

再来看一个简单的案例:

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的结果,分别是FALSETRUE,其中,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]1x[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

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

  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对应元素的编号,如下所示:

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()函数调用的过程如下:

  1. 用一个单一值(Single Value)作为第1个参数(这里就是client),需要注意的是,switch()无法处理向量化数据,只能处理单一的数据,因此第1个参数不能是向量;
  2. 在第1个参数后,列出所有可能的情况及对应的值。
  3. 这里需要注意的是,我们只列出了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)
}

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

  1. 创建了一个长度为0的数值向量,即VAT <- numeric(0)
  2. 对向量client中的每个值,用switch()来进行逻辑判断,选出对应的VAT值;
  3. 每次循环,都将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

你可能感兴趣的:(R语言基础--循环语句)