0%

R-for循环

R语言是以向量化编程而闻名的。因此R的for循环相比于向量化操作而言有时候会较慢。

R – for loop

1
2
3
for (variable in vector) {
action
}

有两种方法可以提前终止for循环

  • next退出当前迭代
  • break退出整个for循环
1
2
3
4
5
6
7
8
9
for (i in 1:10) {
if (i < 3)
next

print(i)

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

下面用以下的例子实测R语言中的循环的效率。

  • 使用 for 循环取一千万个数值的绝对值,并使用system.time函数测量函数运行的时间。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#生成一个包含正数和负数的长向量,总共有一千万个数值
long <- rep(c(1,-1), 5000000)

abs_loop <- function(x){
for(i in 1:length(x)){
if(x[i] < 0){
x[i] <- -x[i]
}
}
x
}


> system.time(abs_loop(long))
用户 系统 流逝
1.35 0.01 1.43
  • 再使用R中向量化的方式进行上述操作
1
2
3
4
5
6
7
8
9
10
abs_set <- function(x){
negs <- x < 0
x[negs] <- x[negs] * -1
x
}


> system.time(abs_set(long))
用户 系统 流逝
0.22 0.06 0.28

在使用了R中向量化编程的方式(逻辑判断、取子集、元素方式执行)后,速度提升了大约30倍

  • 最后,我们看看R中自带的取绝对值函数abs有多快
    1
    2
    3
    > system.time(abs(long))
    用户 系统 流逝
    0.02 0.02 0.03
    R中的自带函数几乎是for循环速度运行的300倍!这是由于R中很多basic的函数都是经过多次优化与调试的,所以在可能的情况下,应该尽量避免对其自带函数的修改。

for循环常见的使用错误

通过循环赋值时,要预先生成变量

如果我们需要通过for循环赋值,我们需要预先生成好容器变量,并设置变量相应属性,这将会提升for循环的效率

假设我们需要在1000个不同均值的正态分布中随机抽取1000个数,并存储到列表中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
means <- c(1:1000)

t1 <- Sys.time()
out <- list()
for (i in 1:length(means)) {
out[[i]] <- rnorm(1000, means[[i]])
}
t2 <- Sys.time()
t2 - t1
# Time difference of 0.0890801 secs

rm(out)
t1 <- Sys.time()
out <- vector("list", length(means))
for (i in 1:length(means)) {
out[[i]] <- rnorm(1000, means[[i]])
}
t2 <- Sys.time()
t2 - t1
# Time difference of 0.03803492 secs

可以发现使用vector()预先设置好容器的长度将会提升for循环赋值的速度约2.3倍

使用seq_along()迭代

上面我们使用1:length(mean)mean向量进行迭代,但这种方法在向量为空时将会报错。

1
2
3
4
5
6
means <- c()
out <- vector("list", length(means))
for (i in 1:length(means)) {
out[[i]] <- rnorm(10, means[[i]])
}
#> Error in rnorm(10, means[[i]]): invalid arguments

因此,尽可能使用seq_along()对向量进行迭代。seq_along()返回与输入向量等长的序列

1
2
> seq_along(11:20)
[1] 1 2 3 4 5 6 7 8 9 10

所以,如果向量为空也不用担心报错

1
2
3
4
5
6
7
seq_along(means)
#> integer(0)

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

S3向量的循环

有时候,对S3向量进行迭代时,我们也可能碰到以下问题

1
2
3
4
5
6
xs <- as.Date(c("2020-01-01", "2010-01-01"))
for (x in xs) {
print(x)
}
#> [1] 18262
#> [1] 14610

这是由于循环解除了变量的属性,这个时候使用双中括号[[可以解决

1
2
3
4
5
for (i in seq_along(xs)) {
print(xs[[i]])
}
#> [1] "2020-01-01"
#> [1] "2010-01-01"

总而言之,for循环会比较符合我们的直观思维,但在R中向量化编程的方式会提高我们处理任务的效率。当然,有时候向量化编程也会起到反作用,切记具体情况具体分析,而不是死磕一种方法。

Ref:

Advanced R: https://adv-r.hadley.nz/control-flow.html

完。