Припустимо, ви просто не знаєте розмір 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
та вирощуйте таблицю даних експоненціально (наприклад, використовуючи мій код).