2012-07-14 4 views
36

Selon Creating an R dataframe row-by-row, il n'est pas idéal d'ajouter à un data.frame en utilisant rbind, car il crée une copie de l'ensemble data.frame à chaque fois. Comment puis-je accumuler des données au R pour obtenir un data.frame sans encourir cette pénalité? Le format intermédiaire n'a pas besoin d'être un data.frame.Augmenter un data.frame d'une manière efficace en mémoire

+0

modifié pour préciser ce que je suis sûr que vous vouliez dire. S'il vous plaît revenir si j'ai foiré. –

+0

Si vous êtes toujours intéressé, [voici une autre référence d'autre ensemble de manière différente de développer data.frame] (http://stackoverflow.com/questions/20689650/how-to-append-rows-to-an-r -data-frame/38052208 # 38052208) lorsque vous ne connaissez pas la taille à l'avance. –

Répondre

38

Première approche

J'essayé d'accéder à chaque élément d'une data.frame pré-alloué:

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

Mais tracemem devient fou (par exemple, la data.frame est en cours de copie à une nouvelle adresse chaque fois).

approche alternative (ne fonctionne pas non plus)

Une approche (pas sûr qu'il est plus rapide que je ne l'ai pas encore benchmarkée) est de créer une liste de data.frames, puis les stack tous ensemble:

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) 

Malheureusement, lors de la création de la liste, je pense que vous aurez du mal à pré-allouer. Par exemple:

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

En d'autres termes, le remplacement d'un élément de la liste entraîne la copie de la liste. Je suppose que toute la liste, mais il est possible que ce soit seulement cet élément de la liste. Je ne suis pas intimement familier avec les détails de la gestion de la mémoire de R.

Probablement la meilleure approche

Comme beaucoup de vitesse ou les processus de mémoire limitée ces jours-ci, la meilleure approche pourrait bien être d'utiliser data.table au lieu d'un data.frame. Depuis data.table a le := assign par l'opérateur de référence, il peut mettre à jour sans re-copie:

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 

Mais comme le souligne @MatthewDowle sur, set() est la manière appropriée de le faire dans une boucle. Cela rend encore plus vite:

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) 

(résultats affichés ci-dessous)

Analyse comparative

Avec la course en boucle 10.000 fois, une table de données est presque un ordre plein de grandeur plus rapide:

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 

benchmarks

et la comparaison des := avec 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 

Notez que n ici est 10^10^6 pas 5 comme dans les repères tracés ci-dessus.Il y a donc un ordre de grandeur de travail supplémentaire, et le résultat est mesuré en millisecondes et non en secondes. Impressionnant en effet.

+3

Pour autant que je sache, votre dernier exemple ne développe pas la table data.table. Vous écrasez simplement la première rangée 1000 fois. – Andrie

+0

@Andrie. Oops. Corrigé cela. Merci de l'avoir signalé. –

+3

C'est bien mais avez-vous vu l'exemple de vitesse en bas de '?": = "' Comparer ': =' dans une boucle à 'set()' dans une boucle. ': =' a des frais généraux (par ex.vérifier l'existence et le type des arguments passés à '[.data.table'], c'est pourquoi' set() 'est prévu pour être utilisé dans les boucles. –

5

J'aime RSQLite d'ailleurs: dbWriteTable(...,append=TRUE) déclarations lors de la collecte, et dbReadTable déclaration à la fin.

Si les données sont assez petites, on peut utiliser le fichier ": memory:", s'il est grand, le disque dur.

Bien sûr, il ne peut rivaliser en termes de vitesse:

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 

Mais il pourrait mieux si les data.frame s ont plus d'une ligne. Et vous n'avez pas besoin de connaître le nombre de lignes à l'avance.

+0

L'idée est cool, mais il [est loin d'être efficace] (http://stackoverflow.com/questions/20689650/how-to-append-rows-to-an-r-data-frame/38052208#38052208). Je l'ai mis sur un test sur un autre fil. –

5

Vous pouvez également avoir un objet de liste vide où les éléments sont remplis de données; puis recueillir les résultats à la fin avec sapply ou similaire. Un exemple peut être trouvé here. Cela n'encourra pas les pénalités de croissance d'un objet.

5

Eh bien, je suis très surpris que personne n'a mentionné la conversion à une matrice encore ...

comparaison avec le dt.colon et dt.set fonctions définies par Ari B. Friedman, la conversion à une matrice a le meilleur temps de fonctionnement (légèrement plus rapide que dt.colon). Toutes les affectations à l'intérieur d'une matrice sont effectuées par référence, donc il n'y a pas de copie de mémoire inutile effectuée dans ce code.

CODE:

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) 

RÉSULTAT:

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 

Avantages de l'utilisation d'une matrice:

  • c'est la méthode la plus rapide jusqu'à présent
  • vous n'avez pas apprendre/utiliser les objets de données.table

Con d'utiliser une matrice:

  • vous ne pouvez gérer un type de données dans une matrice (en particulier, si vous aviez des types mixtes dans les colonnes de votre data.frame, ils seront tous convertis à caractère par la ligne: mat = as.matrix (dt) # conversion à matrice)
Questions connexes