Ответ 1
Первый подход
Я попытался получить доступ к каждому элементу предварительно выделенного файла data.frame:
res <- data.frame(x=rep(NA,1000), y=rep(NA,1000))
tracemem(res)
for(i in 1:1000) {
res[i,"x"] <- runif(1)
res[i,"y"] <- rnorm(1)
}
Но tracemem сходит с ума (например, каждый раз копируется data.frame на новый адрес).
Альтернативный подход (тоже не работает)
Один подход (не уверен, что он быстрее, чем я еще не тестировал) состоит в том, чтобы создать список data.frames, а затем stack
все вместе:
makeRow <- function() data.frame(x=runif(1),y=rnorm(1))
res <- replicate(1000, makeRow(), simplify=FALSE ) # returns a list of data.frames
library(taRifx)
res.df <- stack(res)
К сожалению, при создании списка я думаю, что вам будет сложно предварительно выделить. Например:
> tracemem(res)
[1] "<0x79b98b0>"
> res[[2]] <- data.frame()
tracemem[0x79b98b0 -> 0x71da500]:
Другими словами, замена элемента списка приводит к копированию списка. Я принимаю весь список, но это возможно только для этого элемента списка. Я не очень хорошо разбираюсь в деталях управления памятью R.
Вероятно, лучший подход
Как и во многих процессах с ограниченной скоростью или ограничением памяти, в настоящее время наилучшим подходом может быть использование data.table
вместо data.frame
. Поскольку data.table
имеет назначение :=
по ссылочному оператору, он может обновляться без повторного копирования:
library(data.table)
dt <- data.table(x=rep(0,1000), y=rep(0,1000))
tracemem(dt)
for(i in 1:1000) {
dt[i,x := runif(1)]
dt[i,y := rnorm(1)]
}
# note no message from tracemem
Но, как указывает @MatthewDowle, set()
- это подходящий способ сделать это внутри цикла. Это делает его еще быстрее:
library(data.table)
n <- 10^6
dt <- data.table(x=rep(0,n), y=rep(0,n))
dt.colon <- function(dt) {
for(i in 1:n) {
dt[i,x := runif(1)]
dt[i,y := rnorm(1)]
}
}
dt.set <- function(dt) {
for(i in 1:n) {
set(dt,i,1L, runif(1) )
set(dt,i,2L, rnorm(1) )
}
}
library(microbenchmark)
m <- microbenchmark(dt.colon(dt), dt.set(dt),times=2)
(Результаты показаны ниже)
Бенчмаркинг
Когда цикл работает 10000 раз, таблица данных почти на порядок выше:
Unit: seconds
expr min lq median uq max
1 test.df() 523.49057 523.49057 524.52408 525.55759 525.55759
2 test.dt() 62.06398 62.06398 62.98622 63.90845 63.90845
3 test.stack() 1196.30135 1196.30135 1258.79879 1321.29622 1321.29622
И сравнение :=
с set()
:
> m
Unit: milliseconds
expr min lq median uq max
1 dt.colon(dt) 654.54996 654.54996 656.43429 658.3186 658.3186
2 dt.set(dt) 13.29612 13.29612 15.02891 16.7617 16.7617
Заметим, что n
здесь 10 ^ 6 не 10 ^ 5, как в приведенных выше тестах. Таким образом, порядок на порядок больше, и результат измеряется в миллисекундах, а не секундах. Впечатляет действительно.