63 votes

Est-ce que ifelse calcule vraiment ses deux vecteurs à chaque fois ? Est-ce lent ?

Est-ce que ifelse calcule vraiment à la fois les vecteurs oui et non - c'est-à-dire, l'intégralité de chaque vecteur ? Ou calcule-t-il simplement certaines valeurs de chaque vecteur ?

Aussi, est-ce que ifelse est vraiment si lent ?

74voto

Ricardo Saporta Points 22951

Oui. (Avec exception)

ifelse calcule à la fois sa valeur oui et sa valeur non. Sauf dans le cas où la condition test est soit entièrement TRUE soit entièrement FALSE.

Nous pouvons voir cela en générant des nombres aléatoires et en observant combien de nombres sont réellement générés (en inversant la seed).

# CONDITION TEST, TOUT VRAI
set.seed(1)
dump  <- ifelse(rep(TRUE, 200), rnorm(200), rnorm(200))
nombre.aléatoire.suivant.après.tout.vrai <- rnorm(1)

# CONDITION TEST, TOUT FAUX
set.seed(1)
dump  <- ifelse(rep(FALSE, 200), rnorm(200), rnorm(200))
nombre.aléatoire.suivant.après.tout.faux <- rnorm(1)

# CONDITION TEST, MÉLANGÉ
set.seed(1)
dump   <- ifelse(c(FALSE, rep(TRUE, 199)), rnorm(200), rnorm(200))
nombre.aléatoire.suivant.après.un.peu.VRAI.un.peu.FAUX <- rnorm(1)

# RÉINITIALISER LA SEED, GÉNÉRER PLUSIEURS NOMBRES ALÉATOIRES POUR RECHERCHER UNE CORRESPONDANCE
set.seed(1)
r.1000 <- rnorm(1000)

cat("Quantité de nombres aléatoires générés pendant l'instruction `ifelse` lorsque :",
    "\n\tTout Vrai   ", which(r.1000 == nombre.aléatoire.suivant.après.tout.vrai) - 1,
    "\n\tTout Faux  ", which(r.1000 == nombre.aléatoire.suivant.après.tout.faux) - 1,
    "\n\tMélangé V/F", which(r.1000 == nombre.aléatoire.suivant.après.un.peu.VRAI.un.peu.FAUX) - 1
  )

Donne la sortie suivante :

Quantité de nombres aléatoires générés pendant l'instruction `ifelse` lorsque : 
  Tout Vrai   200 
  Tout Faux  200 
  Mélangé V/F  400   <~~ Remarquez DEUX FOIS PLUS de nombres ont été générés lorsque la condition contenait à la fois des valeurs V et F

Nous pouvons aussi le voir dans le code source lui-même :

.
.
if (any(test[!nas]))    
    ans[test & !nas] <- rep(yes, length.out = length(ans))[test &   # <~~~~ Cette ligne et celle ci-dessous
        !nas]
if (any(!test[!nas])) 
    ans[!test & !nas] <- rep(no, length.out = length(ans))[!test &  # <~~~~ ... sont les coupables
        !nas]
.
.

Remarquez que les valeurs yes et no sont calculées uniquement s'il existe une valeur non-NA de test qui est TRUE ou FALSE (respectivement).
À ce stade - et c'est l'aspect important en termes d'efficacité - la totalité de chaque vecteur est calculée.


D'accord, mais est-ce que c'est plus lent ?

Voyons si nous pouvons le tester :

library(microbenchmark)

# Créez quelques données d'exemple
  N <- 1e4
  set.seed(1)
  X <- sample(c(seq(100), rep(NA, 100)), N, TRUE)
  Y <- ifelse(is.na(X), rnorm(X), NA)  # Y a une configuration NA/non-NA inversée par rapport à X

Ces deux instructions génèrent les mêmes résultats

ouiifelse <- quote(sort(ifelse(is.na(X), Y+17, X-17 ) ))
noniflese  <- quote(sort(c(Y[is.na(X)]+17, X[is.na(Y)]-17)))

identical(eval(ouiifelse), eval(noniflese))
# [1] TRUE

mais l'une est deux fois plus rapide que l'autre

microbenchmark(eval(ouiifelse), eval(noniflese), times=50L)

N = 1,000
Unité: millisecondes
            expr      min       lq   médiane       uq      max neval
 eval(ouiifelse) 2.286621 2.348590 2.411776 2.537604 10.05973    50
  eval(noniflese) 1.088669 1.093864 1.122075 1.149558 61.23110    50

N = 10,000
Unité: millisecondes
            expr      min       lq   médiane       uq      max neval
 eval(ouiifelse) 30.32039 36.19569 38.50461 40.84996 98.77294    50
  eval(noniflese) 12.70274 13.58295 14.38579 20.03587 21.68665    50

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X