StackOverflow'a baktım, ancak bir R veri çerçevesine satır eklemeyi içeren sorunuma özel bir çözüm bulamadım.
Boş bir 2 sütunlu veri çerçevesini aşağıdaki gibi başlatıyorum.
df = data.frame(x = numeric(), y = character())
Ardından, amacım bir değerler listesini yinelemek ve her yinelemede listenin sonuna bir değer eklemektir. Aşağıdaki kod ile başladım.
for (i in 1:10) {
df$x = rbind(df$x, i)
df$y = rbind(df$y, toString(i))
}
Ayrıca c
, append
ve merge
fonksiyonlarını da denedim ancak başarılı olamadım. Herhangi bir öneriniz varsa lütfen bana bildirin.
Ne yapmaya çalıştığınızı bilmemekle birlikte, bir öneri daha paylaşacağım: Her sütun için istediğiniz türde vektörleri önceden ayırın, bu vektörlere değerler ekleyin ve sonunda data.frame
inizi oluşturun.
Julian'ın f3
(önceden ayrılmış bir data.frame
) ile devam etmek, şimdiye kadarki en hızlı seçenek olarak tanımlandı:
# pre-allocate space
f3 <- function(n){
df <- data.frame(x = numeric(n), y = character(n), stringsAsFactors = FALSE)
for(i in 1:n){
df$x[i] <- i
df$y[i] <- toString(i)
}
df
}
İşte benzer bir yaklaşım, ancak data.frame
in son adım olarak oluşturulduğu bir yaklaşım.
# Use preallocated vectors
f4 <- function(n) {
x <- numeric(n)
y <- character(n)
for (i in 1:n) {
x[i] <- i
y[i] <- i
}
data.frame(x, y, stringsAsFactors=FALSE)
}
microbenchmark" paketinden microbenchmark
bize system.time
dan daha kapsamlı bir fikir verecektir:
library(microbenchmark)
microbenchmark(f1(1000), f3(1000), f4(1000), times = 5)
# Unit: milliseconds
# expr min lq median uq max neval
# f1(1000) 1024.539618 1029.693877 1045.972666 1055.25931 1112.769176 5
# f3(1000) 149.417636 150.529011 150.827393 151.02230 160.637845 5
# f4(1000) 7.872647 7.892395 7.901151 7.95077 8.049581 5
f1()(aşağıdaki yaklaşım),
data.frameyapısını ne kadar sık çağırdığı ve R'de nesneleri bu şekilde büyütmek genellikle yavaş olduğu için inanılmaz derecede verimsizdir.
f3(), ön ayırma nedeniyle çok daha gelişmiştir, ancak
data.frameyapısının kendisi burada darboğazın bir parçası olabilir. f4()
, almak istediğiniz yaklaşımdan ödün vermeden bu darboğazı atlamaya çalışır.
Bu gerçekten iyi bir fikir değil, ancak bu şekilde yapmak istiyorsanız, sanırım deneyebilirsiniz:
for (i in 1:10) {
df <- rbind(df, data.frame(x = i, y = toString(i)))
}
Kodunuzda başka bir sorun daha olduğunu unutmayın:
stringsAsFactors
kullanmalısınız. Kullanım: df = data.frame(x = numeric(), y = character(), stringsAsFactors = FALSE)
Önerilen üç çözümü karşılaştıralım:
# use rbind
f1 <- function(n){
df <- data.frame(x = numeric(), y = character())
for(i in 1:n){
df <- rbind(df, data.frame(x = i, y = toString(i)))
}
df
}
# use list
f2 <- function(n){
df <- data.frame(x = numeric(), y = character(), stringsAsFactors = FALSE)
for(i in 1:n){
df[i,] <- list(i, toString(i))
}
df
}
# pre-allocate space
f3 <- function(n){
df <- data.frame(x = numeric(1000), y = character(1000), stringsAsFactors = FALSE)
for(i in 1:n){
df$x[i] <- i
df$y[i] <- toString(i)
}
df
}
system.time(f1(1000))
# user system elapsed
# 1.33 0.00 1.32
system.time(f2(1000))
# user system elapsed
# 0.19 0.00 0.19
system.time(f3(1000))
# user system elapsed
# 0.14 0.00 0.14
En iyi çözüm, alanı önceden tahsis etmektir (R'de amaçlandığı gibi). Sonraki en iyi çözüm list
kullanmaktır ve en kötü çözüm (en azından bu zamanlama sonuçlarına göre) rbind
gibi görünmektedir.
Data.frame'in boyutunu önceden bilmediğinizi varsayalım. Birkaç satır da olabilir, birkaç milyon da. Dinamik olarak büyüyen bir çeşit konteynere ihtiyacınız var. SO'daki deneyimlerimi ve ilgili tüm cevapları göz önünde bulundurarak 4 farklı çözüm buldum:
data.frame'e rbindlist
data.table'ın hızlı
set` işlemini kullanın ve gerektiğinde tabloyu manuel olarak ikiye katlayarak birleştirin.
RSQLite` kullanın ve bellekte tutulan tabloya ekleyin.
data.frame'in kendi büyüme yeteneği ve data.frame
i saklamak için özel ortam (referans semantiğine sahip) kullanması, böylece dönüşte kopyalanmayacaktır.
Burada, hem az hem de çok sayıda eklenmiş satır için tüm yöntemlerin bir testi yer almaktadır. Her yöntemin kendisiyle ilişkili 3 işlevi vardır:
create(first_element)
, içine first_element
yerleştirilmiş uygun destek nesnesini döndürür.
append(object, element)
tablonun sonuna element
ekler (object
ile temsil edilir).
access(object)
eklenen tüm öğelerle birlikte data.frame
dosyasını alır.
rbindlist
Bu oldukça kolay ve anlaşılırdır:
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
+ gerektiğinde tabloyu manuel olarak ikiye katlama.Tablonun gerçek uzunluğunu bir rowcount
niteliğinde saklayacağım.
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,]))
}
RSQLite
çözümünden çok umutluydumBu temel olarak benzer bir başlıktaki Karsten W. cevabı'in kopyala&yapıştırıdır.
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
'in kendi satır ekleme + özel ortamı.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)
}
Kolaylık sağlamak için hepsini dolaylı çağrı ile kapsayacak tek bir test fonksiyonu kullanacağım. (Kontrol ettim: fonksiyonları doğrudan çağırmak yerine do.call
kullanmak kodun ölçülebilir şekilde daha uzun çalışmasını sağlamıyor).
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)))
}
Şimdi n=10 ekleme için performansı görelim.
Ayrıca, sadece test kurulumunun ek yükünü ölçmek için hiçbir şey yapmayan bir 'placebo' işlevi (son eki 0
olan) ekledim.
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 satırları için (ölçümler Intel(R) Core(TM) i7-4710HQ CPU @ 2.50GHz üzerinde yapılmıştır):
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
Görünüşe göre SQLite tabanlı sulution, büyük verilerde bir miktar hız kazanmasına rağmen, data.table + manuel üstel büyümeye yakın bir yerde değil. Aradaki fark neredeyse iki büyüklük mertebesinde!
Oldukça az sayıda satır ekleyeceğinizi biliyorsanız (n<=100), devam edin ve mümkün olan en basit çözümü kullanın: satırları parantez gösterimini kullanarak data.frame'e atayın ve data.frame'in önceden doldurulmamış olduğu gerçeğini göz ardı edin.
Diğer her şey için data.table::set
kullanın ve data.table'ı katlanarak büyütün (örneğin benim kodumu kullanarak).