Lecture rapide de très grandes tables comme des images de données

j'ai de très grandes tables (30 millions de lignes) que je voudrais charger comme un dataframes dans R. read.table() a beaucoup de fonctionnalités pratiques, mais il semble qu'il ya beaucoup de logique dans la mise en œuvre qui ralentirait les choses. Dans mon cas, je suppose que je connais les types des colonnes à l'avance, la table ne contient aucun en-tête de colonne ou noms de ligne, et n'a pas de caractères pathologiques dont je dois me soucier.

je sais que la lecture dans une table comme une liste en utilisant scan() peut être assez rapide, par exemple:

datalist <- scan('myfile',sep='t',list(url='',popularity=0,mintime=0,maxtime=0)))

mais certaines de mes tentatives pour convertir ceci en une base de données semblent diminuer la performance de ce qui précède par un facteur de 6:

df <- as.data.frame(scan('myfile',sep='t',list(url='',popularity=0,mintime=0,maxtime=0))))

y a-t-il une meilleure façon de faire cela? Ou très probablement complètement approche différente du problème?

444
demandé sur Uwe Keim 2009-11-13 10:53:33

8 réponses

une mise à jour, plusieurs années plus tard

cette réponse est ancienne, et R est passé à autre chose. Peaufiner read.table pour courir un peu plus vite a peu d'avantage précieux. Vos options sont:

  1. utilisant fread dans data.table pour l'importation de données à partir de fichiers csv / tab-delimited directement dans R. Voir réponse de mnel .

  2. utilisant read_table en readr (on CRAN from April 2015). Cela fonctionne un peu comme fread ci-dessus. Le readme dans le lien explique la différence entre les deux fonctions ( readr prétend actuellement être "1,5-2x plus lent" que data.table::fread ).

  3. read.csv.raw à partir de iotools fournit une troisième option pour la lecture de fichiers CSV.

  4. essayer de stocker autant de données que vous pouvez dans les bases de données plutôt que des fichiers plats. (En plus d'être un meilleur support de stockage permanent, les données sont transmises à et à partir de R dans un format binaire, ce qui est plus rapide.) read.csv.sql dans le colis sqldf , comme décrit dans JD Longue réponse , l'importation de données dans une temporaire base de données SQLite et puis, il lit dans R. Voir aussi: le RODBC , et l'inverse dépend de la section DBI package de la page. MonetDB.R " vous donne un type de données qui prétend être une base de données, mais qui est en fait un MonetDB en dessous, augmentant les performances. Données d'importation avec ses monetdb.read.csv de la fonction. dplyr vous permet de travailler directement avec les données stockées dans plusieurs types de base de données.

  5. stocker des données dans des formats binaires peut également être utile pour améliorer les performances. Utilisez saveRDS / readRDS (voir ci-dessous), les paquets h5 ou rhdf5 pour le format HDF5, ou write_fst / read_fst à partir du fst paquet.


la réponse originale

il y a quelques choses simples à essayer, que vous utilisiez read.de table ou de numérisation.

  1. Set nrows = le nombre d'enregistrements dans vos données ( nmax dans scan ).

  2. s'assurer que comment.char="" pour désactiver l'interprétation des commentaires.

  3. définir explicitement les classes de chaque colonne en utilisant colClasses dans read.table .

  4. réglage multi.line=FALSE peut également améliorer les performances en scan.

si aucune de ces choses ne fonctionne, alors utiliser l'un des paquets de profilage pour déterminez quelles lignes ralentissent les choses. Peut-être Pouvez-vous écrire une version réduite de read.table basée sur les résultats.

l'autre alternative est de filtrer vos données avant de les lire dans R.

ou, si le problème est que vous devez le lire dans régulièrement, puis utiliser ces méthodes pour lire les données en une seule fois, puis Enregistrer la base de données comme un blob binaire avec save saveRDS , la prochaine fois, vous pourrez le récupérer plus vite avec load readRDS .

365
répondu Richie Cotton 2018-02-12 09:35:30

voici un exemple qui utilise fread de data.table 1.8.7

les exemples viennent de la page d'aide à fread , avec les minuteries sur mon Windows XP Core 2 duo E8400.

library(data.table)
# Demo speedup
n=1e6
DT = data.table( a=sample(1:1000,n,replace=TRUE),
                 b=sample(1:1000,n,replace=TRUE),
                 c=rnorm(n),
                 d=sample(c("foo","bar","baz","qux","quux"),n,replace=TRUE),
                 e=rnorm(n),
                 f=sample(1:1000,n,replace=TRUE) )
DT[2,b:=NA_integer_]
DT[4,c:=NA_real_]
DT[3,d:=NA_character_]
DT[5,d:=""]
DT[2,e:=+Inf]
DT[3,e:=-Inf]

standard lire.tableau 1519150920"
write.table(DT,"test.csv",sep=",",row.names=FALSE,quote=FALSE)
cat("File size (MB):",round(file.info("test.csv")$size/1024^2),"\n")    
## File size (MB): 51 

system.time(DF1 <- read.csv("test.csv",stringsAsFactors=FALSE))        
##    user  system elapsed 
##   24.71    0.15   25.42
# second run will be faster
system.time(DF1 <- read.csv("test.csv",stringsAsFactors=FALSE))        
##    user  system elapsed 
##   17.85    0.07   17.98

lecture optimisée.tableau 1519150920"
system.time(DF2 <- read.table("test.csv",header=TRUE,sep=",",quote="",  
                          stringsAsFactors=FALSE,comment.char="",nrows=n,                   
                          colClasses=c("integer","integer","numeric",                        
                                       "character","numeric","integer")))


##    user  system elapsed 
##   10.20    0.03   10.32

fread

require(data.table)
system.time(DT <- fread("test.csv"))                                  
 ##    user  system elapsed 
##    3.12    0.01    3.22

sqldf

require(sqldf)

system.time(SQLDF <- read.csv.sql("test.csv",dbname=NULL))             

##    user  system elapsed 
##   12.49    0.09   12.69

# sqldf as on SO

f <- file("test.csv")
system.time(SQLf <- sqldf("select * from f", dbname = tempfile(), file.format = list(header = T, row.names = F)))

##    user  system elapsed 
##   10.21    0.47   10.73

ff / ffdf

 require(ff)

 system.time(FFDF <- read.csv.ffdf(file="test.csv",nrows=n))   
 ##    user  system elapsed 
 ##   10.85    0.10   10.99

en résumé:

##    user  system elapsed  Method
##   24.71    0.15   25.42  read.csv (first time)
##   17.85    0.07   17.98  read.csv (second time)
##   10.20    0.03   10.32  Optimized read.table
##    3.12    0.01    3.22  fread
##   12.49    0.09   12.69  sqldf
##   10.21    0.47   10.73  sqldf on SO
##   10.85    0.10   10.99  ffdf
256
répondu mnel 2013-02-25 01:56:36

je n'ai pas vu cette question initialement et posé une question similaire, quelques jours plus tard. Je vais retirer ma question précédente, mais j'ai pensé ajouter une réponse ici pour expliquer comment j'ai utilisé sqldf() pour faire ceci.

il y a eu petite discussion quant à la meilleure façon d'importer 2Go ou plus de données de texte dans un cadre de données R. Hier, j'ai écrit un post de blog à propos de l'utilisation sqldf() pour importer le les données dans SQLite comme une zone de transit, et puis le sucking de SQLite dans R. cela fonctionne vraiment bien pour moi. J'ai pu extraire 2 Go (3 colonnes, lignes 40mm) de données en moins de 5 minutes. Par contre, la commande read.csv a été exécutée toute la nuit et n'a jamais été exécutée.

Voici mon code de test:

configurer les données d'essai:

bigdf <- data.frame(dim=sample(letters, replace=T, 4e7), fact1=rnorm(4e7), fact2=rnorm(4e7, 20, 50))
write.csv(bigdf, 'bigdf.csv', quote = F)

j'ai redémarré R avant d'exécuter la routine d'importation suivante:

library(sqldf)
f <- file("bigdf.csv")
system.time(bigdf <- sqldf("select * from f", dbname = tempfile(), file.format = list(header = T, row.names = F)))

I laissez la ligne suivante fonctionner toute la nuit, mais il n'a jamais terminé:

system.time(big.df <- read.csv('bigdf.csv'))
243
répondu JD Long 2011-11-21 21:36:20

étrangement, personne n'a répondu à la partie inférieure de la question pendant des années, même s'il s'agit d'une question importante -- data.frame s sont simplement des listes avec les bons attributs, donc si vous avez de grandes données, vous ne voulez pas utiliser as.data.frame ou similaire pour une liste. Il est beaucoup plus rapide de simplement "transformer" une liste en un cadre de données en place:

attr(df, "row.names") <- .set_row_names(length(df[[1]]))
class(df) <- "data.frame"

cela ne fait pas de copie des données de sorte qu'il est immédiat (contrairement à toutes les autres méthodes). Il suppose que vous avez déjà mis names() sur la liste en conséquence.

[comme pour le chargement de grandes données dans R -- personnellement, je les décharge par colonne dans des fichiers binaires et utilise readBin() - qui est de loin la méthode la plus rapide (autre que mmapping) et n'est limité que par la vitesse du disque. L'analyse des fichiers ASCII est en soi lente (même en C) par rapport aux données binaires.]

70
répondu Simon Urbanek 2013-02-24 21:42:54

c'était auparavant demandé sur R-Help 151980920" , donc cela vaut la peine d'examiner.

une suggestion était d'utiliser readChar() et de faire ensuite la manipulation de chaîne sur le résultat avec strsplit() et substr() . Vous pouvez voir que la logique impliquée dans readChar est beaucoup moins que read.table.

Je ne sais pas si la mémoire est un problème ici, mais vous pourriez aussi voulez jeter un oeil à la HadoopStreaming package . Ce utilise Hadoop , qui est un MapReduce framework conçu pour traiter de grands ensembles de données. Pour cela, vous utiliserez la fonction hsTableReader. C'est un exemple (mais il a une courbe d'apprentissage pour apprendre Hadoop):

