Выращивание кадра данных эффективным образом

В соответствии с Создание строки данных по строке R по умолчанию, это не идеальное дополнение к data.frame с помощью rbind, поскольку оно создает копию весь data.frame каждый раз. Как накапливать данные в R, приводя к data.frame, не нанося этого штрафа? Промежуточный формат не должен быть data.frame.

Ответ 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

benchmarks

И сравнение := с 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, как в приведенных выше тестах. Таким образом, порядок на порядок больше, и результат измеряется в миллисекундах, а не секундах. Впечатляет действительно.

Ответ 2

Мне нравится RSQLite: dbWriteTable(...,append=TRUE) при сборке и dbReadTable в конце.

Если данные достаточно малы, можно использовать файл ": memory:", если он большой, жесткий диск.

Конечно, он не может конкурировать с точки зрения скорости:

makeRow <- function() data.frame(x=runif(1),y=rnorm(1))

library(RSQLite)
con <- dbConnect(RSQLite::SQLite(), ":memory:")

collect1 <- function(n) {
  for (i in 1:n) dbWriteTable(con, "test", makeRow(), append=TRUE)
  dbReadTable(con, "test", row.names=NULL)
}

collect2 <- function(n) {
  res <- data.frame(x=rep(NA, n), y=rep(NA, n))
  for(i in 1:n) res[i,] <- makeRow()[1,]
  res
}

> system.time(collect1(1000))
   User      System verstrichen 
   7.01        0.00        7.05  
> system.time(collect2(1000))
   User      System verstrichen 
   0.80        0.01        0.81 

Но это может выглядеть лучше, если data.frame имеет более одной строки. И вам не нужно знать количество строк заранее.

Ответ 3

У вас также может быть пустой объект списка, где элементы заполняются с помощью dataframes; затем собирайте результаты в конце с помощью sapply или аналогичного. Ниже приведен пример здесь. Это не приведет к штрафам за увеличение объекта.

Ответ 4

Ну, я очень удивлен, что никто не упоминал о преобразовании в матрицу...

Сравнение с функциями dt.colon и dt.set, определенными Ari B Friedman, преобразование в матрицу имеет лучшее время работы (немного быстрее, чем dt.colon). Все аффекты внутри матрицы выполняются с помощью ссылки, поэтому в этом коде нет лишней копии памяти.

CODE:

library(data.table)
n <- 10^4
dt <- data.table(x=rep(0,n), y=rep(0,n))

use.matrix <- function(dt) {
  mat = as.matrix(dt)  # converting to matrix
  for(i in 1:n) {
    mat[i,1] = runif(1)
    mat[i,2] = rnorm(1)
  }
  return(as.data.frame(mat))  # converting back to a data.frame
}


dt.colon <- function(dt) { # same as Ari function
  for(i in 1:n) {
    dt[i,x := runif(1)]
    dt[i,y := rnorm(1)]
  }
}

dt.set <- function(dt) { # same as Ari function
  for(i in 1:n) {
    set(dt,i,1L, runif(1) )
    set(dt,i,2L, rnorm(1) )
  }
}

library(microbenchmark)
microbenchmark(dt.colon(dt), dt.set(dt), use.matrix(dt),times=10)

РЕЗУЛЬТАТ:

Unit: milliseconds
           expr        min         lq     median         uq        max neval
   dt.colon(dt) 7107.68494 7193.54792 7262.76720 7277.24841 7472.41726    10
     dt.set(dt)   93.25954   94.10291   95.07181   97.09725   99.18583    10
 use.matrix(dt)   48.15595   51.71100   52.39375   54.59252   55.04192    10

Плюсы использования матрицы:

  • это самый быстрый метод до сих пор
  • Вам не нужно изучать/использовать объекты data.table

Con с использованием матрицы:

  • вы можете обрабатывать только один тип данных в матрице (в частности, если у вас были смешанные типы в столбцах вашего data.frame, то все они будут преобразованы в символ по строке: mat = as. матрица (dt) # преобразование в матрицу)