1
votes

Définir un nouveau type de boucle for dans R?

J'essaie de trouver comment définir une boucle for personnalisée dans R, ou si c'est même possible.

Exemples

Quelques choses qui seraient agréable à avoir

Est-il possible de définir un nouveau type de boucle for dans R (et si oui, comment), ou est-ce une limitation inhérente au langage et donc pas quelque chose qui peut être fait?

Cas d'utilisation

Voici un exemple aléatoire de la façon dont for_each_with_index pourrait simplifier l'arithmétique capricieuse

Supposons que nous voulions gratter le 36e au 55e article d'un site Web et attribuez la sortie à une position dans une liste. Cela fonctionne bien

# 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")
           }

Mais nous voyons des arithmétiques pointilleuses ( 36:55 , i - 35 etc.) qui pourraient théoriquement être abstraite via for_each_with_index énumérant sur chaque élément de l'objet articles , comme ceci:

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")
           }

En utilisant for_each_with_index , nous avons évité l'arithmétique fastidieuse. Cet exemple est très simple, mais lorsque la complexité augmente de quelques crans, c'est-à-dire lorsque nous avons diverses conditions, des boucles imbriquées, etc., les choses deviennent beaucoup plus complexes et ces améliorations apparemment mineures de clarté deviennent plus profondes

r

5 commentaires

@ chinsoon12 J'ai mis à jour la question avec un exemple. Désolé, c'est long. J'espère que ça a du sens


Voir lapply (36:55, function (i) {read_html (paste0 (...))})


Je ne vois pas en quoi i + 35 est moins pointilleux que i - 35


La différence essentielle n'est pas entre i + 35 et i - 35, mais entre l'utilisation d'une boucle for et non. Les boucles For sont très inefficaces dans R, impliquant de nombreuses copies inutiles. Ils fonctionnent très bien pour boucler sur un petit nombre de chaînes, mais si vous devez faire des calculs sur des centaines de milliers d'enregistrements, les boucles for tueront vos performances.


@BigFinger a tort: ​​les boucles for ne sont pas particulièrement inefficaces. Comme les autres constructions de contrôle (if, while, etc.), ce sont des appels de fonction en interne, juste avec des règles spéciales dans l'analyseur pour construire l'appel. Si vous voulez une sémantique différente de pour, vous pouvez définir votre propre fonction, mais vous ne pouvez pas changer la syntaxe du langage, donc cela devra être fait avec un opérateur infixe (comme la réponse de Martin Morgan) ou un appel de fonction normal.


4 Réponses :


1
votes
  1. Il est préférable d'éviter les boucles for dans R, en particulier pour votre calcul. Le bouclage en R est réalisé avec des fonctions comme lapply, sapply, mapply, tapply. Ceux-ci sont flexibles et peuvent être personnalisés par en leur passant vos propres fonctions.
  2. Jetez un œil à la fonction try (), que vous pouvez utiliser pour encapsuler votre code. Si vous définissez l'argument "silent" sur true, les erreurs seront ignorées.
  3. Merci d'avoir publié un exemple. La solution de @HubertL est la bonne approche. Un index n'est pas nécessaire dans ce cas. Si vous voulez vraiment transmettre l'index à lapply au lieu du numéro de page réel, cela peut être fait facilement:

    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)
    

2 commentaires

combien de temps faut-il pour effectuer un million d'itérations, system.time (for (i in 1: 1000000) {}) ? Qu'en est-il de lapply (1: 1000000, function (i) {}) ?


Oui, c'est une excellente question. Si vous le mettez comme ça, il ne devrait y avoir aucun avantage à utiliser lapply sur la boucle for. J'ai besoin de réviser ma réponse. Dans cet exemple, read_html opère sur un élément de la liste à la fois, il ne semble donc pas y avoir de moyen d'optimiser le code.



0
votes

Expansion sur le commentaire @Cole, et comme mentionné par @BigFinger dans sa réponse, vous devriez "toujours" penser lapply lorsque vous avez besoin d'une boucle for :

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 () construit une liste pour que vous n'ayez pas à l'initialiser.

lapply n'est pas facile à utiliser au début, mais c'est très pratique. Si vous aimez tidyverse , vous pouvez également jeter un œil à purr::map()


0 commentaires

3
votes

Le package foreach fournit un modèle

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

1:10 %with_index% sqrt

Ceci utilise la construction R % any% , qui est un opérateur d'infixe qui peut être défini par l'utilisateur, donc

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

Il a a également défini la fonction foreach () pour configurer le côté droit. % do% doit être écrit de telle manière que l'implémentation fonctionne pour des rhs relativement généraux, et ce n'est pas une tâche triviale.

Implémentation for_each ()% with_index% {} serait probablement assez intéressant et très éducatif.


1 commentaires

Très belle approche!



1
votes

Vous pouvez le faire avec cette fonction:

`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

Si vous voulez utiliser la syntaxe for-like, c'est un peu plus difficile, car vous ne pouvez pas modifier l'analyseur. Cependant, après l'analyse, les boucles for ne sont que des appels de fonction, vous pouvez donc toujours le faire si vous pouvez trouver où placer l'index dans l'appel. Une façon pourrait être de l'écrire comme ceci:

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

C'est une syntaxe légale, mais dans la boucle standard cela ne fonctionnerait pas, car {7: 9; j } évalue la même chose que j , ce qui n'est pas ce que vous voulez. Mais vous pouvez écrire votre propre fonction de boucle for pour la gérer:

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


0 commentaires