Le plus gros problème et la racine de l'inefficacité est l'indexation de data.frame, je veux dire toutes ces lignes où vous utilisez temp[,]
.
Essayez d'éviter cela autant que possible. J'ai pris ta fonction, change d'indexation et ici version_A
dayloop2_A <- function(temp){
res <- numeric(nrow(temp))
for (i in 1:nrow(temp)){
res[i] <- i
if (i > 1) {
if ((temp[i,6] == temp[i-1,6]) & (temp[i,3] == temp[i-1,3])) {
res[i] <- temp[i,9] + res[i-1]
} else {
res[i] <- temp[i,9]
}
} else {
res[i] <- temp[i,9]
}
}
temp$`Kumm.` <- res
return(temp)
}
Comme vous pouvez le voir, je crée des vecteurs res
qui rassemblent les résultats. À la fin, je l'ajoute data.frame
et je n'ai pas besoin de jouer avec les noms. Alors, comment est-ce mieux?
Je lance chaque fonction pour data.frame
avec nrow
de 1.000 à 10.000 par 1000 et la mesure du temps avecsystem.time
X <- as.data.frame(matrix(sample(1:10, n*9, TRUE), n, 9))
system.time(dayloop2(X))
Le résultat est
Vous pouvez voir que votre version dépend de façon exponentielle de nrow(X)
. La version modifiée a une relation linéaire, et le lm
modèle simple prévoit que pour 850 000 lignes, le calcul prend 6 minutes et 10 secondes.
Puissance de vectorisation
Comme Shane et Calimo le déclarent dans leurs réponses, la vectorisation est la clé d'une meilleure performance. À partir de votre code, vous pouvez sortir de la boucle:
- conditionnement
- initialisation des résultats (qui sont
temp[i,9]
)
Cela conduit à ce code
dayloop2_B <- function(temp){
cond <- c(FALSE, (temp[-nrow(temp),6] == temp[-1,6]) & (temp[-nrow(temp),3] == temp[-1,3]))
res <- temp[,9]
for (i in 1:nrow(temp)) {
if (cond[i]) res[i] <- temp[i,9] + res[i-1]
}
temp$`Kumm.` <- res
return(temp)
}
Comparez le résultat pour ces fonctions, cette fois nrow
de 10 000 à 100 000 par 10 000.
Accorder l'écoute
Un autre ajustement consiste à changer dans une boucle l'indexation temp[i,9]
vers res[i]
(qui sont exactement les mêmes dans l'itération de boucle i-ème). C'est encore une différence entre l'indexation d'un vecteur et l'indexation d'un data.frame
.
Deuxième chose: lorsque vous regardez la boucle, vous pouvez voir qu'il n'est pas nécessaire de boucler sur tout i
, mais seulement pour celles qui correspondent à la condition.
Alors on y va
dayloop2_D <- function(temp){
cond <- c(FALSE, (temp[-nrow(temp),6] == temp[-1,6]) & (temp[-nrow(temp),3] == temp[-1,3]))
res <- temp[,9]
for (i in (1:nrow(temp))[cond]) {
res[i] <- res[i] + res[i-1]
}
temp$`Kumm.` <- res
return(temp)
}
Les performances que vous gagnez dépendent fortement d'une structure de données. Précisément - sur le pourcentage des TRUE
valeurs de la condition. Pour mes données simulées, il faut du temps de calcul pour 850 000 lignes en dessous d'une seconde.
Je veux que tu puisses aller plus loin, je vois au moins deux choses qui peuvent être faites:
- écrire un
C
code pour faire du sperme conditionnel
si vous savez que dans votre séquence de données max n'est pas grande, vous pouvez changer la boucle en while vectorisé, quelque chose comme
while (any(cond)) {
indx <- c(FALSE, cond[-1] & !cond[-n])
res[indx] <- res[indx] + res[which(indx)-1]
cond[indx] <- FALSE
}
Le code utilisé pour les simulations et les figures est disponible sur GitHub .
res = c(1,2,3,4)
etcond
est toutTRUE
, alors résultat final devrait être:1
,3
(cause1+2
),6
(deuxième cause est maintenant3
, et le troisième est3
aussi),10
(6+4
). Faire simple somme que vous avez1
,3
,5
,7
.Stratégies générales pour accélérer le code R
Tout d'abord, déterminez où se trouve réellement la partie lente. Il n'est pas nécessaire d'optimiser le code qui ne s'exécute pas lentement. Pour de petites quantités de code, le simple fait d'y réfléchir peut fonctionner. Si cela échoue, RProf et des outils de profilage similaires peuvent être utiles.
Une fois que vous avez déterminé le goulot d'étranglement, pensez à des algorithmes plus efficaces pour faire ce que vous voulez. Les calculs ne doivent être exécutés qu'une seule fois si possible, donc:
L' utilisation de fonctions plus efficaces peut produire des gains de vitesse modérés ou importants. Par exemple,
paste0
produit un petit gain d'efficacité mais.colSums()
et ses parents produisent des gains un peu plus prononcés.mean
est particulièrement lent .Ensuite, vous pouvez éviter certains problèmes particulièrement courants :
cbind
vous ralentira très rapidement.Essayez une meilleure vectorisation , ce qui peut souvent mais pas toujours aider. À cet égard, des commandes intrinsèquement vectorisées comme
ifelse
,diff
et autres apporteront plus d'améliorations que laapply
famille de commandes (qui ne fournissent que peu ou pas d'augmentation de vitesse sur une boucle bien écrite).Vous pouvez également essayer de fournir plus d' informations aux fonctions de R . Par exemple, utilisez
vapply
plutôt quesapply
et spécifiezcolClasses
lors de la lecture de données textuelles . Les gains de vitesse seront variables en fonction de la quantité de devinettes que vous éliminez.Ensuite, considérez les packages optimisés : le
data.table
package peut produire des gains de vitesse massifs là où son utilisation est possible, dans la manipulation de données et dans la lecture de grandes quantités de données (fread
).Ensuite, essayez de gagner de la vitesse grâce à des moyens plus efficaces d'appeler R :
Ra
etjit
en concert pour la compilation juste à temps (Dirk a un exemple dans cette présentation ).Et enfin, si tout ce qui précède ne vous permet toujours pas de vous rendre aussi rapidement que nécessaire, vous devrez peut-être passer à un langage plus rapide pour l'extrait de code lent . La combinaison de
Rcpp
etinline
ici permet de ne remplacer que la partie la plus lente de l'algorithme par du code C ++ particulièrement facile. Ici, par exemple, est ma première tentative de le faire , et cela épate même les solutions R hautement optimisées.Si vous avez encore des problèmes après tout cela, vous avez juste besoin de plus de puissance de calcul. Examinez la parallélisation ( http://cran.r-project.org/web/views/HighPerformanceComputing.html ) ou même les solutions basées sur GPU (
gpu-tools
).Liens vers d'autres conseils
la source
Si vous utilisez des
for
boucles, vous codez probablement R comme s'il s'agissait de C ou Java ou autre chose. Un code R correctement vectorisé est extrêmement rapide.Prenons par exemple ces deux simples bits de code pour générer une liste de 10000 entiers en séquence:
Le premier exemple de code est de savoir comment coder une boucle en utilisant un paradigme de codage traditionnel. Cela prend 28 secondes pour terminer
Vous pouvez obtenir une amélioration presque 100 fois par la simple action de pré-allocation de mémoire:
Mais en utilisant l'opération de vecteur de base R utilisant l'opérateur deux-points,
:
cette opération est pratiquement instantanée:la source
a[i]
ne change pas. Maissystem.time({a <- NULL; for(i in 1:1e5){a[i] <- 2*i} }); system.time({a <- 1:1e5; for(i in 1:1e5){a[i] <- 2*i} }); system.time({a <- NULL; a <- 2*(1:1e5)})
a un résultat similaire.rep(1, 1e5)
- les horaires sont identiques.Cela pourrait être rendu beaucoup plus rapide en sautant les boucles en utilisant des index ou des
ifelse()
instructions imbriquées .la source
i
valeur -th dépend dei-1
-th donc elle ne peut pas être définie comme vous le faites (en utilisantwhich()-1
).Je n'aime pas la réécriture du code ... Bien sûr, ifelse et lapply sont de meilleures options, mais parfois il est difficile de faire cela.
J'utilise fréquemment data.frames comme on utiliserait des listes telles que
df$var[i]
Voici un exemple inventé:
Version data.frame:
version de la liste:
17 fois plus rapide d'utiliser une liste de vecteurs qu'un data.frame.
Des commentaires sur les raisons pour lesquelles les data.frames internes sont si lents à cet égard? On pourrait penser qu'ils fonctionnent comme des listes ...
Pour un code encore plus rapide, faites ceci
class(d)='list'
au lieu ded=as.list(d)
etclass(d)='data.frame'
la source
[<-.data.frame
, qui est en quelque sorte appelée lorsque vous le faitesd$foo[i] = mark
et qui peut finir par faire une nouvelle copie du vecteur de éventuellement tout le data.frame à chaque<-
modification. Cela ferait une question intéressante sur SO.df$var[i]
notation passe- t -elle par la même[<-.data.frame
fonction? J'ai remarqué que c'était assez long en effet. Sinon, quelle fonction utilise-t-il?d$foo[i]=mark
est grossièrement traduit end <- `$<-`(d, 'foo', `[<-`(d$foo, i, mark))
, mais avec une certaine utilisation de variables temporaires.Comme Ari l'a mentionné à la fin de sa réponse, les packages
Rcpp
etinline
facilitent incroyablement la rapidité des choses. À titre d'exemple, essayez ceinline
code (avertissement: non testé):Il existe une procédure similaire pour
#include
les choses, où vous passez simplement un paramètreà la fonction cxx, comme
include=inc
. Ce qui est vraiment cool à ce sujet, c'est qu'il fait tout le lien et la compilation pour vous, donc le prototypage est vraiment rapide.Avertissement: je ne suis pas totalement sûr que la classe de tmp doive être numérique et non numérique ou autre. Mais j'en suis surtout sûr.
Edit: si vous avez encore besoin de plus de vitesse après cela, OpenMP est une fonction de parallélisation idéale
C++
. Je n'ai pas essayé de l'utiliser à partir deinline
, mais cela devrait fonctionner. L'idée serait, dans le cas desn
cœurs, de faire effectuer une itération de bouclek
park % n
. Une introduction convenable en Matloff est l'art de la programmation R , disponible ici , au chapitre 16, Recourir à C .la source
Les réponses ici sont excellentes. Un aspect mineur non couvert est que la question déclare " Mon PC fonctionne toujours (environ 10h maintenant) et je n'ai aucune idée de l'exécution ". Je mets toujours le code suivant dans des boucles lors du développement pour avoir une idée de la façon dont les changements semblent affecter la vitesse et aussi pour surveiller le temps qu'il faudra pour terminer.
Fonctionne également avec lapply.
Si la fonction dans la boucle est assez rapide mais que le nombre de boucles est important, envisagez d'imprimer de temps en temps, car l'impression sur la console elle-même a une surcharge. par exemple
la source
cat(sprintf("\nNow running... %40s, %s/%s \n", nm[i], i, n))
puisque je suis généralement en boucle sur des choses nommées (avec des nomsnm
).Dans R, vous pouvez souvent accélérer le traitement en boucle en utilisant les
apply
fonctions de famille (dans votre cas, ce serait probablement le casreplicate
). Jetez un œil auplyr
package qui fournit des barres de progression.Une autre option consiste à éviter complètement les boucles et à les remplacer par de l'arithmétique vectorisée. Je ne sais pas exactement ce que vous faites, mais vous pouvez probablement appliquer votre fonction à toutes les lignes à la fois:
Ce sera beaucoup plus rapide et vous pourrez ensuite filtrer les lignes avec votre condition:
L'arithmétique vectorisée nécessite plus de temps et de réflexion sur le problème, mais vous pouvez parfois économiser plusieurs ordres de grandeur en temps d'exécution.
la source
Le traitement avec
data.table
est une option viable:Si vous ignorez les gains possibles du filtrage des conditions, c'est très rapide. Évidemment, si vous pouvez faire le calcul sur le sous-ensemble de données, cela aide.
la source