R veri çerçevesine satırlar nasıl eklenir


121

StackOverflow'a baktım, ancak bir R veri çerçevesine satır eklemeyi içeren sorunuma özel bir çözüm bulamıyorum.

Aşağıdaki gibi 2 sütunlu boş bir veri çerçevesi başlatıyorum.

df = data.frame(x = numeric(), y = character())

Daha sonra amacım, bir değerler listesini yinelemek ve her yinelemede listenin sonuna bir değer eklemektir. Aşağıdaki kodla başladım.

for (i in 1:10) {
    df$x = rbind(df$x, i)
    df$y = rbind(df$y, toString(i))
}

Ayrıca işlevleri teşebbüs c, appendve mergebaşarılı olamadı. Herhangi bir öneriniz olursa bilmek isterim.


2
R'nin nasıl kullanılması gerektiğini bildiğimi sanmıyorum, ancak her yinelemede indeksleri güncellemek için gerekli olan ek kod satırını yok saymak istedim ve veri çerçevesinin boyutunu kolayca önceden tahsis edemiyorum çünkü Sonuçta kaç sıra alacağını bilmiyorum. Yukarıdakilerin yalnızca tekrarlanabilir olması amaçlanan bir oyuncak örneği olduğunu unutmayın. Her iki durumda da, öneriniz için teşekkürler!
Gyan Veda

Yanıtlar:


115

Güncelleme

Ne yapmaya çalıştığınızı bilmeden, bir öneri daha paylaşacağım: Her sütun için istediğiniz türde vektörleri önceden tahsis edin, bu vektörlere değerler ekleyin ve sonunda data.frame.

Julian'ın f3(önceden tahsis edilmiş data.frame) şu ana kadarki en hızlı seçenek olarak devam etmesi:

# 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.frameson adım olarak oluşturulduğu yer.

# 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"microbenchmark" paketinden elde edilen bilgiler bize şunlardan daha kapsamlı bilgi verecektir system.time:

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), ne kadar sık ​​çağırdığı data.frameve bu şekilde büyüyen nesneler genellikle R.'de yavaş f3()olduğu için, ön tahsis nedeniyle çok daha iyi olduğu için inanılmaz derecede verimsizdir , ancak data.frameyapının kendisi buradaki darboğazın bir parçası olabilir. f4()Almak istediğiniz yaklaşımdan ödün vermeden bu darboğazı atlamaya çalışır.


Orijinal cevap

Bu gerçekten iyi bir fikir değil, ancak bu şekilde yapmak istersen, deneyebilirsin sanırım:

for (i in 1:10) {
  df <- rbind(df, data.frame(x = i, y = toString(i)))
}

Kodunuzda başka bir problem olduğunu unutmayın:

  • stringsAsFactorsKarakterlerin faktörlere dönüştürülmesini istemiyorsanız kullanmalısınız . kullanın:df = data.frame(x = numeric(), y = character(), stringsAsFactors = FALSE)

6
Teşekkürler! Bu benim sorunumu çözer. Bu neden "gerçekten iyi bir fikir değil"? Ve for döngüsünde x ve y ne şekilde karıştırılır?
Gyan Veda

5
@ user2932774, R'de bir nesneyi bu şekilde büyütmek inanılmaz derecede verimsizdir. Bir iyileştirme (ancak yine de en iyi yol değil), data.framebeklediğiniz nihai boyutta bir önceden tahsis etmek ve [çıkarma / değiştirme ile değerleri eklemek olacaktır .
A5C1D2H2I1M1N2O1R2T1

1
Teşekkürler Ananda. Normalde ön tahsis ile giderim, ancak bunun gerçekten iyi bir fikir olmadığı konusunda hemfikir değilim. Bu duruma bağlıdır. Benim durumumda, küçük verilerle uğraşıyorum ve alternatifi kodlamak için daha fazla zaman alacak. Ayrıca, her yinelemede önceden tahsis edilmiş veri çerçevesinin uygun bölümlerini doldurmak için sayısal indeksleri güncellemek için gerekli olandan daha zarif bir koddur. Sadece merak ediyorum, size göre bu görevi yerine getirmenin "en iyi yolu" nedir? Ön tahsisin en iyisi olacağını düşünürdüm.
Gyan Veda

2
@ user2932774, harika. Bakış açınızı da takdir ediyorum - ben de büyük veri kümeleriyle neredeyse hiç çalışmıyorum. Bununla birlikte, bir işlev veya başka bir şey yazmak için çalışacaksam, genellikle mümkün olduğunda daha iyi hızlar elde etmek için kodu değiştirmeye çalışmak için biraz fazladan çaba harcardım. Oldukça büyük bir hız farkı örneği için güncellememe bakın.
A5C1D2H2I1M1N2O1R2T1

