R语言进阶 | 程序结构控制

在R语言中有两种程序控制结构:分支结构(choices)和循环结构(loops)。

  • 分支结构,比如if()switch(),可以根据输入(判断条件)的不同选择执行不同的代码块;

  • 循环结构,比如forwhilerepeat,则可以重复执行一段代码块,但往往每次重复都有相应参数的改变。

想跳过这一期推文?如果你已经掌握以下知识点👇🏻,Go ahead

  • ififelse()的区别是什么?

  • x分别为True, False, NA时,下面代码中y的值为多少?

    y <- if (x) 3

  • switch("x", x = , y = 2, z = 3)返回值是什么?

分支结构

if语句的基础形式如下所示:

if (condition) true_action
if (condition) true_action else false_action

如果判断语句condition的值为TRUE,执行true_action代码块,相反如果判断语句condition值为FALSE则执行false_action代码块。当执行的代码块包含较多语句时,往往把代码块写入{}中:

grade <- function(x) {
  if (x > 90) {
    "A"
  } else if (x > 80) {
    "B"
  } else if (x > 50) {
    "C"
  } else {
    "F"
  }
}

由于执行for语句可以返回相应的计算值,所以可以用于赋值语句:

x1 <- if (TRUE) 1 else 2
x2 <- if (FALSE) 1 else 2

c(x1, x2)
#> [1] 1 2

当使用没有else的单参数for语句,且判断值为FALSE时,此时if语句对于程序不产生任何影响。但实际上此时for语句会返回一个NULL值,只不过像c()paste()等函数会忽略输入参数中的NULL值。

greet <- function(name, birthday = FALSE) {
  paste0(
    "Hi ", name,
    if (birthday) " and HAPPY BIRTHDAY"
  )
}
greet("Maria", FALSE)
#> [1] "Hi Maria"
greet("Jaime", TRUE)
#> [1] "Hi Jaime and HAPPY BIRTHDAY"

无效输入(Invalid input)

判断语句condition在被执行后应该是(或者本来就是)单个逻辑值:TRUEFALSE
。而如果是其他的值则可能会引发错误。

if ("x") 1
#> Error in if ("x") 1: argument is not interpretable as logical
if (logical()) 1
#> Error in if (logical()) 1: argument is of length zero
if (NA) 1
#> Error in if (NA) 1: missing value where TRUE/FALSE needed

对于判断值是数字的情况,如果数字非零,会被解读为TRUE值;相反如果判断值为零则会被解读为FALSE;另外如果判断值是数字类型的NaN,则会抛出错误:

x <- 1:10
if (length(x)) "not empty" else "empty"
#> [1] "not empty"

if (0.5) "not empty" else "empty"
#> [1] "not empty"

x <- numeric()
if (length(x)) "not empty" else "empty"
#> [1] "empty"

if (NaN) "not empty" else "empty"
#> Error in if (NaN) "not empty" else "empty": argument is not interpretable as logical

如果判断语句是长度大于1的逻辑向量,程序会抛出警示:

if (c(TRUE, FALSE)) 1
#> Warning in if (c(TRUE, FALSE)) 1: the condition has length > 1 and only the
#> first element will be used
#> [1] 1

在R 3.5.0之后的版本,可以通过设置全局变量使程序遇到上面这种情况时抛出错误。有时候这是非常有用的,因为这样可以显式地指出程序的错误之处。

Sys.setenv("_R_CHECK_LENGTH_1_CONDITION_" = "true")
if (c(TRUE, FALSE)) 1
#> Error in if (c(TRUE, FALSE)) 1: the condition has length > 1

向量化的if语句

由于if语句不支持逻辑值向量(长度大于1),所以处理逻辑值向量的情况应该使用向量化的if语句ifelse()

x <- 1:10
ifelse(x %% 5 == 0, "XXX", as.character(x))
#>  [1] "1"   "2"   "3"   "4"   "XXX" "6"   "7"   "8"   "9"   "XXX"

ifelse(x %% 2 == 0, "even", "odd")
#>  [1] "odd"  "even" "odd"  "even" "odd"  "even" "odd"  "even" "odd"  "even"

另外有一个向量化的且支持更多判断条件的判断函数dplyr::case_when(),其用法如下:

dplyr::case_when(
  x %% 35 == 0 ~ "fizz buzz",
  x %% 5 == 0 ~ "fizz",
  x %% 7 == 0 ~ "buzz",
  is.na(x) ~ "???",
  TRUE ~ as.character(x)
)
#>  [1] "1"    "2"    "3"    "4"    "fizz" "6"    "buzz" "8"    "9"    "fizz"

