Wyhodowanie danych.kadrowanie w sposób efektywny pamięciowo

Zgodnie z tworzeniem ramki danych r wiersz po wierszu , nie jest idealnym rozwiązaniem dołączanie do data.frame za pomocą rbind, ponieważ tworzy ona kopię całych danych.kadruj za każdym razem. Jak gromadzić dane w R w wyniku data.frame bez ponoszenia tej kary? Format pośredni nie musi być data.frame.

Author: Community, 2012-07-14

4 answers

Pierwsze podejście

Próbowałem uzyskać dostęp do każdego elementu wstępnie przydzielonych danych.frame:

res <- data.frame(x=rep(NA,1000), y=rep(NA,1000))
tracemem(res)
for(i in 1:1000) {
  res[i,"x"] <- runif(1)
  res[i,"y"] <- rnorm(1)
}

Ale tracemem szaleje (np. dane.ramka jest kopiowana na nowy adres za każdym razem).

Alternatywne podejście (również nie działa)

Jednym z podejść (nie jestem pewien, czy jest szybszy, ponieważ nie mam jeszcze benchmarked) jest stworzenie listy danych.ramki, a następnie stack wszystkie razem:

makeRow <- function() data.frame(x=runif(1),y=rnorm(1))
res <- replicate(1000, makeRow(), simplify=FALSE ) # returns a list of data.frames
library(taRifx)
res.df <- stack(res)

Niestety tworząc listę myślę, że będzie ciężko przeznaczyć na wstępną alokację. Na przykład:

> tracemem(res)
[1] "<0x79b98b0>"
> res[[2]] <- data.frame()
tracemem[0x79b98b0 -> 0x71da500]: 

Innymi słowy, zastąpienie elementu listy powoduje skopiowanie listy. Zakładam, że cała lista, ale możliwe, że to tylko ten element listy. Nie znam szczegółów zarządzania pamięcią R.

Prawdopodobnie najlepsze podejście

Jak w przypadku wielu procesów o ograniczonej szybkości lub pamięci, najlepszym rozwiązaniem może być użycie data.table zamiast data.frame. Od data.table ma := przypisać przez operatora odniesienia, może aktualizować bez ponownego kopiowania:

library(data.table)
dt <- data.table(x=rep(0,1000), y=rep(0,1000))
tracemem(dt)
for(i in 1:1000) {
  dt[i,x := runif(1)]
  dt[i,y := rnorm(1)]
}
# note no message from tracemem

Ale jak wskazuje @ MatthewDowle, set() jest właściwym sposobem, aby to zrobić wewnątrz pętli. To sprawia, że jeszcze szybciej:

library(data.table)
n <- 10^6
dt <- data.table(x=rep(0,n), y=rep(0,n))

dt.colon <- function(dt) {
  for(i in 1:n) {
    dt[i,x := runif(1)]
    dt[i,y := rnorm(1)]
  }
}

dt.set <- function(dt) {
  for(i in 1:n) {
    set(dt,i,1L, runif(1) )
    set(dt,i,2L, rnorm(1) )
  }
}

library(microbenchmark)
m <- microbenchmark(dt.colon(dt), dt.set(dt),times=2)

(wyniki pokazane poniżej)

Benchmarking

Z pętlą wykonaną 10 000 razy, Tabela danych jest prawie o pełny rząd wielkości szybsza:
Unit: seconds
          expr        min         lq     median         uq        max
1    test.df()  523.49057  523.49057  524.52408  525.55759  525.55759
2    test.dt()   62.06398   62.06398   62.98622   63.90845   63.90845
3 test.stack() 1196.30135 1196.30135 1258.79879 1321.29622 1321.29622

benchmarki

I porównanie := z set():

> m
Unit: milliseconds
          expr       min        lq    median       uq      max
1 dt.colon(dt) 654.54996 654.54996 656.43429 658.3186 658.3186
2   dt.set(dt)  13.29612  13.29612  15.02891  16.7617  16.7617

Zauważ, że n tutaj jest 10^6 a nie 10^5 jak w benchmarkach wykreślonych powyżej. Więc jest rząd wielkości więcej pracy, a wynik jest mierzony w milisekundach, a nie sekundach. Imponujące.

 39
