Семейство R's больше, чем синтаксический сахар?

144
задан Jeromy Anglim 3 August 2012 в 00:46
поделиться

2 ответа

Функции apply в R не предоставляют улучшенная производительность по сравнению с другими функциями цикла (например, для ). Единственным исключением является lapply , который может быть немного быстрее, потому что он выполняет больше работы в коде C, чем в R (см. этот вопрос для примера ).

Но в целом правило таково, что вы должны использовать функцию применения для ясности, а не для производительности .

Я бы добавил к этому, что функции apply не имеют побочных эффектов , что является важным отличием, когда дело доходит до функционального программирования с R. Это можно изменить, используя assign или << - , но это может быть очень опасно. Побочные эффекты также затрудняют понимание программы, поскольку состояние переменной зависит от истории.

Править:

Просто чтобы подчеркнуть это с помощью тривиального примера, который рекурсивно вычисляет последовательность Фибоначчи; это может быть выполнено несколько раз, чтобы получить точную оценку, но дело в том, что ни один из методов не имеет существенно различающейся производительности:

> fibo <- function(n) {
+   if ( n < 2 ) n
+   else fibo(n-1) + fibo(n-2)
+ }
> system.time(for(i in 0:26) fibo(i))
   user  system elapsed 
   7.48    0.00    7.52 
> system.time(sapply(0:26, fibo))
   user  system elapsed 
   7.50    0.00    7.54 
> system.time(lapply(0:26, fibo))
   user  system elapsed 
   7.48    0.04    7.54 
> library(plyr)
> system.time(ldply(0:26, fibo))
   user  system elapsed 
   7.52    0.00    7.58 

Редактировать 2:

Относительно использования параллельных пакетов для R (например, rpvm, rmpi, snow ), они обычно предоставляют функции семейства apply (даже пакет foreach по сути эквивалентен, несмотря на название).Вот простой пример функции sapply в snow :

library(snow)
cl <- makeSOCKcluster(c("localhost","localhost"))
parSapply(cl, 1:20, get("+"), 3)

В этом примере используется кластер сокетов, для которого не требуется устанавливать дополнительное программное обеспечение; в противном случае вам понадобится что-то вроде PVM или MPI (см. страницу кластеризации Тирни ). snow имеет следующие функции apply:

parLapply(cl, x, fun, ...)
parSapply(cl, X, FUN, ..., simplify = TRUE, USE.NAMES = TRUE)
parApply(cl, X, MARGIN, FUN, ...)
parRapply(cl, x, fun, ...)
parCapply(cl, x, fun, ...)

Имеет смысл, что функции apply должны использоваться для параллельного выполнения, поскольку они не имеют побочных эффектов . Когда вы изменяете значение переменной в цикле for , оно устанавливается глобально. С другой стороны, все функции apply можно безопасно использовать параллельно, поскольку изменения являются локальными для вызова функции (если вы не попытаетесь использовать assign или << - ], и в этом случае вы можете вызвать побочные эффекты). Излишне говорить, что очень важно быть осторожным с локальными и глобальными переменными, особенно при параллельном выполнении.

Изменить:

Вот тривиальный пример, демонстрирующий разницу между для и * применить в отношении побочных эффектов:

> df <- 1:10
> # *apply example
> lapply(2:3, function(i) df <- df * i)
> df
 [1]  1  2  3  4  5  6  7  8  9 10
> # for loop example
> for(i in 2:3) df <- df * i
> df
 [1]  6 12 18 24 30 36 42 48 54 60

Обратите внимание, как df в родительской среде изменяется на для , но не на * apply .

150
ответ дан 23 November 2019 в 22:42
поделиться

Иногда ускорение может быть значительным, например, когда вам нужно вложить циклы for, чтобы получить среднее значение на основе группировки более чем одного фактора. Здесь у вас есть два подхода, которые дают вам один и тот же результат:

set.seed(1)  #for reproducability of the results

# The data
X <- rnorm(100000)
Y <- as.factor(sample(letters[1:5],100000,replace=T))
Z <- as.factor(sample(letters[1:10],100000,replace=T))

# the function forloop that averages X over every combination of Y and Z
forloop <- function(x,y,z){
# These ones are for optimization, so the functions 
#levels() and length() don't have to be called more than once.
  ylev <- levels(y)
  zlev <- levels(z)
  n <- length(ylev)
  p <- length(zlev)

  out <- matrix(NA,ncol=p,nrow=n)
  for(i in 1:n){
      for(j in 1:p){
          out[i,j] <- (mean(x[y==ylev[i] & z==zlev[j]]))
      }
  }
  rownames(out) <- ylev
  colnames(out) <- zlev
  return(out)
}

# Used on the generated data
forloop(X,Y,Z)

# The same using tapply
tapply(X,list(Y,Z),mean)

Оба дают один и тот же результат, представляя собой матрицу 5 x 10 со средними значениями и именованными строками и столбцами. Но:

> system.time(forloop(X,Y,Z))
   user  system elapsed 
   0.94    0.02    0.95 

> system.time(tapply(X,list(Y,Z),mean))
   user  system elapsed 
   0.06    0.00    0.06 

Ну вот. Что я выиграл? ;-)

70
ответ дан 23 November 2019 в 22:42
поделиться
Другие вопросы по тегам:

Похожие вопросы: