Предположим, вы просто не знаете заранее размер data.frame. Это может быть несколько строк или несколько миллионов. Вам нужен какой-то контейнер, который динамично растет. Принимая во внимание мой опыт и все связанные с ним ответы в SO, у меня есть 4 различных решения:
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
решение
Это в основном копирование и вставка ответа Карстена В. в аналогичной теме.
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 @ 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
и увеличивайте data.table экспоненциально (например, используя мой код).