569 Stimmen

Schnelles Einlesen sehr großer Tabellen als Dataframes

Ich habe sehr große Tabellen (30 Millionen Zeilen), die ich als Dataframes in R laden möchte. read.table() hat viele praktische Funktionen, aber es scheint, dass die Implementierung eine Menge Logik enthält, die alles verlangsamen würde. In meinem Fall gehe ich davon aus, dass ich die Spaltentypen im Voraus kenne, dass die Tabelle keine Spaltenüberschriften oder Zeilennamen enthält und dass es keine pathologischen Zeichen gibt, über die ich mir Sorgen machen müsste.

Ich weiß, dass das Einlesen einer Tabelle als Liste mit scan() kann recht schnell sein, z.B.:

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

Aber einige meiner Versuche, dies in einen Datenrahmen zu konvertieren, scheinen die Leistung des oben genannten um den Faktor 6 zu verringern:

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

Gibt es eine bessere Möglichkeit, dies zu tun? Oder möglicherweise eine ganz andere Herangehensweise an das Problem?

501voto

Richie Cotton Punkte 112708

Ein Update, mehrere Jahre später

Diese Antwort ist alt, und R hat sich weiterentwickelt. Optimierung von read.table ein bisschen schneller zu laufen, hat kaum einen Nutzen. Ihre Optionen sind:

  1. 使用方法 vroom aus dem tidyverse-Paket vroom zum Importieren von Daten aus csv/tab-getrennten Dateien direkt in ein R-Tibble. Siehe Hectors Antwort .

  2. 使用方法 fread en data.table für den Import von Daten aus csv/tab-getrennten Dateien direkt in R. Siehe Antwort von mnel .

  3. 使用方法 read_table en readr (auf CRAN ab April 2015). Dies funktioniert ähnlich wie fread oben. Die readme im Link erklärt den Unterschied zwischen den beiden Funktionen ( readr behauptet derzeit, "1,5-2x langsamer" zu sein als data.table::fread ).

  4. read.csv.raw de iotools bietet eine dritte Möglichkeit zum schnellen Lesen von CSV-Dateien.

  5. Versuchen Sie, so viele Daten wie möglich in Datenbanken und nicht in flachen Dateien zu speichern. (Abgesehen davon, dass es sich um ein besseres dauerhaftes Speichermedium handelt, werden die Daten in einem binären Format an und von R übermittelt, was schneller ist). read.csv.sql en el sqldf Paket, wie beschrieben in Die Antwort von JD Long importiert Daten in eine temporäre SQLite-Datenbank und liest sie dann in R ein. Siehe auch: die RODBC Paket, und der Abschnitt "reverse depends" des DBI Paket Seite. MonetDB.R gibt Ihnen einen Datentyp, der vorgibt, ein Datenrahmen zu sein, aber in Wirklichkeit eine MonetDB darunter ist, was die Leistung erhöht. Importieren Sie Daten mit seinem monetdb.read.csv Funktion. dplyr ermöglicht es Ihnen, direkt mit Daten zu arbeiten, die in verschiedenen Arten von Datenbanken gespeichert sind.

  6. Die Speicherung von Daten in Binärformaten kann auch zur Verbesserung der Leistung nützlich sein. Verwenden Sie saveRDS / readRDS (siehe unten), die h5 o rhdf5 Pakete für das HDF5-Format, oder write_fst / read_fst von der fst Paket.


Die ursprüngliche Antwort

Unabhängig davon, ob Sie read.table oder scan verwenden, gibt es ein paar einfache Möglichkeiten, die Sie ausprobieren können.

  1. Set nrows = die Anzahl der Datensätze in Ihren Daten ( nmax en scan ).

  2. Stellen Sie sicher, dass comment.char="" um die Interpretation von Kommentaren auszuschalten.

  3. Definieren Sie die Klassen der einzelnen Spalten explizit mit colClasses en read.table .

  4. Einstellung multi.line=FALSE kann auch die Leistung beim Scannen verbessern.

Wenn nichts davon funktioniert, dann verwenden Sie eine der Profilierungspakete um festzustellen, welche Leitungen die Arbeit verlangsamen. Vielleicht können Sie eine gekürzte Version von read.table auf der Grundlage der Ergebnisse.

Die andere Alternative ist das Filtern Ihrer Daten, bevor Sie sie in R einlesen.

