apply() работает медленно - как сделать это быстрее или каковы мои альтернативы?
У меня довольно большой фрейм данных, около 10 миллионов строк. Имеет столбцы x
а также y
и что я хочу, чтобы вычислить
hypot <- function(x) {sqrt(x[1]^2 + x[2]^2)}
для каждого ряда. С помощью apply
это займет много времени (около 5 минут, интерполяция из меньших размеров) и памяти.
Но мне кажется, что это слишком много, поэтому я пробовал разные вещи:
- составление
hypot
функция сокращает время примерно на 10% - используя функции из
plyr
значительно увеличивает время работы.
Какой самый быстрый способ сделать это?
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 раз медленнее, чем векторизованные решения). Как уже указывалось в ОП, компиляция не очень помогает.
Хотя ответ Бена Болкерса является исчерпывающим, я объясню другие причины, по которым следует избегать 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]
R векторизована, так что вы можете использовать следующее, конечно, подключив свою собственную матрицу
X = t(matrix(1:4, 2, 2))^2
> [,1] [,2]
[1,] 1 4
[2,] 9 16
rowSums(X)^0.5
Красиво и эффективно:)