1
Whoa, bu çok büyük bir fark! Bu simülasyonu çalıştırdığınız ve bana microbenchmark paketini öğrettiğiniz için teşekkürler. Bu ekstra çabayı göstermenin güzel olduğuna kesinlikle katılıyorum. Benim özel durumumda, sanırım bir daha asla çalıştırmak zorunda kalmayacağım bazı kodlarda kısa ve kirli bir şey istedim. :)
Gyan Veda

35

Ö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). Bir sonraki en iyi çözüm kullanmaktır listve en kötü çözüm (en azından bu zamanlama sonuçlarına göre) öyle görünmektedir rbind.


Teşekkürler! Yine de Ananda'nın önerisine katılmıyorum. Karakterlerin bir faktörün seviyelerine dönüştürülmesini isteyip istemediğim, çıktıyla ne yapmak istediğime bağlı olacaktır. Sanırım önerdiğiniz çözümle stringsAsFactors'ü FALSE olarak ayarlamak gerekiyor.
Gyan Veda

Simülasyon için teşekkürler. Ön ayırmanın işlem hızı açısından en iyisi olduğunun farkındayım, ancak bu kodlama kararını verirken düşündüğüm tek faktör bu değil.
Gyan Veda

1
F1'de x sayısal vektörüne dizge atayarak kafanız karıştı. Doğru satır:df <- rbind(df, data.frame(x = i, y = toString(i)))
Eldar Agalarov

14

Diyelim ki data.frame'in boyutunu önceden bilmiyorsunuz. Birkaç sıra veya birkaç milyon olabilir. Dinamik olarak büyüyen bir tür konteynere ihtiyacınız var. SO'daki deneyimlerimi ve ilgili tüm cevapları dikkate alarak 4 farklı çözümle geliyorum:

  1. rbindlist data.frame'e

  2. data.tableHızlı setçalışmayı kullanın ve gerektiğinde masayı manuel olarak ikiye katlayarak birleştirin.

  3. RSQLiteHafızada tutulan tabloyu kullanın ve ekleyin.

  4. data.frameData.frame'i saklamak için özel ortamı (referans semantiğine sahip) büyütme ve kullanma becerisi, böylece geri dönüşte kopyalanmayacaktır.

Burada, hem küçük hem de çok sayıda eklenen satırlar için tüm yöntemlerin bir testi bulunmaktadır. Her yöntemin kendisiyle ilişkili 3 işlevi vardır:

  • create(first_element)bu, yerleştirilmiş uygun destek nesnesini döndürür first_element.

  • append(object, element)elementtablonun sonuna ekler (ile temsil edilir object).

  • access(object)data.frameeklenen tüm öğelerle birlikte alır .

rbindlist data.frame'e

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 masayı manuel olarak ikiye katlama.

Tablonun gerçek uzunluğunu bir rowcountöznitelikte 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,]))
}

Hızlı kayıt ekleme için SQL optimize edilmelidir, bu nedenle başlangıçta büyük umutlarım vardı RSQLite çözüm

Bu temelde Karsten W. yanıtının benzer konuya kopyalanması ve yapıştırılması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.framekendi 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)
}

Test paketi:

Kolaylık sağlamak için, hepsini dolaylı aramayla kapsayacak şekilde bir test işlevi kullanacağım. (Kontrol ettim: do.callişlevleri doğrudan çağırmak yerine kullanmak kodun daha uzun süre ölçülebilir çalışmasını sağlamaz).

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 eklemenin performansını görelim.

Ayrıca 0hiçbir şey yapmayan bir 'plasebo' işlevi (son ekli ) ekledim - sadece test kurulumunun ek yükünü ölçmek için.

r<-microbenchmark(test(0,n=10), test(1,n=10),test(2,n=10),test(3,n=10), test(4,n=10))
autoplot(r)

N = 10 satır eklemek için zamanlamalar

N = 100 satır için zamanlamalar N = 1000 satır için zamanlamalar

1E5 satırları için (ölçümler Intel (R) Core (TM) i7-4710HQ CPU @ 2.50GHz'de 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

SQLite tabanlı sulandırma, büyük verilerde bir miktar hız kazanmasına rağmen, data.table + manuel üstel büyümeye yakın değil gibi görünüyor. Aradaki fark neredeyse iki kat büyüklüğündedir!

özet

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ış.

Diğer her şey data.table::setiçin data.table'ı üssel olarak kullanın ve büyütün (örneğin benim kodumu kullanarak).


2
SQLite'ın yavaş olmasının nedeni, her INSERT INTO'da, REINDEX'e ihtiyaç duymasıdır; bu, O (n) olup, burada n, satır sayısıdır. Bu, SQL veritabanına her seferinde bir satır eklemenin O (n ^ 2) olduğu anlamına gelir. Tüm bir data.frame'i aynı anda eklerseniz SQLite çok hızlı olabilir, ancak satır satır büyümede en iyisi değildir.
Julian Zucker

5

Purrr, tidyr ve dplyr ile güncelleme

Soru zaten tarihli olduğundan (6 yıl), yanıtlarda daha yeni paketler tidyr ve purrr ile bir çözüm eksik. Bu nedenle, bu paketlerle çalışan insanlar için, önceki cevaplara bir çözüm eklemek istiyorum - özellikle hepsi oldukça ilginç.

Purrr ve tidyr'in en büyük avantajı daha iyi okunabilirliktir IMHO. purrr, lapply'yi daha esnek map () ailesiyle değiştirir, tidyr süper sezgisel add_row yöntemini sunar - sadece söylediği şeyi yapar :)