Author: Ari B. Friedman,
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
2012-07-18 11:04:28

Możesz również mieć obiekt pustej listy, w którym elementy są wypełnione ramkami danych; następnie zbierz wyniki na końcu za pomocą sapply lub podobnych. Przykład można znaleźć tutaj. Nie pociągnie to za sobą kar za uprawianie przedmiotu.

 6
Author: Marc in the box,
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
2012-07-14 20:09:02

Jestem bardzo zaskoczony, że nikt jeszcze nie wspomniał o konwersji do macierzy...

W porównaniu z dt.dwukropek i dt.zbiór funkcji zdefiniowanych przez Ariego B. Friedmana , konwersja na macierz ma najlepszy czas działania (nieco szybszy niż dt.dwukropek ). Wszystkie afektacje wewnątrz macierzy są wykonywane przez odniesienie, Więc nie ma niepotrzebnej kopii pamięci wykonywanej w tym kodzie.

Kod:

library(data.table)
n <- 10^4
dt <- data.table(x=rep(0,n), y=rep(0,n))

use.matrix <- function(dt) {
  mat = as.matrix(dt)  # converting to matrix
  for(i in 1:n) {
    mat[i,1] = runif(1)
    mat[i,2] = rnorm(1)
  }
  return(as.data.frame(mat))  # converting back to a data.frame
}


dt.colon <- function(dt) { # same as Ari's function
  for(i in 1:n) {
    dt[i,x := runif(1)]
    dt[i,y := rnorm(1)]
  }
}

dt.set <- function(dt) { # same as Ari's function
  for(i in 1:n) {
    set(dt,i,1L, runif(1) )
    set(dt,i,2L, rnorm(1) )
  }
}

library(microbenchmark)
microbenchmark(dt.colon(dt), dt.set(dt), use.matrix(dt),times=10)

Wynik:

Unit: milliseconds
           expr        min         lq     median         uq        max neval
   dt.colon(dt) 7107.68494 7193.54792 7262.76720 7277.24841 7472.41726    10
     dt.set(dt)   93.25954   94.10291   95.07181   97.09725   99.18583    10
 use.matrix(dt)   48.15595   51.71100   52.39375   54.59252   55.04192    10

Plusy użycie macierzy:

  • jest to najszybsza Metoda do tej pory
  • nie musisz uczyć się/używać danych.obiekty tabeli

Con użycia macierzy:

  • można obsługiwać tylko jeden typ danych w macierzy (w szczególności, jeśli w kolumnach danych były mieszane typy.ramki, wtedy wszystkie zostaną przekonwertowane na znak w linii: mat = as.macierz (dt) # konwersja na macierz )
 6
Author: Jealie,
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:25:55

Lubię RSQLite w tej kwestii: dbWriteTable(...,append=TRUE) oświadczenia Podczas zbierania, i dbReadTable Oświadczenie na końcu.

Jeśli dane są wystarczająco małe, można użyć pliku": memory:", jeśli jest duży, dysk twardy.

Oczywiście nie może konkurować pod względem prędkości:

makeRow <- function() data.frame(x=runif(1),y=rnorm(1))

library(RSQLite)
con <- dbConnect(RSQLite::SQLite(), ":memory:")

collect1 <- function(n) {
  for (i in 1:n) dbWriteTable(con, "test", makeRow(), append=TRUE)
  dbReadTable(con, "test", row.names=NULL)
}

collect2 <- function(n) {
  res <- data.frame(x=rep(NA, n), y=rep(NA, n))
  for(i in 1:n) res[i,] <- makeRow()[1,]
  res
}

> system.time(collect1(1000))
   User      System verstrichen 
   7.01        0.00        7.05  
> system.time(collect2(1000))
   User      System verstrichen 
   0.80        0.01        0.81 

Ale może lepiej wyglądać, jeśli data.frame s mają więcej niż jeden rząd. I nie musisz znać liczby wierszy z góry.

 5
Author: Karsten W.,
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-01-21 05:06:36