Schnell Lesen sehr große Tabellen als Datenrahmen
Frage
Ich habe sehr große Tabellen (30 Millionen Zeilen), dass ich zu Last als Datenrahmen in R. read.table()
möchte hat viele praktische Funktionen, aber es scheint, wie es eine Menge Logik in der Umsetzung ist, dass die Dinge verlangsamen würde Nieder. In meinem Fall, ich nehme ich die Typen der Spalten im Voraus weiß, hat die Tabelle keine Spaltenüberschriften oder Zeilennamen zu enthalten, und habe keine pathologischen Zeichen, dass ich Sorge habe über.
Ich weiß, dass als eine Liste mit scan()
in einer Tabelle zu lesen ziemlich schnell sein kann, z.
datalist <- scan('myfile',sep='\t',list(url='',popularity=0,mintime=0,maxtime=0)))
Aber einige meiner Versuche, dies zu einem Datenrahmen zu konvertieren erscheinen die Leistung der oben um einen Faktor von 6 zu verringern:
df <- as.data.frame(scan('myfile',sep='\t',list(url='',popularity=0,mintime=0,maxtime=0))))
Gibt es einen besseren Weg, dies zu tun? Oder womöglich ganz andere Herangehensweise an das Problem?
Lösung
Ein Update, einige Jahre später
Diese Antwort ist alt, und R hat sich weiterentwickelt. Tweaking read.table
ein bisschen schneller zu laufen hat sehr wenig Nutzen. Ihre Optionen sind:
-
Mit
fread
indata.table
für Daten aus csv / tabstoppgetrennten Dateien direkt in R. Siehe MNEL Antwort . -
Mit
read_table
inreadr
(auf CRAN ab April 2015). Dies funktioniert ähnlich wiefread
oben. Das readme in dem Link, um die Differenz zwischen den beiden Funktionen erklärt (readr
derzeit behauptet "1.5-2x langsamer" alsdata.table::fread
zu sein). -
read.csv.raw
voniotools
zum schnellen Lesen von CSV-Dateien. -
Der Versuch, zu speichern, so viele Daten wie Sie in Datenbanken können anstatt flachen Dateien. (Sowie ein besseres Dauerspeichermedium zu sein, wird die Daten in einem binären Format zu und von R geleitet, die schneller ist.)
read.csv.sql
in dersqldf
Paket wie beschrieben, in Antwort JD Longs, liest sie importiert Daten in eine temporäre SQLite-Datenbank und dann auch in R. Siehe auch:RODBC
Paket, und umgekehrt Abschnitt der abhängigDBI
Paket Seite.MonetDB.R
gibt Ihnen einen Datentyp, der vorgibt, eine zu sein aber Datenrahmen ist wirklich ein MonetDB unterhalb Dies steigert die Leistung. Importieren von Daten mit dermonetdb.read.csv
Funktion.dplyr
können Sie direkt in verschiedene Arten gespeichert mit Daten arbeiten von Datenbank. -
Das Speichern von Daten in binären Formaten können auch zur Verbesserung der Leistung von Nutzen sein. Verwenden
saveRDS
/readRDS
(siehe unten), dieh5
oderrhdf5
Pakete für HDF5 Format oderwrite_fst
/read_fst
aus derfst
Paket.
Die ursprüngliche Antwort
Es gibt ein paar einfache Dinge, um zu versuchen, ob Sie read.table oder Scan verwenden.
-
Set
nrows
= die Anzahl der Datensätze in Ihren Daten (nmax
inscan
). -
Stellen Sie sicher, dass
comment.char=""
Interpretation der Kommentare auszuschalten. -
definiert Explizit die Klassen jeden Spalt
colClasses
inread.table
verwendet wird. -
multi.line=FALSE
Einstellung kann auch die Leistung in Scan verbessern.
Wenn keine dieser Sache arbeiten, dann verwenden Sie einen der Profilierungs Pakete um zu bestimmen, welche Zeilen Dinge nach unten verlangsamen. Vielleicht können Sie eine abgespeckte Version von read.table
schreiben basierend auf den Ergebnissen.
Die andere Alternative ist die Filterung Ihrer Daten, bevor Sie es in R lesen.
Oder, wenn das Problem ist, dass man es in regelmäßig zu lesen, dann diese Methoden verwenden, um die Daten in einmal zu lesen, dann speichern Sie den Datenrahmen als binäres Blob mit save
saveRDS
, dann sind Sie beim nächsten Mal schneller abrufen können mit load
readRDS
.
Andere Tipps
Hier ist ein Beispiel, das fread
von data.table
1.8.7
Die Beispiele stammen aus 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 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
optimiertem 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
Zusammenfassung:
## 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
Ich habe diese Frage nicht von Anfang an und fragte eine ähnliche Frage ein paar Tage später. Ich werde meine vorherige Frage nach unten nehmen, aber ich dachte, dass ich hier eine Antwort hinzufügen würde zu erklären, wie ich sqldf()
verwendet, dies zu tun.
Es gab wenig Diskussion zu der beste Weg, 2 GB oder mehr von Textdaten in einen R-Datenrahmen zu importieren. Gestern schrieb ich eine Blog-Post über sqldf()
unter Verwendung der Daten in SQLite als Bereitstellungsbereich zu importieren, und saugen sie dann von SQLite in R. Das funktioniert wirklich gut für mich. Ich konnte in 2GB (3 Spalten, 40mm Reihen) von Daten in <5 Minuten ziehen. Im Gegensatz dazu lief der read.csv
Befehl die ganze Nacht und nie vollendet.
Hier ist mein Test-Code:
die Testdaten ein:
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 neu gestartet R, bevor Sie die folgenden Import-Routine ausgeführt wird:
library(sqldf)
f <- file("bigdf.csv")
system.time(bigdf <- sqldf("select * from f", dbname = tempfile(), file.format = list(header = T, row.names = F)))
Ich lasse die folgende Zeile die ganze Nacht laufen, aber es nie beendet:
system.time(big.df <- read.csv('bigdf.csv'))
Seltsam, antwortete niemand den unteren Teil der Frage seit Jahren, obwohl dies ein sehr wichtiger ist - data.frame
s sind einfach Listen mit den richtigen Eigenschaften, so dass, wenn Sie große Daten haben Sie nicht wollen, zu verwenden as.data.frame
oder ähnlich eine Liste. Es ist viel schneller zu einfach „drehen“, um eine Liste in einen Datenrahmen an Ort und Stelle:
attr(df, "row.names") <- .set_row_names(length(df[[1]]))
class(df) <- "data.frame"
Das macht keine Kopie der Daten, so dass es sofort ist (im Gegensatz zu allen anderen Methoden). Er geht davon aus, dass Sie bereits names()
auf der Liste entsprechend eingestellt haben.
[Wie für große Daten in R Laden - ich persönlich Dump sie durch Spalte in binäre Dateien und die Verwendung readBin()
- das ist bei weitem die schnellste Methode (außer mmapping) und wird nur durch die Plattengeschwindigkeit begrenzt. ASCII-Dateien Parsen ist von Natur aus langsam (auch in C) im Vergleich zu binären Daten.]
Dies war bisher fragte auf R-Help , so dass der wert zu überprüfen.
Ein Vorschlag war, es zu verwenden readChar()
und dann String-Manipulation zu tun auf dem Ergebnis mit strsplit()
und substr()
. Sie können die Logik in readChar beteiligt sehen weniger viel als read.table.
Ich weiß nicht, wenn der Speicher ein Problem hier ist, aber man könnte auch nutzt Hadoop , die ein MapReduce Rahmen für den Umgang mit großen Datenmengen ausgelegt ist. Dazu würden Sie die hsTableReader-Funktion verwenden. Dies ist ein Beispiel (aber es hat eine Lernkurve Hadoop 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)
Die Grundidee ist es, das Datenimport in Stücke zu brechen. Man könnte sogar so weit gehen, als einen der parallelen Frameworks zu verwenden (zB Schnee) und den Datenimport in parallel ausführen, indem Sie die Datei segmentieren, aber wahrscheinlich für großen Datensätze, die nicht helfen, da Sie in den Speichern Einschränkungen ausgeführt werden sollen, weshalb Karten reduzieren, ist ein besserer Ansatz.
Eine geringfügige zusätzliche Punkte erwähnenswert. Wenn Sie eine sehr große Datei, die Sie die Anzahl der Zeilen im Fluge berechnen können (wenn kein Header) mit (wo bedGraph
der Name der Datei in Ihrem Arbeitsverzeichnis ist):
>numRow=as.integer(system(paste("wc -l", bedGraph, "| sed 's/[^0-9.]*\\([0-9.]*\\).*/\\1/'"), intern=T))
Sie können dann verwenden, entweder in 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
Eine Alternative ist das vroom
Paket zu verwenden. Jetzt auf CRAN.
vroom
nicht die gesamte Datei laden, es Indizes, wo jeder Datensatz befindet, und wird später zu lesen, wenn Sie es verwenden.
Sie zahlen nur für das, was Sie verwenden.
Siehe Einführung in vroom , Lassen Sie sich mit vroom und die vroom Benchmarks .
Die grundlegende Übersicht ist, dass die anfängliche Lesen einer großen Datei, wird viel schneller sein, und nachfolgende Änderungen an den Daten können geringfügig langsamer sein. Also je nachdem, was Ihre Anwendung ist, könnte es die beste Option sein.
Sehen Sie ein vereinfachtes Beispiel von vroom Benchmarks unten, die wichtigsten Teile sind die super schnelle Lesezeiten zu sehen, aber leicht Sämann Operationen wie Summe etc ..
package read print sample filter aggregate total
read.delim 1m 21.5s 1ms 315ms 764ms 1m 22.6s
readr 33.1s 90ms 2ms 202ms 825ms 34.2s
data.table 15.7s 13ms 1ms 129ms 394ms 16.3s
vroom (altrep) dplyr 1.7s 89ms 1.7s 1.3s 1.9s 6.7s
Oft Ich denke, es ist nur gute Praxis zu halten größere Datenbanken in einer Datenbank (z Postgres). Ich benutze nicht etwas zu viel größer als (nrow * ncol) ncell = 10 M, die ziemlich klein ist; aber ich finde oft möchte ich R erstellen und zu halten, speicherintensive Diagramme nur während ich Abfrage aus mehreren Datenbanken. In Zukunft von 32 GB Laptops, verschwinden einige dieser Arten von Gedächtnisstörungen. Aber der Reiz, eine Datenbank für die Verwendung der Daten zu halten und dann Speicher mit R für die resultierenden Abfrageergebnisse und Grafiken kann noch nützlich sein. Einige Vorteile sind:
(1) Die Daten bleiben in der Datenbank geladen. Sie verbinden einfach in pgAdmin auf die Datenbanken, die Sie wünschen, wenn Sie Ihren Laptop zurück einschalten.
(2) Es ist wahr, R viele raffinierte statistische und Grafikoperationen als SQL tun können. Aber ich denke, SQL besser zu Abfrage von großen Datenmengen als R ausgelegt ist.
# 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)
Anstelle des herkömmlichen read.table I Feel fread ist eine schnellere Funktion. Angeben zusätzliche Attribute wie nur die gewünschten Spalten auswählen, unter Angabe colclasses und String als Faktoren, die die Zeit in Anspruch nehmen zu importieren, um die Datei zu reduzieren.
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"))