str <- "key1\t3.9\nkey1\t8.9\nkey1\t1.2\nkey1\t3.9\nkey1\t8.9\nkey1\t1.2\nkey2\t9.9\nkey2\"
cat(str)
cols = list(key='',val=0)
con <- textConnection(str, open = "r")
hsTableReader(con,cols,chunkSize=6,FUN=print,ignoreKey=TRUE)
close(con)

L'idée de base ici est de briser l'importation de données en morceaux. On pourrait même aller jusqu'à utiliser l'un des cadres parallèles (par exemple, à la neige) et exécuter le importer des données en parallèle en segmentant le fichier, mais très probablement pour de grands ensembles de données qui ne vous aideront pas car vous rencontrerez des contraintes de mémoire, c'est pourquoi map-reduce est une meilleure approche.

30
répondu Shane 2011-10-11 09:34:14

un petit point supplémentaire à mentionner. Si vous avez un fichier très volumineux, vous pouvez à la volée calculer le nombre de lignes (si aucun en-tête) en utilisant (où bedGraph est le nom de votre fichier dans votre répertoire de travail):

>numRow=as.integer(system(paste("wc -l", bedGraph, "| sed 's/[^0-9.]*\([0-9.]*\).*/\1/'"), intern=T))

vous pouvez alors l'utiliser dans read.csv , read.table ...

