Jak dodawać wiersze do ramki danych R

Rozglądałem się po StackOverflow, ale nie mogę znaleźć rozwiązania specyficznego dla mojego problemu, polegającego na dodawaniu wierszy do ramki danych R.

Inicjalizuję pustą 2-kolumnową ramkę danych, w następujący sposób.

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

Następnie, moim celem jest iteracja poprzez listę wartości i, w każdej iteracji, dołączyć wartość do końca listy. Zacząłem od następującego kodu.

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

Spróbowałem też funkcjic, append, i merge bez powodzenia. Proszę daj mi znać jeśli masz jakieś sugestie.

Author: Gyan Veda, 2013-12-19

5 answers

Update

Nie wiedząc, co próbujesz zrobić, podzielę się jeszcze jedną sugestią: Prealokuj wektory typu, który chcesz dla każdej kolumny, Wstaw wartości do tych wektorów, a następnie, na koniec, utwórz data.frame.

Kontynuując f3 (prealokowany data.frame) jako najszybsza opcja do tej pory, zdefiniowana jako:

# 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
}

Oto podobne podejście, ale takie, w którym data.frame jest tworzony jako ostatni krok.

# 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 z "microbenchmark" pakiet da nam bardziej wszechstronny wgląd niż 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() (podejście poniżej) jest niewiarygodnie nieefektywne ze względu na to, jak często wywołuje data.frame i ponieważ rosnące obiekty w ten sposób są generalnie powolne w R. f3() jest znacznie ulepszone ze względu na prealokację, ale sama struktura data.frame może być częścią wąskiego gardła tutaj. f4() próbuje ominąć to wąskie gardło bez narażania podejścia, które chcesz podjąć.


Oryginalna odpowiedź

To jest naprawdę nie jest to dobry pomysł, ale jeśli chcesz to zrobić w ten sposób, myślę, że możesz spróbować: {]}

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

Zauważ, że w Twoim kodzie jest jeszcze jeden problem:

  • powinieneś użyć stringsAsFactors, jeśli chcesz, aby znaki nie zostały przekonwertowane na czynniki. Użycie: df = data.frame(x = numeric(), y = character(), stringsAsFactors = FALSE)
 96
Author: A5C1D2H2I1M1N2O1R2T1,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-12-19 20:10:38

Porównajmy trzy proponowane rozwiązania:

# 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

Najlepszym rozwiązaniem jest wstępna alokacja przestrzeni (zgodnie z przeznaczeniem w R). Kolejnym najlepszym rozwiązaniem jest użycie list, A najgorszym rozwiązaniem (przynajmniej na podstawie tych wyników czasowych) wydaje się rbind.

 26
Author: Julián Urbano,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-04-25 17:07:13

Załóżmy, że po prostu nie znasz rozmiaru danych.klatka z góry. Może to być kilka rzędów lub kilka milionów. Musisz mieć jakiś kontener, który rośnie dynamicznie. Biorąc pod uwagę moje doświadczenie i wszystkie związane z nim odpowiedzi, więc przychodzę z 4 odrębnymi rozwiązaniami: {]}

  1. rbindlist do danych.ramka

  2. Użyj szybkiej operacji data.table set i połącz ją z ręcznym podwojeniem tabeli, gdy potrzebne.

  3. Użyj RSQLite i dołącz do tabeli przechowywanej w pamięci.

  4. data.frame'S własne możliwości rozwoju i korzystania z własnego środowiska (które ma semantykę odniesienia) do przechowywania danych.ramka więc nie będzie kopiowana po powrocie.

Oto test wszystkich metod zarówno dla małej, jak i dużej liczby dołączonych wierszy. Każda metoda ma 3 związane z nią funkcje:

  • create(first_element) to zwraca odpowiedni obiekt podkładowy z first_element włożonym.

  • append(object, element) dodaje element do końca tabeli (reprezentowanej przez object).

  • access(object) pobiera data.frame ze wszystkimi wstawionymi elementami.

rbindlist do danych.frame

To jest dość proste i proste:

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 + ręczne podwojenie tabeli w razie potrzeby.

Zachowam prawdziwą Długość Tabeli w rowcount atrybut.

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 powinien być zoptymalizowany pod kątem szybkiego wstawiania rekordów, więc początkowo miałem duże nadzieje na rozwiązanieRSQLite

To jest w zasadzie kopiuj i wklej Karsten W. odpowiedź w podobnym wątku.

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's own row-appending + custom environment.

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)
}

Zestaw testów:

Dla wygody użyję jednej funkcji testowej, aby pokryć je wszystkie wywołaniem pośrednim. (Sprawdziłem: używanie do.call zamiast wywoływania funkcji bezpośrednio nie powoduje wydłużenia działania kodu).

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)))
}

Zobaczmy wydajność dla N = 10 insercji.

Dodałem również funkcje "placebo" (z przyrostkiem 0), które niczego nie wykonują - tylko po to, aby zmierzyć obciążenie zestawu testowego.

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

Czas dodawania N = 10 wierszy

Czasy dla N = 100 wierszy Terminy dla N = 1000 wierszy

[27]} dla 1E5 wierszy (pomiary wykonywane na procesorze Intel(R) Core(TM) i7-4710HQ @ 2.50 GHz):
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

Wygląda na to, że oparty na SQLite sulucja, chociaż odzyskuje pewną prędkość na dużych danych, nie jest w pobliżu danych.tabela + ręczny wzrost wykładniczy. Różnica jest prawie dwa rzędy wielkości!

Podsumowanie

jeśli wiesz, że dodasz raczej małą liczbę wierszy( N

do wszystkiego innego użyj data.table::set i powiększyć dane.tabelę wykładniczo (np. używając mojego kodu).

 12
Author: Adam Ryczkowski,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:34:23

Weźmy wektor 'punkt', który ma liczby od 1 do 5

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

Jeśli chcemy dodać liczbę 6 w dowolnym miejscu wewnątrz wektora to poniższe polecenie może się przydać

I) Wektory

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

Ii) Kolumny tabeli

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

Komenda append przyjmuje trzy argumenty:

  1. wektor/kolumna do modyfikacji.
  2. wartość, którą należy uwzględnić w zmodyfikowanym wektorze.
  3. indeks dolny, po którym wartości należy dołączyć.
Proste...!! Przepraszamy w razie jakichkolwiek...!
 2
Author: Praneeth Krishna,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-02-07 13:25:28

Bardziej ogólne rozwiązanie może być następujące.

    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
}

Funkcja extendDf () rozszerza ramkę danych o n wierszy.

Jako przykład:

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
 1
Author: Pisca46,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-07-06 15:30:45