Припустимо, ви просто не знаєте розмір data.frame заздалегідь. Це може бути кілька рядів, або кілька мільйонів. Потрібно мати якийсь контейнер, який динамічно зростає. Беручи до уваги мій досвід та всі відповіді на відповіді, я маю чотири різних рішення:
rbindlist до фрейму data.frame
Використовуйте data.tableшвидку setроботу і з'єднайте її з подвоєнням столу вручну при необхідності.
Використовуйте RSQLiteта додайте до таблиці, що зберігається в пам'яті.
data.frameвласна здатність вирощувати та використовувати користувальницьке середовище (яке має довідкову семантику) для зберігання data.frame, щоб воно не було скопійовано при поверненні.
Ось тест всіх методів як для малої, так і для великої кількості доданих рядків. Кожен метод має 3 функції, пов'язані з ним:
create(first_element)який повертає відповідний резервний об'єкт із first_elementвкладеним.
append(object, element)що додає elementдо кінця таблиці (представленого object).
access(object)отримує data.frameз усіма вставленими елементами.
rbindlist до фрейму data.frame
Це досить просто і прямо:
create.1<-function(elems)
{
return(as.data.table(elems))
}
append.1<-function(dt, elems)
{
return(rbindlist(list(dt, elems),use.names = TRUE))
}
access.1<-function(dt)
{
return(dt)
}
data.table::set + вручну подвоєння таблиці при необхідності.
Я буду зберігати справжню довжину таблиці в rowcountатрибуті.
create.2<-function(elems)
{
return(as.data.table(elems))
}
append.2<-function(dt, elems)
{
n<-attr(dt, 'rowcount')
if (is.null(n))
n<-nrow(dt)
if (n==nrow(dt))
{
tmp<-elems[1]
tmp[[1]]<-rep(NA,n)
dt<-rbindlist(list(dt, tmp), fill=TRUE, use.names=TRUE)
setattr(dt,'rowcount', n)
}
pos<-as.integer(match(names(elems), colnames(dt)))
for (j in seq_along(pos))
{
set(dt, i=as.integer(n+1), pos[[j]], elems[[j]])
}
setattr(dt,'rowcount',n+1)
return(dt)
}
access.2<-function(elems)
{
n<-attr(elems, 'rowcount')
return(as.data.table(elems[1:n,]))
}
SQL повинен бути оптимізований для швидкого вставки запису, тому я спочатку покладав великі надії на RSQLiteрішення
Це в основному копіювання та вставка відповіді Karsten W. на подібній темі.
create.3<-function(elems)
{
con <- RSQLite::dbConnect(RSQLite::SQLite(), ":memory:")
RSQLite::dbWriteTable(con, 't', as.data.frame(elems))
return(con)
}
append.3<-function(con, elems)
{
RSQLite::dbWriteTable(con, 't', as.data.frame(elems), append=TRUE)
return(con)
}
access.3<-function(con)
{
return(RSQLite::dbReadTable(con, "t", row.names=NULL))
}
data.frameвласне додавання до рядків + користувацьке середовище.
create.4<-function(elems)
{
env<-new.env()
env$dt<-as.data.frame(elems)
return(env)
}
append.4<-function(env, elems)
{
env$dt[nrow(env$dt)+1,]<-elems
return(env)
}
access.4<-function(env)
{
return(env$dt)
}
Тестовий набір:
Для зручності я використовую одну тестову функцію, щоб покрити їх усі непрямими дзвінками. (Я перевірив: використання do.callзамість прямого виклику функцій не збільшує запуск коду довше).
test<-function(id, n=1000)
{
n<-n-1
el<-list(a=1,b=2,c=3,d=4)
o<-do.call(paste0('create.',id),list(el))
s<-paste0('append.',id)
for (i in 1:n)
{
o<-do.call(s,list(o,el))
}
return(do.call(paste0('access.', id), list(o)))
}
Давайте подивимося на ефективність для n = 10 вставок.
Я також додав функції «плацебо» (із суфіксом 0), які нічого не виконують - лише для вимірювання накладних витрат тестової установки.
r<-microbenchmark(test(0,n=10), test(1,n=10),test(2,n=10),test(3,n=10), test(4,n=10))
autoplot(r)


Для рядків 1E5 (вимірювання зроблені на процесорі Intel (R) Core (TM) i7-4710HQ CPU @ 2,50 ГГц):
nr function time
4 data.frame 228.251
3 sqlite 133.716
2 data.table 3.059
1 rbindlist 169.998
0 placebo 0.202
Схоже, що сулюція на основі SQLite, хоча і набирає деяку швидкість для великих даних, ніде не знаходиться поблизу data.table + ручне експоненціальне зростання. Різниця майже в два порядки!
Підсумок
Якщо ви знаєте, що ви будете додавати досить невелику кількість рядків (n <= 100), вперед і скористайтеся найпростішим можливим рішенням: просто призначте рядки в data.frame за допомогою позначення дужок і ігноруйте факт, що data.frame є не попередньо заселений.
Для всього іншого використовуйте data.table::setта вирощуйте таблицю даних експоненціально (наприклад, використовуючи мій код).