lapply vs pour boucle-Performance R
on dit souvent qu'il faut préférer lapply
aux for
boucles.
Il y a quelques exceptions comme par exemple Hadley Wickham souligne dans son Advance R book.
( http://adv-r.had.co.nz/Functionals.html ) (modification en place, récursion, etc.). Le suivant est un de ces cas.
juste pour apprendre, j'ai essayé de réécrire un algorithme perceptron sous une forme fonctionnelle afin de comparer performance relative. source ( https://rpubs.com/FaiHas/197581 ).
voici le code.
# prepare input
data(iris)
irissubdf <- iris[1:100, c(1, 3, 5)]
names(irissubdf) <- c("sepal", "petal", "species")
head(irissubdf)
irissubdf$y <- 1
irissubdf[irissubdf[, 3] == "setosa", 4] <- -1
x <- irissubdf[, c(1, 2)]
y <- irissubdf[, 4]
# perceptron function with for
perceptron <- function(x, y, eta, niter) {
# initialize weight vector
weight <- rep(0, dim(x)[2] + 1)
errors <- rep(0, niter)
# loop over number of epochs niter
for (jj in 1:niter) {
# loop through training data set
for (ii in 1:length(y)) {
# Predict binary label using Heaviside activation
# function
z <- sum(weight[2:length(weight)] * as.numeric(x[ii,
])) + weight[1]
if (z < 0) {
ypred <- -1
} else {
ypred <- 1
}
# Change weight - the formula doesn't do anything
# if the predicted value is correct
weightdiff <- eta * (y[ii] - ypred) * c(1,
as.numeric(x[ii, ]))
weight <- weight + weightdiff
# Update error function
if ((y[ii] - ypred) != 0) {
errors[jj] <- errors[jj] + 1
}
}
}
# weight to decide between the two species
return(errors)
}
err <- perceptron(x, y, 1, 10)
### my rewriting in functional form auxiliary
### function
faux <- function(x, weight, y, eta) {
err <- 0
z <- sum(weight[2:length(weight)] * as.numeric(x)) +
weight[1]
if (z < 0) {
ypred <- -1
} else {
ypred <- 1
}
# Change weight - the formula doesn't do anything
# if the predicted value is correct
weightdiff <- eta * (y - ypred) * c(1, as.numeric(x))
weight <<- weight + weightdiff
# Update error function
if ((y - ypred) != 0) {
err <- 1
}
err
}
weight <- rep(0, 3)
weightdiff <- rep(0, 3)
f <- function() {
t <- replicate(10, sum(unlist(lapply(seq_along(irissubdf$y),
function(i) {
faux(irissubdf[i, 1:2], weight, irissubdf$y[i],
1)
}))))
weight <<- rep(0, 3)
t
}
Je ne m'attendais pas à une amélioration constante en raison de ce qui précède
question. Mais néanmoins, j'ai été vraiment surpris quand j'ai vu la forte détérioration
en utilisant lapply
et replicate
.
j'ai obtenu ce résultat en utilisant la fonction microbenchmark
de microbenchmark
bibliothèque
Quelles pourraient être les raisons? Pourrait-il être certains de fuite de mémoire?
expr min lq mean median uq
f() 48670.878 50600.7200 52767.6871 51746.2530 53541.2440
perceptron(as.matrix(irissubdf[1:2]), irissubdf$y, 1, 10) 4184.131 4437.2990 4686.7506 4532.6655 4751.4795
perceptronC(as.matrix(irissubdf[1:2]), irissubdf$y, 1, 10) 95.793 104.2045 123.7735 116.6065 140.5545
max neval
109715.673 100
6513.684 100
264.858 100
La première fonction est la lapply
/ replicate
fonction
la seconde est la fonction avec for
boucles
la troisième est la même fonction dans C++
en utilisant Rcpp
ici selon Roland le profilage de la fonction. Je ne suis pas sûr que je peux l'interpréter dans le droit chemin. Il ressemble pour moi la plupart du temps est passé en. profilage de fonction
2 réponses
tout d'Abord, c'est une déjà longue démystifié le mythe que for
boucles sont plus lentement que lapply
. Les boucles for
en R ont été rendues beaucoup plus performantes et sont actuellement au moins aussi rapides que lapply
.
cela dit, vous devez repenser votre utilisation de lapply
ici. Votre mise en œuvre exige l'affectation à l'environnement global, parce que votre code exige que vous mettiez à jour le poids pendant la boucle. Et c'est une raison valable ne pas considérer lapply
.
lapply
est une fonction que vous devez utiliser pour ses effets secondaires (ou l'absence d'effets secondaires). La fonction lapply
combine les résultats dans une liste automatiquement et ne perturbe pas l'environnement dans lequel vous travaillez, contrairement à une boucle for
. Il en va de même pour replicate
. Voir aussi cette question:
est-ce que les R's appliquent la famille plus que le sucre syntaxique?
la raison pour laquelle votre solution lapply
est beaucoup plus lente, est que votre façon de l'utiliser crée beaucoup plus de frais généraux.
-
replicate
n'est rien d'autre quesapply
en interne, donc vous combinez en faitsapply
etlapply
pour implémenter votre double boucle.sapply
crée des frais généraux supplémentaires parce qu'il doit tester si oui ou non le résultat peut être simplifié. Donc une bouclefor
sera en fait plus rapide qu'en utilisantreplicate
. - dans votre fonction
lapply
anonyme, vous devez accéder à la base de données pour x et y pour chaque observation. Cela signifie que -contrairement à dans votre boucle for - par exemple la fonction$
doit être appelée à chaque fois. - parce que vous utilisez ces fonctions haut de gamme, votre solution" lapply "appelle 49 fonctions, par rapport à votre solution
for
qui n'appelle que 26. Ces fonctions supplémentaires pour la solutionlapply
inclure des appels à des fonctions commematch
,structure
,[[
,names
,%in%
,sys.call
,duplicated
, ... Toutes les fonctions qui ne sont pas nécessaires à votre bouclefor
car celle-ci ne fait aucune de ces vérifications.
si vous voulez voir d'où vient ce surplus, regardez le code interne de replicate
, unlist
, sapply
et simplify2array
.
vous pouvez utiliser ce qui suit code pour avoir une meilleure idée de l'endroit où vous perdez votre performance avec le lapply
. Exécutez cette ligne par ligne!
Rprof(interval = 0.0001)
f()
Rprof(NULL)
fprof <- summaryRprof()$by.self
Rprof(interval = 0.0001)
perceptron(as.matrix(irissubdf[1:2]), irissubdf$y, 1, 10)
Rprof(NULL)
perprof <- summaryRprof()$by.self
fprof$Fun <- rownames(fprof)
perprof$Fun <- rownames(perprof)
Selftime <- merge(fprof, perprof,
all = TRUE,
by = 'Fun',
suffixes = c(".lapply",".for"))
sum(!is.na(Selftime$self.time.lapply))
sum(!is.na(Selftime$self.time.for))
Selftime[order(Selftime$self.time.lapply, decreasing = TRUE),
c("Fun","self.time.lapply","self.time.for")]
Selftime[is.na(Selftime$self.time.for),]
en fait,
j'ai testé la différence avec un problème que A résoudre récemment.
essayez vous-même.
dans ma conclusion, n'ont aucune différence, mais pour boucle à mon cas ont été insignificamment plus rapide que lapply.
Ps: j'essaie surtout de garder la même logique en usage.
ds <- data.frame(matrix(rnorm(1000000), ncol = 8))
n <- c('a','b','c','d','e','f','g','h')
func <- function(ds, target_col, query_col, value){
return (unique(as.vector(ds[ds[query_col] == value, target_col])))
}
f1 <- function(x, y){
named_list <- list()
for (i in y){
named_list[[i]] <- func(x, 'a', 'b', i)
}
return (named_list)
}
f2 <- function(x, y){
list2 <- lapply(setNames(nm = y), func, ds = x, target_col = "a", query_col = "b")
return(list2)
}
benchmark(f1(ds2, n ))
benchmark(f2(ds2, n ))
comme vous pouvez le voir, j'ai fait une routine simple pour construire un named_list basé sur une base de données, la fonction func fait les valeurs de colonne extraites, la f1 utilise une boucle for pour itérer à travers la dataframe et la f2 utilise une fonction lapply.
dans mon ordinateur je reçois ce résultat:
test replications elapsed relative user.self sys.self user.child
1 f1(ds2, n) 100 110.24 1 110.112 0 0
sys.child
1 0
& & &
test replications elapsed relative user.self sys.self user.child
1 f1(ds2, n) 100 110.24 1 110.112 0 0
sys.child
1 0