apply () работает медленно - как сделать это быстрее или какие у меня есть альтернативы?

У меня довольно большой фрейм данных, около 10 миллионов строк. У него есть столбцы x и y, и я хочу вычислить

hypot <- function(x) {sqrt(x[1]^2 + x[2]^2)}

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

Но для меня это слишком много, поэтому я пробовал разные вещи:

  • компиляция функции hypot сокращает время примерно на 10%
  • использование функций из plyr значительно увеличивает время работы.

Какой самый быстрый способ сделать это?


person aplavin    schedule 20.12.2012    source источник


Ответы (3)


А как насчет with(my_data,sqrt(x^2+y^2)) ?

set.seed(101)
d <- data.frame(x=runif(1e5),y=runif(1e5))

library(rbenchmark)

Две разные построчные функции, одна из которых использует преимущества векторизации:

hypot <- function(x) sqrt(x[1]^2+x[2]^2)
hypot2 <- function(x) sqrt(sum(x^2))

Попробуйте скомпилировать и это:

library(compiler)
chypot <- cmpfun(hypot)
chypot2 <- cmpfun(hypot2)

benchmark(sqrt(d[,1]^2+d[,2]^2),
          with(d,sqrt(x^2+y^2)),
          apply(d,1,hypot),
          apply(d,1,hypot2),
          apply(d,1,chypot),
          apply(d,1,chypot2),
          replications=50)

Полученные результаты:

                       test replications elapsed relative user.self sys.self
5       apply(d, 1, chypot)           50  61.147  244.588    60.480    0.172
6      apply(d, 1, chypot2)           50  33.971  135.884    33.658    0.172
3        apply(d, 1, hypot)           50  63.920  255.680    63.308    0.364
4       apply(d, 1, hypot2)           50  36.657  146.628    36.218    0.260
1 sqrt(d[, 1]^2 + d[, 2]^2)           50   0.265    1.060     0.124    0.144
2  with(d, sqrt(x^2 + y^2))           50   0.250    1.000     0.100    0.144

Как и ожидалось, решение with() и решение для индексации столбцов а-ля Тайлер Ринкер практически идентичны; hypot2 в два раза быстрее исходного hypot (но все же примерно в 150 раз медленнее, чем векторизованные решения). Как уже указывал ОП, компиляция не очень помогает.

