【问题标题】:Define a new type of for loop in R?在 R 中定义一种新的 for 循环?
【发布时间】:2020-04-15 04:27:36
【问题描述】:

我正在尝试研究如何在 R 中定义自定义 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,我们避免了繁琐的算术。这个例子非常简单,但是当复杂度上升一些时,即当我们有各种条件、嵌套循环等时,事情会变得更加复杂,这些看似微小的清晰度改进会变得更加深刻

【问题讨论】:

  • @chinsoon12 我已经用一个例子更新了这个问题。对不起,它很长。我希望这是有道理的
  • lapply(36:55, function (i) {read_html(paste0(...))})
  • 我看不出i + 35i - 35 更不挑剔
  • 本质区别不是 i + 35 和 i - 35 之间,而是使用 for 循环与不使用之间。 R 中的 for 循环效率非常低,涉及大量不必要的复制。它们在循环少量字符串时工作得很好,但是如果您必须对数十万条记录进行计算,for 循环会降低您的性能。
  • @BigFinger 是错误的:for 循环并不是特别低效。与其他控制构造(if、while 等)一样,它们是内部函数调用,只是在解析器中使用特殊规则来构造调用。如果您想要与 for 不同的语义,您可以定义自己的函数,但不能更改语言的语法,因此需要使用中缀运算符(如 Martin Morgan 的答案)或常规函数调用来完成。

标签: r


【解决方案1】:

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% {} 可能会非常有趣,而且很有教育意义。

【讨论】:

  • 非常好的方法!
【解决方案2】:
  1. 最好避免在 R 中使用 for 循环,尤其是对于您的 main 计算。 R 中的循环是通过 lapply 等函数实现的, sapply,mapply,tapply。这些是灵活的,可以通过以下方式定制 将您自己的函数传递给它们。
  2. 查看可用于包装代码的 try() 函数。如果将参数“silent”设置为 true,则会忽略错误。

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

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)

【讨论】:

  • 执行一百万次迭代需要多长时间,system.time( for (i in 1:1000000) {} )lapply(1:1000000, function(i) {}) 呢?
  • 是的,这是一个很好的问题。如果你这样说,在 for 循环上使用 lapply 应该没有任何好处。我需要修改我的答案。在此示例中,read_html 一次对列表的一个元素进行操作,因此似乎没有办法优化代码。
【解决方案3】:

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

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

【讨论】:

    【解决方案4】:

    扩展@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()

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2014-01-15
      • 1970-01-01
      • 2020-06-29
      • 2014-08-15
      • 1970-01-01
      • 2020-08-29
      • 1970-01-01
      相关资源
      最近更新 更多