Oder, wenn das Problem darin besteht, dass Sie die Daten regelmäßig einlesen müssen, dann verwenden Sie diese Methoden, um die Daten einmal einzulesen und dann den Datenrahmen als binären Blob zu speichern mit save saveRDS dann können Sie sie beim nächsten Mal schneller abrufen mit load readRDS .

306voto

mnel Punkte 109980

Hier ist ein Beispiel, das Folgendes verwendet fread de data.table 1.8.7

Die Beispiele stammen von der Hilfeseite zu fread mit den Timings auf meinem 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-Lesetabelle

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

optimierte read.table

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

Zusammengefasst:

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

261voto

JD Long Punkte 57096

Ich habe diese Frage zunächst nicht gesehen und habe ein paar Tage später eine ähnliche Frage gestellt. Ich werde meine vorherige Frage löschen, aber ich dachte, ich würde hier eine Antwort hinzufügen, um zu erklären, wie ich sqldf() um dies zu tun.

Es gab schon ein wenig Diskussion wie man am besten 2 GB oder mehr an Textdaten in einen R-Datenrahmen importiert. Gestern schrieb ich ein Blog-Eintrag über die Verwendung sqldf() um die Daten in SQLite als Zwischenlager zu importieren und sie dann aus SQLite in R zu saugen. Das funktioniert bei mir sehr gut. Ich konnte 2 GB (3 Spalten, 40 mm Zeilen) an Daten in weniger als 5 Minuten einlesen. Im Gegensatz dazu ist die read.csv Der Befehl lief die ganze Nacht und wurde nie abgeschlossen.

Hier ist mein Testcode:

Legen Sie die Testdaten fest:

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

Ich habe R neu gestartet, bevor ich die folgende Importroutine ausgeführt habe:

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

Ich habe die folgende Zeile die ganze Nacht laufen lassen, aber sie wurde nie abgeschlossen:

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

81voto

Simon Urbanek Punkte 13512

Seltsamerweise hat jahrelang niemand auf den unteren Teil der Frage geantwortet, obwohl dies eine wichtige Frage ist. data.frame s sind einfach Listen mit den richtigen Attributen. Wenn Sie also große Daten haben, sollten Sie nicht as.data.frame oder ähnlich für eine Liste. Es ist viel schneller, eine Liste einfach an Ort und Stelle in einen Datenrahmen zu "verwandeln":

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

Dabei wird keine Kopie der Daten erstellt, so dass sie sofort verfügbar sind (im Gegensatz zu allen anderen Methoden). Sie geht davon aus, dass Sie bereits names() entsprechend auf der Liste.

[Was das Laden großer Daten in R angeht - ich persönlich lade sie spaltenweise in Binärdateien und verwende readBin() - das ist bei weitem die schnellste Methode (außer mmapping) und wird nur durch die Festplattengeschwindigkeit begrenzt. Das Parsen von ASCII-Dateien ist von Natur aus langsam (sogar in C) im Vergleich zu Binärdaten].

33voto

Shane Punkte 95376

Dies war zuvor gefragt am R-Help Das sollte also überprüft werden.

Ein Vorschlag war die Verwendung von readChar() und bearbeiten Sie dann das Ergebnis mit strsplit() y substr() . Wie Sie sehen, ist die Logik in readChar viel weniger umfangreich als in read.table.

Ich weiß nicht, ob die Erinnerung hier eine Rolle spielt, aber Sie könnten auch wollen einen Blick auf die HadoopStreaming Paket . Diese verwendet Hadoop ein MapReduce-Framework, das für den Umgang mit großen Datensätzen konzipiert ist. Hierfür würden Sie die Funktion hsTableReader verwenden. Dies ist ein Beispiel (aber es hat eine Lernkurve, um Hadoop zu lernen):

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)

Der Grundgedanke dabei ist, den Datenimport in einzelne Abschnitte zu unterteilen. Sie könnten sogar so weit gehen, eines der parallelen Frameworks (z. B. Snow) zu verwenden und den Datenimport parallel auszuführen, indem Sie die Datei segmentieren, aber höchstwahrscheinlich wird das bei großen Datensätzen nicht helfen, da Sie auf Speicherbeschränkungen stoßen werden, weshalb Map-Reduce ein besserer Ansatz ist.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X