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

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

 million <- as.matrix(rnorm(100000)) mash  0) x[i] <- 1 else x[i] <- -1 return(x) } squish 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 отлично подходит для простого числового вектора. И он возвращает объект того же classа, что и тот, который ему передан.

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

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

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

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

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

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

    IF применил бы здесь решение (а это не так), вы могли бы сравнить:

     > 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 

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

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

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

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

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

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

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

     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 
    Давайте будем гением компьютера.