在 R 中定义一种新的 for 循环类型?



我正在尝试弄清楚如何在R中定义自定义for循环,或者如果可能的话。

例子

有几件事会很好

  • 相当于露比的each_with_index,以及
  • 完全忽略异常的for循环(无需手动将异常处理编码到循环中(。

是否有可能在 R 中定义一种新的 for 循环(如果是,如何定义(,或者这是语言的固有限制,因此不能做到?

使用案例

这是一个随机的例子,说明for_each_with_index如何简化挑剔的算术

假设我们想从网站上抓取第 36 篇到 55 篇文章,并将输出分配给列表中的某个位置。这效果很好

library(rvest)
library(dplyr)
articles <- vector(mode = "list", length = 20)
for(i in 36:55) {
paste0("Scraping article ", i) %>% print
articles[[i - 35]] <- read_html(paste0("http://afr.herokuapp.com/articles/", i)) %>% 
html_nodes("p") %>% html_text %>% paste0(collapse="/n")
}

但是我们看到一些挑剔的算术(36:55i - 35等(,理论上可以通过for_each_with_index枚举articles对象的每个元素来抽象出来,如下所示:

# NOT ACTUAL R CODE
library(rvest)
library(dplyr)
articles <- vector(mode = "list", length = 20)
for_each_with_index(articles, i) {
paste0("Scraping article ", i) %>% print
articles[[i]] <- read_html(paste0("http://afr.herokuapp.com/articles/", i + 35)) %>% 
html_nodes("p") %>% html_text %>% paste0(collapse="/n")
}

通过使用for_each_with_index,我们避免了繁琐的算术。这个例子非常简单,但是当复杂性出现一些缺口时,即当我们有各种条件、嵌套循环等时,事情会变得更加复杂,这些看似很小的清晰度改进变得更加深刻

foreach 包提供了一个模型

res = foreach(i = 1:3) %do% {
sqrt(i)
}

这是使用 R%any%构造,该构造是可由用户定义的中缀运算符,因此

`%with_index%` <- function(lhs, rhs) {
## implement ...
Map(function(i) {
list(i, rhs(lhs[[i]]))
}, seq_along(lhs))
}
1:10 %with_index% sqrt

它还定义了foreach()功能来设置右侧。%do%必须以这样一种方式编写,即实现适用于相对一般的rhs,这不是一项微不足道的任务。

实施for_each() %with_index% {}可能会非常有趣,并且非常有教育意义。

  1. 最好避免在 R 中使用循环,尤其是对于您的主 计算。R中的循环是通过lapply等函数实现的, 应用,应用,应用。这些是灵活的,可以通过以下方式定制 将您自己的函数传递给它们。
  2. 看看 try(( 函数,你可以用它来包装你的代码。如果将参数"静默"设置为 true,则将忽略错误。

感谢您发布示例。@HubertL的解决方案是正确的方法。在这种情况下,不需要索引。如果你真的想传递索引而不是实际的页码,这可以很容易地完成:

my_scraper <- function(article_id){ 
paste0("Scraping article ", article_id) %>% print
read_html(paste0("http://afr.herokuapp.com/articles/", article_id + 35)) %>%
html_nodes("p") %>% 
html_text %>% 
paste0(collapse="/n")}
articles <- lapply(1:20, my_scraper)

你可以用这个函数来做到这一点:

for_with_index <- function(var, index, seq, expr) {
env <- parent.frame() # This is where evaluation takes place
for (i in seq_along(seq)) {
assign(as.character(substitute(index)), i, envir = env)
assign(as.character(substitute(var)), seq[i], envir = env)
eval(substitute(expr), envir = env)
}
}
for_with_index(i, j, 7:9, cat("Entry ", j, " is ", i, "n"))
#> Entry  1  is  7 
#> Entry  2  is  8 
#> Entry  3  is  9

如果你想使用类似 for 的语法,那就有点难了,因为你不能修改解析器。 但是,解析后,for 循环只是函数调用,因此如果您能弄清楚将索引放在调用中的位置,您仍然可以这样做。 一种方法可能是这样写:

for (i in {7:9;j}) 
cat("Entry ", j, " is ", i, "n")

这是合法语法,但在标准循环中它不起作用,因为{7:9;j}的计算结果与j相同,这不是您想要的。 但是你可以编写自己的 for 循环函数来处理它:

`for` <- function(var, seq, expr) { 
env <- parent.frame()
seq <- substitute(seq)
if (is.call(seq) && seq[[1]] == "{" && length(seq) == 3) {
index2 <- seq[[3]]
seq <- eval(seq[[2]], env)
for (index in seq_along(seq)) {
assign(as.character(substitute(var)), seq[index], envir = env)
assign(as.character(index2), index, envir = env)
eval(substitute(expr), envir = env)
}
} else {
seq <- eval(seq, env)
oldfor <- substitute(for (var in seq) expr, 
list(var = substitute(var), 
seq = seq, 
expr = substitute(expr)))
oldfor[[1]] <- base::`for`
eval(oldfor, env)
}
}
for (i in 7:9) 
print(i)
#> [1] 7
#> [1] 8
#> [1] 9
for (i in {7:9; j}) 
cat("Entry ", j, " is ", i, "n")
#> Entry  1  is  7 
#> Entry  2  is  8 
#> Entry  3  is  9

扩展@Cole评论,正如@BigFinger在他们的回答中提到的,当你需要一个for循环时,你"总是"应该lapply思考:

library(rvest)
library(dplyr)
my_scraper <- function(article_id){ 
paste0("Scraping article ", article_id) %>% print
read_html(paste0("http://afr.herokuapp.com/articles/", article_id)) %>%
html_nodes("p") %>% 
html_text %>% 
paste0(collapse="/n")}
articles <- lapply(36:55, my_scraper)

lapply()会生成一个列表,因此您不必对其进行初始化。

lapply一开始并不容易使用,但非常方便。如果你喜欢tidyverse你也可以看看purr::map()

相关内容

  • 没有找到相关文章

最新更新