Question Lecture rapide de très grandes tables sous forme de données


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

Je sais que lire dans un tableau 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 de convertir cela en une base de données semblent réduire 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))))

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


439
2017-11-13 07:53


origine


Réponses:


Une mise à jour, plusieurs années plus tard

Cette réponse est ancienne et R a évolué. Peaufiner read.table courir un peu plus vite a peu de bénéfice. Vos options sont:

  1. En utilisant fread dans data.table pour importer des données à partir de fichiers csv / délimités par des tabulations directement dans R. Voir la réponse de mnel.

  2. En utilisant read_table dans readr (sur CRAN à partir d'avril 2015). Cela fonctionne beaucoup comme fread au 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 de iotools fournit une troisième option pour lire rapidement les 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 depuis R dans un format binaire, ce qui est plus rapide.) read.csv.sql dans le sqldf paquet, comme décrit dans La réponse de JD Long, importe les données dans une base de données SQLite temporaire, puis les lit dans R. Voir aussi: RODBC paquet, et l'inverse dépend de la section de la DBI paquet page. MonetDB.R vous donne un type de données qui prétend être un cadre de données, mais qui est vraiment un MonetDB en dessous, ce qui augmente les performances. Importer des données avec ses monetdb.read.csv fonction. dplyr vous permet de travailler directement avec des données stockées dans plusieurs types de bases de données.

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


La réponse originale

Il y a quelques petites choses à essayer, que vous utilisiez read.table ou scan.

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

  2. Sois sûr 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 dans l'analyse.

Si aucune de ces choses ne fonctionne, utilisez l'un des paquets de profilage pour déterminer quelles lignes ralentissent les choses. Peut-être que vous pouvez écrire une version réduite de read.table basé sur les résultats.

L'autre alternative consiste à filtrer vos données avant de les lire dans R.

Ou, si le problème est que vous devez le lire régulièrement, utilisez ces méthodes pour lire les données une fois, puis enregistrez le bloc de données sous forme de blob binaire avec save  saveRDS, puis la prochaine fois vous pouvez le récupérer plus rapidement avec load  readRDS.


355
2017-11-13 10:35



Voici un exemple qui utilise fread de data.table 1.8.7

Les exemples proviennent de la page d'aide pour fread, avec les horaires 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 read.table

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

read.table optimisé

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

252
2018-02-25 01:07



Je n'ai pas vu cette question initialement et j'ai posé une question similaire quelques jours plus tard. Je vais prendre ma question précédente, mais je pensais que j'ajouterais une réponse ici pour expliquer comment j'ai utilisé sqldf() pour faire ça.

Il y a eu peu de discussion quant à la meilleure façon d'importer 2 Go ou plus de données texte dans un bloc de données R. Hier j'ai écrit un article de blog sur l'utilisation sqldf() pour importer les données dans SQLite en tant que zone de transit, puis le sucer de SQLite dans R. Cela fonctionne très bien pour moi. J'ai été capable de tirer 2 Go (3 colonnes, 40 mm lignes) de données en moins de 5 minutes. En revanche, le read.csv commande a couru toute la nuit et n'a jamais été terminée.

Voici mon code de test:

Configurez les données de test:

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

J'ai laissé la ligne suivante courir toute la nuit mais elle ne s'est jamais terminée:

system.time(big.df <- read.csv('bigdf.csv'))

243
2017-11-30 15:48



Étrangement, personne n'a répondu à la partie inférieure de la question pendant des années même si c'est un important - data.frames 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 "transformer" une liste en une trame de données:

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

Cela ne fait aucune copie des données, donc il est immédiat (contrairement à toutes les autres méthodes). Il suppose que vous avez déjà défini names() sur la liste en conséquence.

[Quant au chargement de grandes données dans R - personnellement, je les jette par colonne dans des fichiers binaires et utiliser readBin() - C'est de loin la méthode la plus rapide (autre que le mmapping) et elle n'est limitée que par la vitesse du disque. L'analyse des fichiers ASCII est intrinsèquement lente (même en C) par rapport aux données binaires.]


69
2017-12-20 04:01



C'était auparavant demandé sur R-Help, donc ça vaut le coup d'être revu.

Une suggestion était d'utiliser readChar() et ensuite faire de la manipulation de chaîne sur le résultat avec strsplit() et substr(). Vous pouvez voir 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 vouloir jeter un oeil à la HadoopStreaming paquet. Ce utilise Hadoop, qui est un cadre MapReduce conçu pour traiter de grands ensembles de données. Pour cela, vous utiliserez la fonction hsTableReader. Ceci 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 casser l'importation de données en morceaux. Vous pourriez même aller jusqu'à utiliser l'un des frameworks parallèles (par exemple neige) et exécuter l'importation de données en parallèle en segmentant le fichier, mais très probablement pour les fichiers volumineux qui ne vous aideront pas, car vous rencontrerez des contraintes de mémoire. C'est pourquoi map-reduce est une meilleure approche.


30
2017-11-13 15:18



Des points supplémentaires mineurs à mentionner. Si vous avez un très gros fichier, 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 soit 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
2017-11-28 17:20



Souvent, je pense que c'est une bonne pratique de conserver des bases de données plus volumineuses dans une base de données (par exemple Postgres). Je n'utilise rien de trop grand que (nrow * ncol) ncell = 10M, ce qui est assez petit; mais je trouve souvent que je veux que R crée et conserve des graphes intensifs en mémoire uniquement lorsque je demande à 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 disparaîtront. Mais l'attrait d'utiliser une base de données pour conserver les données et ensuite utiliser la mémoire de R pour les résultats de la requête et les graphiques qui en résultent peut encore être utile. 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 lorsque vous rallumez votre ordinateur portable.

(2) Il est vrai que R peut faire beaucoup plus d'opérations statistiques et graphiques 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
2017-08-22 16:13



Au lieu de la table de lecture conventionnelle, je pense que le fread est une fonction plus rapide. Si vous spécifiez des attributs supplémentaires, par exemple sélectionner uniquement les colonnes requises, en spécifiant colclasses et string comme facteurs, vous réduirez le délai d'importation du 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
2018-04-18 07:22