person Ben Bolker    schedule 20.12.2012
comment
Спасибо, сработало мгновенно! Я новичок в R, до сих пор не могу привыкнуть, что все операторы векторизованы. - person aplavin; 20.12.2012
comment
Векторы - это прекрасно :) - person Ricardo Saporta; 20.12.2012
comment
+1 за бенчмаркинг! интересно, что [ медленнее, чем with. $ даст вам еще более быстрые результаты! - person Ricardo Saporta; 20.12.2012
comment
@RicardoSaporta, я думаю, это просто шум - разница во времени составляет около 0,007 секунды ... - person Ben Bolker; 20.12.2012
comment
@БенБолкер. Мне было любопытно, поэтому я выполнил это 100x 250 повторений: with и $ были быстрее примерно на 45% времени, [ только около 10%. - person Ricardo Saporta; 20.12.2012
comment
Если m <- as.matrix(d), то sqrt((m * m) %*% c(1, 1)) конкурентоспособен (вероятно, на ~1% быстрее, что ~ничего не значит). - person Josh O'Brien; 21.12.2012
comment
Ни одно из решений для бенчмаркинга не присваивает результат! Мы пытаемся добавить столбец в d или создать новый вектор в родительской среде? - person mnel; 21.12.2012
comment
@mnel: OP, кажется, не говорит (он просто говорит, что я хочу вычислить ...), поэтому я думаю, что это остается открытым для интерпретации. - person Ben Bolker; 21.12.2012
comment
Я надеюсь, что они не будут вычислять, чтобы отобразить 10 миллионов строк в окне консоли. Где они делают назначение будет важно. - person mnel; 21.12.2012
comment
Хм. На основе быстрой пробной версии r <- numeric(1e8) выделение/назначение вектора соответствующего размера занимает около 4-8 секунд. Если приведенный выше пример масштабируется линейно, для выполнения вычислений потребуется около 250 секунд; Мы преждевременно беспокоимся об оптимизации? Также неясно, является ли проблемой использование временной памяти (object.size вектора, который я сделал выше, составляет 760 МБ). Мы уже получили OP ускорение примерно в 250 раз по сравнению с их исходным решением ... неясно, где после этого должен быть приоритет ... - person Ben Bolker; 21.12.2012
comment
Я думаю, что проблемы с памятью (преобразование внутри приложения) также важны здесь. - person mnel; 21.12.2012
comment
@chersanya: я рассмеялся, когда увидел ваш первый комментарий выше, так как после некоторого использования R я не могу привыкнуть к тому, что другие языки не векторизованы. Каждый раз, когда мне нужно сейчас, я думаю про себя, неужели я должен сам писать этот цикл? - person Aaron left Stack Overflow; 21.12.2012

Хотя ответ Бена Болкерса является исчерпывающим, я объясню другие причины, по которым следует избегать apply в data.frames.

apply преобразует ваш data.frame в матрицу. Это создаст копию (пустая трата времени и памяти), а также, возможно, вызовет непреднамеренные преобразования типов.

Учитывая, что у вас есть 10 миллионов строк данных, я бы посоветовал вам взглянуть на пакет data.table, который позволит вам делать что-то эффективно с точки зрения памяти и времени.


Например, используя tracemem

x <- apply(d,1, hypot2)
tracemem[0x2f2f4410 -> 0x2f31b8b8]: as.matrix.data.frame as.matrix apply 

Это еще хуже, если вы затем назначите столбцу в d

d$x <- apply(d,1, hypot2)
tracemem[0x2f2f4410 -> 0x2ee71cb8]: as.matrix.data.frame as.matrix apply 
tracemem[0x2f2f4410 -> 0x2fa9c878]: 
tracemem[0x2fa9c878 -> 0x2fa9c3d8]: $<-.data.frame $<- 
tracemem[0x2fa9c3d8 -> 0x2fa9c1b8]: $<-.data.frame $<- 

4 экземпляра! -- с 10 миллионами строк это, вероятно, придет и укусит вас в какой-то момент.

Если мы используем with, copying не задействовано, если мы назначаем вектору

y <- with(d, sqrt(x^2 + y^2))

Но будет, если мы присвоим столбцу в data.frame d

d$y <- with(d, sqrt(x^2 + y^2))
tracemem[0x2fa9c1b8 -> 0x2faa00d8]: 
tracemem[0x2faa00d8 -> 0x2faa0f48]: $<-.data.frame $<- 
tracemem[0x2faa0f48 -> 0x2faa0d08]: $<-.data.frame $<- 

Теперь, если вы используете data.table и := для назначения по ссылке (без копирования)

 library(data.table)
 DT <- data.table(d)



tracemem(DT)
[1] "<0x2d67a9a0>"
DT[,y := sqrt(x^2 + y^2)]

Никаких копий!


Возможно, здесь меня поправят, но следует учитывать еще одну проблему с памятью: sqrt(x^2+y^2)) создаст 4 временные переменные (внутренне) x^2, y^2, x^2 + y^2, а затем sqrt(x^2 + y^2))

Следующее будет медленнее, но будет создаваться только две переменные.

 DT[, rowid := .I] # previous option: DT[, rowid := seq_len(nrow(DT))]
 DT[, y2 := sqrt(x^2 + y^2), by = rowid]
person mnel    schedule 20.12.2012

R векторизован, поэтому вы можете использовать следующее, конечно, подключив свою собственную матрицу

X = t(matrix(1:4, 2, 2))^2
>      [,1] [,2]
 [1,]    1    4
 [2,]    9   16

rowSums(X)^0.5

Красиво и эффективно :)

person Róisín Grannell    schedule 21.12.2012