단순히 data.frame의 크기를 미리 모른다고 가정합니다. 몇 개의 행 또는 수백만 개가 될 수 있습니다. 동적으로 커지는 일종의 컨테이너가 필요합니다. 내 경험과 모든 관련 답변을 고려하여 다음과 같은 4 가지 솔루션을 제공합니다.
rbindlist
data.frame에
사용 data.table
의 빠른 set
작동 및 필요시 수동으로 테이블을 두 배로 부부를.
RSQLite
메모리에 보관 된 테이블을 사용 하고 추가합니다.
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.50GHz에서 측정) :
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을 기하 급수적으로 사용 하고 확장합니다 (예 : 내 코드 사용).