lapply vs for loop-Leistung R




performance (2)

Tatsächlich,

Ich habe den Unterschied mit einem Problem getestet, das kürzlich gelöst wurde.

Probiere es einfach selbst aus.

In meinem Fazit habe ich keinen Unterschied, aber die for-Schleife zu meinem Fall war unwesentlich schneller als lapply.

Ps: Ich versuche meistens, die gleiche Logik zu verwenden.

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

Wie Sie sehen konnten, habe ich eine einfache Routine ausgeführt, um eine named_list basierend auf einem Datenrahmen zu erstellen. Die func-Funktion führt die extrahierten Spaltenwerte aus, das f1 verwendet eine for-Schleife, um den Datenrahmen zu durchlaufen, und das f2 verwendet eine lapply-Funktion.

Auf meinem Computer erhalte ich folgende Ergebnisse:

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

Es wird oft gesagt, dass man es for Schleifen vorziehen sollte, lapply for . Es gibt einige Ausnahmen, wie zum Beispiel Hadley Wickham in seinem Advance R-Buch herausstellt.

( http://adv-r.had.co.nz/Functionals.html ) (Ändern an Ort und Stelle, Rekursion usw.). Das Folgende ist einer dieser Fälle.

Um zu lernen, habe ich versucht, einen Perzeptron-Algorithmus in einer funktionalen Form umzuschreiben, um die relative Leistung zu bewerten. Quelle ( https://rpubs.com/FaiHas/197581 ).

Hier ist der 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
}

Aufgrund der oben genannten Probleme habe ich keine konsequente Verbesserung erwartet. Trotzdem war ich sehr überrascht, als ich die scharfe Verschlechterung bei lapply und replicate .

Ich erhielt diese Ergebnisse unter Verwendung der microbenchmark Funktion aus der microbenchmark Bibliothek

Was könnten die Gründe sein? Könnte es ein Speicherleck sein?

                                                      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

Die erste Funktion ist die Funktion lapply / replicate

Die zweite ist die Funktion mit for Schleifen

Die dritte Funktion ist dieselbe in C++ mit Rcpp

Hier laut Roland die Profilierung der Funktion. Ich bin mir nicht sicher, ob ich es richtig interpretieren kann. Mir scheint, dass die meiste Zeit in der Erstellung von Untermengen für Funktionsprofile verbracht wird


Answer #1

Erstens ist es ein schon lange entlarvter Mythos, dass for Schleifen langsamer als lapply . Die for Schleifen in R wurden deutlich performanter gestaltet und sind derzeit mindestens so schnell wie lapply .

Das heißt, Sie müssen Ihre Verwendung von lapply hier überdenken. Ihre Implementierung erfordert die Zuordnung zur globalen Umgebung, da Sie für Ihren Code das Gewicht während der Schleife aktualisieren müssen. Und das ist ein triftiger Grund, nicht lapply .

lapply ist eine Funktion, die Sie wegen ihrer Nebenwirkungen (oder des Fehlens von Nebenwirkungen) verwenden sollten. Die Funktion fasst die Ergebnisse automatisch in einer Liste zusammen und wirkt sich im Gegensatz zu einer for Schleife nicht auf die Umgebung aus, in der Sie arbeiten. Das gleiche gilt für die replicate . Siehe auch diese Frage:

Ist R's Apply-Familie mehr als syntaktischer Zucker?

Der Grund, warum Ihre lapply viel langsamer ist, ist, dass Ihre Art der Verwendung viel mehr Aufwand verursacht.

  • replicate ist nichts anderes als intern sapply , also kombinieren Sie tatsächlich sapply und lapply , um Ihre Doppelschleife zu implementieren. sapply erzeugt zusätzlichen Overhead, da geprüft werden muss, ob das Ergebnis vereinfacht werden kann. Eine for Schleife ist also tatsächlich schneller als die Verwendung von replicate .
  • Innerhalb Ihrer lapply anonymen Funktion müssen Sie für jede Beobachtung sowohl für x als auch für y auf den Datenrahmen zugreifen. Dies bedeutet, dass im Gegensatz zu Ihrer for-Schleife zB die Funktion $ jedes Mal aufgerufen werden muss.
  • Da Sie diese High-End-Funktionen verwenden, ruft Ihre "lapply" -Lösung 49 Funktionen auf, im Vergleich zu Ihrer " for -Lösung, die nur 26 aufruft. %in% , sys.call , duplicated , ... Alle Funktionen, die von Ihrer for Schleife nicht benötigt werden, da diese keine dieser Prüfungen durchführt.

Wenn Sie sehen möchten, woher dieser zusätzliche Aufwand stammt, unlist sapply den internen Code für replicate , unlist , sapply und simplify2array sapply .

Sie können den folgenden Code verwenden, um eine bessere Vorstellung davon zu erhalten, wo Sie Ihre Leistung mit dem lapply verlieren. Führen Sie diese Zeile für Zeile!

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),]




lapply