我正在尝试弄清楚如何在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:55
、i - 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% {}
可能会非常有趣,并且非常有教育意义。
- 最好避免在 R 中使用循环,尤其是对于您的主 计算。R中的循环是通过lapply等函数实现的, 应用,应用,应用。这些是灵活的,可以通过以下方式定制 将您自己的函数传递给它们。
- 看看 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()