Подтвердить что ты не робот

Почему метод apply() медленнее, чем цикл for в R?

В качестве передового опыта я пытаюсь определить, лучше ли создавать функцию и 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 хорош с простым числовым вектором. И он возвращает объект того же класса, что и тот, который ему передан.

4b9b3361

Ответ 1

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

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

В цикле 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(...)) для преобразования чего-либо в вектор и в конце пытается упростить ответ в подходящую форму. Кроме того, это требует времени, следовательно, также может быть медленнее. Тем не менее, это не на моей машине.

IF apply будет решением здесь (и это не так), вы можете сравнить:

> 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

Ответ 2

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

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

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

Ответ 3

Вы можете использовать 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

В этом случае гораздо быстрее выполнить замену на основе индекса, чем семейство 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

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

Ответ 5

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

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