map_df(1:1000, function(x) { df %>% add_row(x = x, y = toString(x)) })

Bu çözüm kısa ve okuması sezgiseldir ve nispeten hızlıdır:

system.time(
   map_df(1:1000, function(x) { df %>% add_row(x = x, y = toString(x)) })
)
   user  system elapsed 
   0.756   0.006   0.766

Neredeyse doğrusal olarak ölçeklenir, bu nedenle 1e5 satırları için performans:

system.time(
  map_df(1:100000, function(x) { df %>% add_row(x = x, y = toString(x)) })
)
   user  system elapsed 
 76.035   0.259  76.489 

@Adam Ryczkowski tarafından yapılan karşılaştırmada data.table (plaseboyu görmezden gelirseniz) hemen ardından ikinci sırada yer almasını sağlar:

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

Kullanmanıza gerek yok add_row. Örneğin: map_dfr(1:1e5, function(x) { tibble(x = x, y = toString(x)) }).
user3808394

@ user3808394 teşekkürler, bu ilginç bir alternatif! birisi sıfırdan bir veri çerçevesi oluşturmak isterse, sizinki daha kısadır, bu nedenle daha iyi bir çözümdür. Zaten bir veri çerçeveniz varsa, benim çözümüm elbette daha iyi.
Agile Bean

Zaten bir veri çerçeveniz varsa bind_rows(df, map_dfr(1:1e5, function(x) { tibble(x = x, y = toString(x)) })), kullanmak yerine yaparsınız add_row.
user3808394

2

1'den 5'e kadar sayıları olan bir vektör 'noktası' alalım

point = c(1,2,3,4,5)

vektörün herhangi bir yerine 6 sayısını eklemek istersek, aşağıdaki komut işe yarayabilir

i) Vektörler

new_var = append(point, 6 ,after = length(point))

ii) bir tablonun sütunları

new_var = append(point, 6 ,after = length(mtcars$mpg))

Komut appendüç argüman alır:

  1. değiştirilecek vektör / sütun.
  2. değiştirilen vektöre dahil edilecek değer.
  3. bir alt simge, ardından değerler eklenecektir.

basit...!! Herhangi bir durumda özür dilerim ...!


1

Aşağıdakiler için daha genel bir çözüm olabilir.

    extendDf <- function (df, n) {
    withFactors <- sum(sapply (df, function(X) (is.factor(X)) )) > 0
    nr          <- nrow (df)
    colNames    <- names(df)
    for (c in 1:length(colNames)) {
        if (is.factor(df[,c])) {
            col         <- vector (mode='character', length = nr+n) 
            col[1:nr]   <- as.character(df[,c])
            col[(nr+1):(n+nr)]<- rep(col[1], n)  # to avoid extra levels
            col         <- as.factor(col)
        } else {
            col         <- vector (mode=mode(df[1,c]), length = nr+n)
            class(col)  <- class (df[1,c])
            col[1:nr]   <- df[,c] 
        }
        if (c==1) {
            newDf       <- data.frame (col ,stringsAsFactors=withFactors)
        } else {
            newDf[,c]   <- col 
        }
    }
    names(newDf) <- colNames
    newDf
}

ExtendDf () işlevi, n satırlı bir veri çerçevesini genişletir.

Örnek olarak:

aDf <- data.frame (l=TRUE, i=1L, n=1, c='a', t=Sys.time(), stringsAsFactors = TRUE)
extendDf (aDf, 2)
#      l i n c                   t
# 1  TRUE 1 1 a 2016-07-06 17:12:30
# 2 FALSE 0 0 a 1970-01-01 01:00:00
# 3 FALSE 0 0 a 1970-01-01 01:00:00

system.time (eDf <- extendDf (aDf, 100000))
#    user  system elapsed 
#   0.009   0.002   0.010
system.time (eDf <- extendDf (eDf, 100000))
#    user  system elapsed 
#   0.068   0.002   0.070

0

Çözümüm orijinal cevapla neredeyse aynı ama benim için işe yaramadı.

Bu yüzden sütunlara isimler verdim ve işe yarıyor:

painel <- rbind(painel, data.frame("col1" = xtweets$created_at,
                                   "col2" = xtweets$text))
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.