J'essaie de trouver comment définir une boucle for personnalisée dans R, ou si c'est même possible.
Quelques choses qui seraient agréable à avoir
each_with_index de ruby, ainsi que for qui ignore complètement les exceptions (sans avoir à coder manuellement la gestion des exceptions dans la boucle ). 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?
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
4 Réponses :
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)
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.
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()
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.
Très belle approche!
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
@ 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 + 35est moins pointilleux quei - 35La 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.