switch语句

switch()也是条件判断函数,支持多个判断条件,可以非常简单地替换复杂的if语句:

x_option <- function(x) {
  if (x == "a") {
    "option 1"
  } else if (x == "b") {
    "option 2" 
  } else if (x == "c") {
    "option 3"
  } else {
    stop("Invalid `x` value")
  }
}

##功能替换上面函数
x_option <- function(x) {
  switch(x,
    a = "option 1",
    b = "option 2",
    c = "option 3",
    stop("Invalid `x` value")
  )
}

上面的switch()语句中,如果没有匹配到正确的值,就会执行最后的stop()函数,抛出错误,如果没有stop()函数,却没有匹配到对应的值,则会隐式地返回NULL值:

(switch("c", a = 1, b = 2)) #使用()可以显示隐式输出的值
#> NULL

如果多个判断条件对应相同的输出或执行代码块,前几个判断条件对应的值或者代码块可以空缺,只需填写最后一个具有相同输出值的条件。因为如果某个判断条件对应的执行语句空缺,默认和下一个判断条件相同,这个规则是模仿C语言的switch语句。

legs <- function(x) {
  switch(x,
    cow = ,
    horse = ,
    dog = 4,  #最后一个具有相同输出值的条件
    human = ,
    chicken = 2,
    plant = 0,
    stop("Unknown input")
  )
}
legs("cow")
#> [1] 4
legs("dog")
#> [1] 4

循环结构

for语句能够遍历向量中的每个元素,具有以下的基本形式:

for (item in vector) perform_action

下面的代码中,for循环中的i变量属于全局变量,所以会覆盖已经定义的i值。

i <- 100
for (i in 1:3) {}
i
#> [1] 3

有两种方法终结for循环:

  • next可以退出当前这轮循环;

  • break可以退出整个for循环。

for (i in 1:10) {
  if (i < 3) 
    next

  print(i)

  if (i >= 5)
    break
}
#> [1] 3
#> [1] 4
#> [1] 5

明显的缺陷

使用for循环有3个明显的缺陷。首先,当需要生成数据,必须提前定义好容纳输出数据的容器,否则代码执行的速度会非常慢。

means <- c(1, 50, 20)
out <- vector("list", length(means))
for (i in 1:length(means)) {
  out[[i]] <- rnorm(10, means[[i]])
}

其次,当要迭代1:length(x)时,如果此时x的长度为零,则代码运行过程中可能会出现错误:

means <- c()
out <- vector("list", length(means))
for (i in 1:length(means)) {  #i取两个值1和0
  out[[i]] <- rnorm(10, means[[i]])  #因为means[[1]]索引离界,故报错
}
#> Error in rnorm(10, means[[i]]): invalid arguments

会出现上面的情况的原因是:同时支持产生递增和递减的系列值:

1:length(means)
#> [1] 1 0

如果使用seq_along(x),则会返回和x长度一样的序列值(向量),且返回的序列值就是x中元素的索引值:

seq_along(means)
#> integer(0)

out <- vector("list", length(means))
for (i in seq_along(means)) {
  out[[i]] <- rnorm(10, means[[i]])
}

最后,如果使用for遍历S3类型向量(具有class属性的向量),循环会去除向量元素的属性:

xs <- as.Date(c("2020-01-01", "2010-01-01"))
for (x in xs) {
  print(x)
}
#> [1] 18262  #去除了按格式显示时间的属性,显示的是从1970-01-01至今的天数
#> [1] 14610


for (i in seq_along(xs)) {
  print(xs[[i]])  #[[]]索引出的时间元素变成了单个字符串,所以可以按时间格式显示
}
#> [1] "2020-01-01"
#> [1] "2010-01-01"

whilerepeat

如果提前知道需要遍历的数据对象,那么使用for非常的方便。但是如果提前不知道,则可以使用另外两个相关的函数whilerepaet

  • while(condition) action:如果condition值为TRUE,则执行action

  • repeat(action):一直循环执行action,除非碰到break语句。

对于for循环编写的语句,可以改写用while实现,而对于while编写的语句,则可以改写用repeat实现,而反过来就不一定行。这就意味着whilefor灵活,repeatwhile灵活。但对于for循环能解决的问题可以尽量用for循环。对于很多分析任务,往往可以不使用for循环,因为mapapply等函数可以实现类似的功能,后面我们会详细讲解这些函数。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值