Почему метод apply () медленнее, чем цикл for в R? - PullRequest
21 голосов
/ 04 апреля 2011

В качестве передового опыта я пытаюсь определить, лучше ли создать функцию и apply() ее по матрице, или лучше просто пройтись по матрице через функцию.Я попробовал это в обоих направлениях и с удивлением обнаружил, что apply() медленнее.Задача состоит в том, чтобы взять вектор и оценить его как положительный или отрицательный, а затем вернуть вектор с 1, если он положительный, и с -1, если он отрицательный.Функциональные циклы mash() и функция squish() передаются в функцию apply().

million  <- as.matrix(rnorm(100000))

mash <- function(x){
  for(i in 1:NROW(x))
    if(x[i] > 0) {
      x[i] <- 1
    } else {
      x[i] <- -1
    }
    return(x)
}

squish <- function(x){
  if(x >0) {
    return(1)
  } else {
    return(-1)
  }
}


ptm <- proc.time()
loop_million <- mash(million)
proc.time() - ptm


ptm <- proc.time()
apply_million <- apply(million,1, squish)
proc.time() - ptm

loop_million результаты:

user  system elapsed 
0.468   0.008   0.483 

apply_million результаты:

user  system elapsed 
1.401   0.021   1.423 

В чем преимущество использования apply() по сравнению с for в случае снижения производительности?Есть ли недостаток в моем тесте?Я сравнил два полученных объекта для подсказки и нашел:

> class(apply_million)
[1] "numeric"
> class(loop_million)
[1] "matrix"

, которая только углубляет тайну.Функция apply() не может принимать простой числовой вектор, и поэтому я приведу его с as.matrix() в начале.Но тогда он возвращает число.Цикл for подходит для простого числового вектора.И он возвращает объект того же класса, что и переданный ему.

Ответы [ 5 ]

39 голосов
/ 04 апреля 2011

Суть семейства функций apply (и plyr) - не скорость, а выразительность. Они также имеют тенденцию предотвращать ошибки, потому что они устраняют код бухгалтерского учета, необходимый для циклов.

В последнее время ответы на переполнение стека имеют слишком высокую скорость. Ваш код станет быстрее сам по себе, так как компьютеры будут работать быстрее, а R-core оптимизирует внутренности R. Ваш код никогда не станет более элегантным или более простым для понимания.

В этом случае вы можете получить лучшее из обоих миров: элегантный ответ с использованием векторизации, который также очень быстр, (million > 0) * 2 - 1.

12 голосов
/ 04 апреля 2011

Как сказал Чейз: используйте силу векторизации.Здесь вы сравниваете два плохих решения.

Чтобы выяснить, почему ваше решение для применения медленнее:

В цикле for вы фактически используете векторизованные индексы матрицы, что означает отсутствие преобразованиятипа происходит.Здесь я немного грубоват, но в основном внутренний расчет игнорирует размеры.Они просто сохраняются как атрибут и возвращаются с вектором, представляющим матрицу.Чтобы проиллюстрировать:

> x <- 1:10
> attr(x,"dim") <- c(5,2)
> y <- matrix(1:10,ncol=2)
> all.equal(x,y)
[1] TRUE

Теперь, когда вы используете команду apply, матрица разбивается внутри на 100 000 векторов строк, каждый вектор строк (т.е. одно число) пропускается через функцию, и в концерезультат объединяется в соответствующую форму.Функция apply считает вектор наилучшим в этом случае и, следовательно, должен объединить результаты всех строк.Это требует времени.

Также функция sapply сначала использует as.vector(unlist(...)), чтобы преобразовать что-либо в вектор, и в конце пытается упростить ответ в подходящую форму.Кроме того, это требует времени, следовательно, также саппли может быть медленнее.Тем не менее, это не на моей машине.

ЕСЛИ бы применить решение здесь (и это не так), вы можете сравнить:

> system.time(loop_million <- mash(million))
   user  system elapsed 
   0.75    0.00    0.75    
> system.time(sapply_million <- matrix(unlist(sapply(million,squish,simplify=F))))
   user  system elapsed 
   0.25    0.00    0.25 
> system.time(sapply2_million <- matrix(sapply(million,squish)))
   user  system elapsed 
   0.34    0.00    0.34 
> all.equal(loop_million,sapply_million)
[1] TRUE
> all.equal(loop_million,sapply2_million)
[1] TRUE
6 голосов
/ 04 апреля 2011

Вы можете использовать lapply или sapply для векторов, если хотите. Однако, почему бы не использовать соответствующий инструмент для работы, в этом случае ifelse()?

> ptm <- proc.time()
> ifelse_million <- ifelse(million > 0,1,-1)
> proc.time() - ptm
   user  system elapsed 
  0.077   0.007   0.093 

> all.equal(ifelse_million, loop_million)
[1] TRUE

И для сравнения, вот два сопоставимых прогона, использующих цикл for и sapply:

> ptm <- proc.time()
> apply_million <- sapply(million, squish)
> proc.time() - ptm
   user  system elapsed 
  0.469   0.004   0.474 
> ptm <- proc.time()
> loop_million <- mash(million)
> proc.time() - ptm
   user  system elapsed 
  0.408   0.001   0.417 
4 голосов
/ 04 апреля 2011

В этом случае гораздо быстрее выполнить замену на основе индекса, чем либо ifelse(), *apply() семейство, либо цикл:

> million  <- million2 <- as.matrix(rnorm(100000))
> system.time(million3 <- ifelse(million > 0, 1, -1))
   user  system elapsed 
  0.046   0.000   0.044 
> system.time({million2[(want <- million2 > 0)] <- 1; million2[!want] <- -1}) 
   user  system elapsed 
  0.006   0.000   0.007 
> all.equal(million2, million3)
[1] TRUE

. Все эти инструменты стоит тогона кончиках ваших пальцев.Вы можете использовать тот, который наиболее удобен для вас (поскольку вам нужно понимать код месяцами или годами позже), а затем начать переходить к более оптимизированным решениям, если время вычислений становится непомерно большим.

3 голосов
/ 04 апреля 2011

Лучший пример преимущества скорости для цикла.

for_loop <- function(x){
    out <- vector(mode="numeric",length=NROW(x))
    for(i in seq(length(out)))
        out[i] <- max(x[i,])
    return(out)
    }

apply_loop <- function(x){
    apply(x,1,max)
}

million  <- matrix(rnorm(1000000),ncol=10)
> system.time(apply_loop(million))
  user  system elapsed 
  0.57    0.00    0.56 
> system.time(for_loop(million))
  user  system elapsed 
  0.32    0.00    0.33 

РЕДАКТИРОВАТЬ

Версия, предложенная Эдуардо.

max_col <- function(x){
    x[cbind(seq(NROW(x)),max.col(x))]
}

По ряду

> system.time(for_loop(million))
   user  system elapsed 
   0.99    0.00    1.11 
> system.time(apply_loop(million))
  user  system elapsed 
   1.40    0.00    1.44 
> system.time(max_col(million))
  user  system elapsed 
  0.06    0.00    0.06 

По столбцу

> system.time(for_loop(t(million)))
  user  system elapsed 
  0.05    0.00    0.05 
> system.time(apply_loop(t(million)))
  user  system elapsed 
  0.07    0.00    0.07 
> system.time(max_col(t(million)))
  user  system elapsed 
  0.04    0.00    0.06 
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...