>system.time((BG=read.table(bedGraph, nrows=numRow, col.names=c('chr', 'start', 'end', 'score'),colClasses=c('character', rep('integer',3)))))
   user  system elapsed 
 25.877   0.887  26.752 
>object.size(BG)
203949432 bytes
5
répondu Stephen Henderson 2013-11-28 17:20:55

souvent, je pense que c'est juste une bonne pratique de garder de plus grandes bases de données à l'intérieur d'une base de données (par exemple Postgres). Je n'utilise pas grand-chose de plus grand que (nrow * ncol) ncell = 10M, ce qui est assez petit, mais je trouve souvent que je veux créer et tenir des graphiques intensifs en mémoire seulement pendant que je recherche à partir de plusieurs bases de données. Dans le futur des ordinateurs portables de 32 Go, certains de ces types de problèmes de mémoire vont disparaître. Mais l'attrait d'utiliser une base de données pour tenir les données et ensuite l'utilisation de la mémoire de R pour le les résultats des requêtes et les graphiques qui en résultent peuvent encore être utiles. Certains avantages sont:

(1) Les données restent chargées dans votre base de données. Vous vous reconnectez simplement dans pgadmin aux bases de données que vous voulez quand vous rallumez votre ordinateur portable.

(2) Il est vrai que R peut effectuer beaucoup plus d'opérations de représentation graphique et statistique que SQL. Mais je pense que SQL est mieux conçu pour interroger de grandes quantités de données que R.

# Looking at Voter/Registrant Age by Decade

library(RPostgreSQL);library(lattice)

con <- dbConnect(PostgreSQL(), user= "postgres", password="password",
                 port="2345", host="localhost", dbname="WC2014_08_01_2014")

Decade_BD_1980_42 <- dbGetQuery(con,"Select PrecinctID,Count(PrecinctID),extract(DECADE from Birthdate) from voterdb where extract(DECADE from Birthdate)::numeric > 198 and PrecinctID in (Select * from LD42) Group By PrecinctID,date_part Order by Count DESC;")

Decade_RD_1980_42 <- dbGetQuery(con,"Select PrecinctID,Count(PrecinctID),extract(DECADE from RegistrationDate) from voterdb where extract(DECADE from RegistrationDate)::numeric > 198 and PrecinctID in (Select * from LD42) Group By PrecinctID,date_part Order by Count DESC;")

with(Decade_BD_1980_42,(barchart(~count | as.factor(precinctid))));
mtext("42LD Birthdays later than 1980 by Precinct",side=1,line=0)

with(Decade_RD_1980_42,(barchart(~count | as.factor(precinctid))));
mtext("42LD Registration Dates later than 1980 by Precinct",side=1,line=0)
4
répondu rferrisx 2014-08-24 15:54:25

au lieu de la mention conventionnelle.table I feel fread est une fonction plus rapide. Spécifier des attributs supplémentaires comme sélectionner seulement les colonnes requises, spécifier colclasses et string comme facteurs réduira le temps d'importer le fichier.

data_frame <- fread("filename.csv",sep=",",header=FALSE,stringsAsFactors=FALSE,select=c(1,4,5,6,7),colClasses=c("as.numeric","as.character","as.numeric","as.Date","as.Factor"))
0
répondu Aayush Agrawal 2015-04-18 07:22:01