Schnelles Lesen sehr großer Tabellen als Datenrahmen


503

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

Ich weiß, dass das Lesen einer Tabelle als Liste mit scan()sehr schnell sein kann, z.

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

Einige meiner Versuche, dies in einen Datenrahmen umzuwandeln, scheinen jedoch 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 einen besseren Weg, dies zu tun? Oder ganz andere Herangehensweise an das Problem?

Antworten:


425

Ein Update, einige Jahre später

Diese Antwort ist alt und R ist weitergegangen. Das Optimieren read.table, um ein bisschen schneller zu laufen, hat nur einen geringen Nutzen. Ihre Optionen sind:

  1. Verwendung vroomaus dem tidyverse-Paket vroomzum Importieren von Daten aus durch CSV / Tabulatoren getrennten Dateien direkt in ein R-Tibble.

  2. Verwenden von freadin data.tablezum Importieren von Daten aus durch CSV / Tabulatoren getrennten Dateien direkt in R. Siehe die Antwort von mnel .

  3. Verwendung read_tablein readr(auf CRAN ab April 2015). Dies funktioniert ähnlich wie freadoben. Die Readme-Datei im Link erklärt den Unterschied zwischen den beiden Funktionen ( readrbehauptet derzeit, "1,5-2x langsamer" zu sein als data.table::fread).

  4. read.csv.rawfrom iotoolsbietet eine dritte Option zum schnellen Lesen von CSV-Dateien.

  5. Versuchen Sie, so viele Daten wie möglich in Datenbanken anstatt in Flatfiles zu speichern. (Daten sind nicht nur ein besseres permanentes Speichermedium, sondern werden auch in einem schnelleren Binärformat an und von R übergeben.) read.csv.sqlIm sqldfPaket werden, wie in der Antwort von JD Long beschrieben , Daten in eine temporäre SQLite-Datenbank importiert und dann gelesen Siehe auch: das RODBCPaket, und die Rückseite hängt vom Abschnitt der DBIPaketseite ab . MonetDB.Rgibt Ihnen einen Datentyp, der vorgibt, ein Datenrahmen zu sein, aber tatsächlich eine MonetDB darunter ist, was die Leistung erhöht. Daten mit seiner monetdb.read.csvFunktion importieren. dplyrErmöglicht die direkte Arbeit mit Daten, die in verschiedenen Datenbanktypen gespeichert sind.

  6. Das Speichern von Daten in Binärformaten kann auch zur Verbesserung der Leistung hilfreich sein. Verwenden Sie saveRDS/ readRDS(siehe unten), die h5oder rhdf5Pakete für das HDF5-Format oder write_fst/ read_fstaus dem fstPaket.


Die ursprüngliche Antwort

Es gibt ein paar einfache Dinge zu versuchen, ob Sie read.table oder scan verwenden.

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

  2. Stellen Sie sicher, dass comment.char=""die Interpretation von Kommentaren deaktiviert ist.

  3. Definieren Sie die Klassen jeder Spalte explizit mit colClassesin read.table.

  4. Die Einstellung multi.line=FALSEkann auch die Leistung beim Scannen verbessern.

Wenn keines dieser Dinge funktioniert, verwenden Sie eines der Profilierungspakete, um festzustellen, welche Zeilen die Dinge verlangsamen. Vielleicht können Sie eine gekürzte Version von read.tablebasierend auf den Ergebnissen schreiben .

Die andere Alternative besteht darin, Ihre Daten zu filtern, bevor Sie sie in R einlesen.

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


4
Danke für die Tipps Richie. Ich habe ein wenig getestet, und es scheint, dass die Leistungssteigerungen bei Verwendung der Optionen nrow und colClasses für read.table recht bescheiden sind. Zum Beispiel dauert das Lesen einer ~ 7M-Zeilentabelle 78 Sekunden ohne die Optionen und 67 Sekunden mit den Optionen. (Hinweis: Die Tabelle enthält 1 Zeichenspalte, 4 Ganzzahlspalten und ich lese mit comment.char = '' und stringsAsFactors = FALSE). Die Verwendung von save () und load () ist nach Möglichkeit ein guter Tipp. Sobald diese Tabelle mit save () gespeichert wurde, dauert das Laden derselben Tabelle nur 12 Sekunden.
Eytan

2
Das "Feder" -Paket hat ein neues Binärformat, das gut mit Pythons Pandas-Datenrahmen
funktioniert

4
Ich denke, vielleicht müssen Sie Ihren Beitrag in Bezug auf das Paket erneut aktualisieren feather. Das Lesen von Daten featherist viel schneller als fread. Zum Beispiel war ein 4-GB-Datensatz, den ich gerade geladen habe, read_featherungefähr 4,5-mal schneller als fread. Das Speichern von Daten fwriteist noch schneller. blog.dominodatalab.com/the-r-data-io-shootout
Z Boson

2
Bei Federn sind die Dateigrößen jedoch viel größer als bei RDS. Ich glaube nicht, dass es die Komprimierung unterstützt. Die RDS-Datei ist 216 MB groß und die Federdatei ist 4 GB groß. Ist featheralso schneller zum Lesen, verbraucht aber viel mehr Speicherplatz.
Z Boson

@Zboson Wenn Sie Datenrahmen in einer Datei speichern müssen, auf die sowohl von R als auch von Python aus zugegriffen werden kann, featherist dies eine gute Option. Wenn Sie nur daran interessiert sind, Ihre Daten in R lesen zu können, rdsist dies vorzuziehen.
Richie Cotton

279

Hier ist ein Beispiel, das freadvon verwendetdata.table 1.8.7

Die Beispiele stammen von der freadHilfeseite zu , 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

Zusammenfassend:

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

43
Gute Antwort, und das Benchmarking gilt auch in anderen Zusammenhängen. Lesen Sie einfach eine 4-GB-Datei in weniger als einer Minute mit fread. Hatte versucht, es mit den Basis-R-Funktionen einzulesen, und es dauerte ungefähr 15 Stunden.
Ari B. Friedman

1
Mein Benchmark schlägt noch größere Geschwindigkeitsvorteile für read.csv in data.table vor. Beachten Sie, dass data.table kein Standard-R ist, sondern (leider) "nur" von seinen Erstellern auf CRAN gut geteilt wird. Es wird nicht einmal als Standard genug angesehen, um die gemeinsame R-Paketliste zu erstellen, geschweige denn als Ersatz für Datenrahmen zu qualifizieren. Es hat viele Vorteile, aber auch einige sehr kontraintuitive Aspekte. Möglicherweise möchten Sie as.data.frame (fread.csv ("test.csv")) mit dem Paket verwenden, um wieder in die Standard-R-Datenrahmenwelt zurückzukehren.
ivo Welch

3
@mnel Könnten Sie bitte den Benchmark erneut ausführen und einschließen readr?
Jangorecki

2
Zweiter @jangorecki. Auch angesichts freadeinige echte Konkurrenten jetzt hat, könnte nützlich sein , Benchmarks hinzufügen für eine optimierte freadNutzung - Angabe colClassesusw.
MichaelChirico

1
@jangorecji @ MichaelChirico Der angegebene Code ist vollständig reproduzierbar, so dass es einfach ist, das erneute Ausführen des Codes zu simulieren. Auf meinem Computer ist die verstrichene Zeit doppelt so schnell, wenn nicht mehr für die meisten Ergebnisse, obwohl ich ihn über ein Netzwerk ausführe (und gut aktualisierte Versionen, wie es jetzt einige Zeit ist) ... und mit readr bin ich bei 7s, aber auch unter einer Sekunde, wenn ich ein zweites Mal (0,66s) laufe, vermute ich, dass es etwas Caching oder einen Flaschenhals im Netzwerk gibt. fread für die schnellste hier gezeigte Lösung ist auf 2s auf meiner Seite zum Vergleich (zum ersten Mal läuft um 8.69s) aus irgendeinem Grund langsamer)
R. Prost

249

Ich habe diese Frage anfangs nicht gesehen und einige Tage später eine ähnliche Frage gestellt. Ich werde meine vorherige Frage aufschreiben, aber ich dachte, ich würde hier eine Antwort hinzufügen, um zu erklären, wie ich das früher gemacht sqldf()habe.

Es wurde wenig darüber diskutiert, wie 2 GB oder mehr Textdaten am besten in einen R-Datenrahmen importiert werden können. Gestern habe ich einen Blog-Beitrag geschrieben, in dem es darum geht sqldf(), die Daten als Staging-Bereich in SQLite zu importieren und sie dann von SQLite in R zu übernehmen. Das funktioniert sehr gut für mich. Ich konnte 2 GB (3 Spalten, 40 mm Zeilen) Daten in <5 Minuten abrufen. Im Gegensatz dazu lief der read.csvBefehl die ganze Nacht und wurde nie ausgeführt.

Hier ist mein Testcode:

Richten Sie 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 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'))

1
Hallo. Wie würden Sie es als Eingabe für andere Pakete wie zoo verwenden, die so konzipiert sind, dass sie mit allen Daten gleichzeitig verwendet werden können?
Skan

@skan Das Endobjekt ist ein Datenrahmen. Sie müssen es also in ein Zooobjekt konvertieren, um es mit dem Zoo verwenden zu können. Schauen Sie sich die Beispiele in den Zoo-Dokumenten an, um Abbildungen zu erhalten.
JD Long

@ JD Long. Hallo, das Problem ist, dass beim Konvertieren in ein Zoo-Objekt versucht wird, es in den Speicher zu passen. Wenn es zu groß ist, wird ein Fehler ausgegeben. Und wenn das Ergebnis des Zoo-Objekts (zum Beispiel eine Aggregation von zwei Serien) ebenfalls ist, müsste es auch ein SQL- oder ff-Objekt sein.
Skan

Ich weiß nicht, was mit sqldf los ist. Ich habe eine einfache 1-GB-Datei auf der Festplatte (mit 2 numerischen Spalten) erstellt und DTSQL <- read.csv.sql ("f2.txt", dbname = tempfile ()) verwendet. Es wird versucht, die gesamten Daten in den Speicher zu laden. Morgen werde ich stattdessen ff und revoscaler ausprobieren.
Skan

1
@was m ist tausend, also ist mm tausend tausend oder Millionen. Ich hätte es wahrscheinlich als MM großschreiben sollen. Aber ich finde, dass fast jede Million Abkürzungen für jemanden verwirrend sein können, wenn Sie ein ausreichend vielfältiges Publikum haben. Bei meinem Versuch, übermäßig wortreich zu sein, tut es mir leid, dass ich es verwirrender gemacht habe! Accountingcoach.com/blog/what-does-m-and-mm-stand-for
JD Long

73

Seltsamerweise hat jahrelang niemand den unteren Teil der Frage beantwortet, obwohl dies ein wichtiger Teil ist - data.framees handelt sich lediglich um Listen mit den richtigen Attributen. Wenn Sie also große Datenmengen haben, möchten Sie diese nicht as.data.framefür eine Liste verwenden oder ähnliches. Es ist viel schneller, eine Liste einfach in einen Datenrahmen zu "verwandeln":

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

Dadurch wird keine Kopie der Daten erstellt, sodass diese sofort verfügbar sind (im Gegensatz zu allen anderen Methoden). Es wird davon ausgegangen, dass Sie bereits eingestellt habennames() die Liste bereits entsprechend festgelegt haben.

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


6
Die Verwendung tracmemschlägt dies vor attr<-und erstellt class<-intern Kopien. bit::setattroder data.table::setattrnicht.
Mnel

6
Vielleicht haben Sie die falsche Reihenfolge verwendet? Es gibt keine Kopie, wenn Sie verwenden df=scan(...); names(df)=...; attr...; class...- siehe tracemem()(getestet in R 2.15.2)
Simon Urbanek

3
Können Sie näher erläutern, wie Sie die großen Daten nach Spalten in Binärdateien speichern?
Dabsingh

32

Dies wurde zuvor in der R-Hilfe gefragt , daher lohnt es sich, dies zu überprüfen.

Ein Vorschlag war, readChar()das Ergebnis mit strsplit()und zu verwenden und dann zu manipulieren substr(). Sie können sehen, dass die Logik von readChar viel geringer ist als die von read.table.

Ich weiß nicht, ob Speicher ein Problem ist, aber vielleicht möchten Sie auch einen Blick auf das HadoopStreaming- Paket werfen . Hierbei wird Hadoop verwendet , ein MapReduce-Framework, das für den Umgang mit großen Datenmengen entwickelt wurde. Dazu 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)

Die Grundidee hier ist, den Datenimport in Blöcke aufzuteilen. Sie könnten sogar so weit gehen, eines der parallelen Frameworks (z. B. Schnee) zu verwenden und den Datenimport parallel durchzuführen, indem Sie die Datei segmentieren. Höchstwahrscheinlich jedoch für große Datenmengen, die nicht helfen, da Sie auf Speicherbeschränkungen stoßen. Aus diesem Grund ist das Reduzieren von Karten ein besserer Ansatz.


Ich habe gerade einen kurzen Test durchgeführt und readChar scheint aus unerklärlichen Gründen viel schneller zu sein als readLines. Im Vergleich zu einem einfachen C-Test ist es jedoch immer noch so langsam wie die Sünde. Bei der einfachen Aufgabe, 100 Megabyte zu lesen, ist R etwa 5 - 10x langsamer als C
Jonathan Chang

1
Verstehe deinen Standpunkt nicht. Bei Hadoop geht es darum, sehr große Datenmengen zu verarbeiten, worum es bei der Frage ging.
Shane

1
Trotz des Namens hat hsTableReader nichts mit Hadoop per se zu tun, sondern dient zur Verarbeitung großer Datenmengen in Teilen. Es liest aus con einen Block von Zeilen gleichzeitig und übergibt jeden Block als data.frame zur Verarbeitung an FUN. Mit ignoreKey = FALSE wird eine zusätzliche Gruppierung nach Schlüssel (der Eintrag in der ersten Spalte) durchgeführt, die für Map / Reduce-Ansätze relevant ist.
DavidR

Hallo. Wie würden Sie diese Hadoop-Daten als Eingabe für andere Pakete wie zoo verwenden, die für die gleichzeitige Verwendung mit allen Daten ausgelegt sind?
Skan

10

Eine Alternative ist die Verwendung des vroomPakets. Jetzt auf CRAN. vroomLädt nicht die gesamte Datei, sondern indiziert, wo sich die einzelnen Datensätze befinden, und wird später gelesen, wenn Sie sie verwenden.

Zahlen Sie nur für das, was Sie verwenden.

Siehe Einführung in vroom , Erste Schritte mit vroom und den vroom-Benchmarks .

Die grundlegende Übersicht ist, dass das anfängliche Lesen einer großen Datei viel schneller ist und nachfolgende Änderungen an den Daten möglicherweise etwas langsamer sind. Je nachdem, was Sie verwenden, ist dies möglicherweise die beste Option.

Sehen Sie sich unten ein vereinfachtes Beispiel aus den vroom-Benchmarks an . Die wichtigsten Teile sind die superschnellen Lesezeiten, aber leicht säende Vorgänge wie Aggregate usw.

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

5

Ein kleiner zusätzlicher erwähnenswerter Punkt. Wenn Sie eine sehr große Datei haben, können Sie die Anzahl der Zeilen (wenn kein Header vorhanden ist) im laufenden Betrieb berechnen (wobei bedGraphsich der Name Ihrer Datei in Ihrem Arbeitsverzeichnis befindet):

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

Sie können das dann 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

4

Oft denke ich, dass es nur eine gute Praxis ist, größere Datenbanken in einer Datenbank zu halten (z. B. Postgres). Ich benutze nichts zu viel Größeres als (nrow * ncol) ncell = 10M, was ziemlich klein ist; Ich stelle jedoch häufig fest, dass R nur dann speicherintensive Diagramme erstellen und speichern soll, wenn ich aus mehreren Datenbanken abfrage. In der Zukunft von 32-GB-Laptops werden einige dieser Arten von Speicherproblemen verschwinden. Der Reiz, eine Datenbank zum Speichern der Daten zu verwenden und dann den Speicher von R für die resultierenden Abfrageergebnisse und Diagramme zu verwenden, kann dennoch nützlich sein. Einige Vorteile sind:

(1) Die Daten bleiben in Ihrer Datenbank geladen. Sie stellen in pgadmin einfach wieder eine Verbindung zu den gewünschten Datenbanken her, wenn Sie Ihren Laptop wieder einschalten.

(2) Es ist wahr, dass R viel mehr raffinierte statistische und grafische Operationen ausführen kann als SQL. Ich denke jedoch, dass SQL besser dafür ausgelegt ist, große Datenmengen abzufragen als 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)

3

Ich lese Daten sehr schnell mit dem neuen arrow Paket. Es scheint in einem ziemlich frühen Stadium zu sein.

Insbesondere verwende ich das Parkett- Säulenformat. Dies konvertiert zurück zu adata.frame in R , aber Sie können noch tiefere Beschleunigungen erzielen, wenn Sie dies nicht tun. Dieses Format ist praktisch, da es auch von Python aus verwendet werden kann.

Mein Hauptanwendungsfall hierfür ist ein ziemlich zurückhaltender RShiny-Server. Aus diesen Gründen ziehe ich es vor, Daten an die Apps anzuhängen (dh außerhalb von SQL), und benötige daher eine geringe Dateigröße sowie Geschwindigkeit.

Dieser verlinkte Artikel bietet Benchmarking und einen guten Überblick. Ich habe unten einige interessante Punkte zitiert.

https://ursalabs.org/blog/2019-10-columnar-perf/

Dateigröße

Das heißt, die Parkettdatei ist halb so groß wie die komprimierte CSV-Datei. Einer der Gründe, warum die Parkettdatei so klein ist, ist die Wörterbuchcodierung (auch als "Wörterbuchkomprimierung" bezeichnet). Die Wörterbuchkomprimierung kann zu einer wesentlich besseren Komprimierung führen als die Verwendung eines Allzweck-Byteekompressors wie LZ4 oder ZSTD (die im FST-Format verwendet werden). Parkett wurde entwickelt, um sehr kleine Dateien zu erstellen, die schnell zu lesen sind.

Lesegeschwindigkeit

Bei der Steuerung nach Ausgabetyp (z. B. Vergleich aller R data.frame-Ausgaben miteinander) sehen wir, dass die Leistung von Parkett, Feder und FST innerhalb eines relativ kleinen Bereichs voneinander liegt. Gleiches gilt für die Ausgaben von pandas.DataFrame. data.table :: fread ist mit der Dateigröße von 1,5 GB beeindruckend konkurrenzfähig, liegt jedoch bei der 2,5-GB-CSV hinter den anderen zurück.


Unabhängiger Test

Ich habe ein unabhängiges Benchmarking für einen simulierten Datensatz von 1.000.000 Zeilen durchgeführt. Grundsätzlich habe ich ein paar Dinge durcheinander gebracht, um die Komprimierung herauszufordern. Außerdem habe ich ein kurzes Textfeld mit zufälligen Wörtern und zwei simulierten Faktoren hinzugefügt.

Daten

library(dplyr)
library(tibble)
library(OpenRepGrid)

n <- 1000000

set.seed(1234)
some_levels1 <- sapply(1:10, function(x) paste(LETTERS[sample(1:26, size = sample(3:8, 1), replace = TRUE)], collapse = ""))
some_levels2 <- sapply(1:65, function(x) paste(LETTERS[sample(1:26, size = sample(5:16, 1), replace = TRUE)], collapse = ""))


test_data <- mtcars %>%
  rownames_to_column() %>%
  sample_n(n, replace = TRUE) %>%
  mutate_all(~ sample(., length(.))) %>%
  mutate(factor1 = sample(some_levels1, n, replace = TRUE),
         factor2 = sample(some_levels2, n, replace = TRUE),
         text = randomSentences(n, sample(3:8, n, replace = TRUE))
         )

Lesen und Schreiben

Das Schreiben der Daten ist einfach.

library(arrow)

write_parquet(test_data , "test_data.parquet")

# you can also mess with the compression
write_parquet(test_data, "test_data2.parquet", compress = "gzip", compression_level = 9)

Das Lesen der Daten ist ebenfalls einfach.

read_parquet("test_data.parquet")

# this option will result in lightning fast reads, but in a different format.
read_parquet("test_data2.parquet", as_data_frame = FALSE)

Ich habe das Lesen dieser Daten anhand einiger konkurrierender Optionen getestet und dabei etwas andere Ergebnisse erzielt als mit dem obigen Artikel, der erwartet wird.

Benchmarking

Diese Datei ist bei weitem nicht so groß wie der Benchmark-Artikel. Vielleicht ist das der Unterschied.

Tests

  • rds: test_data.rds (20,3 MB)
  • parquet2_native: (14,9 MB mit höherer Komprimierung und as_data_frame = FALSE)
  • parquet2: test_data2.parquet (14,9 MB mit höherer Komprimierung)
  • Parkett: test_data.parquet (40,7 MB)
  • fst2: test_data2.fst (27,9 MB mit höherer Komprimierung)
  • fst: test_data.fst (76,8 MB)
  • fread2: test_data.csv.gz (23,6 MB)
  • fread: test_data.csv (98,7 MB)
  • Federpfeil: test_data.feather (157,2 MB lesen mit arrow)
  • Feder: test_data.feather (157,2 MB lesen mit feather)

Beobachtungen

Für diese bestimmte Datei freadist eigentlich sehr schnell. Ich mag die kleine Dateigröße aus dem stark komprimierten parquet2Test. Ich kann die Zeit investieren, um mit dem nativen Datenformat zu arbeiten, anstatt mit einemdata.frame wenn ich die Beschleunigung wirklich brauche.

Hier fstist auch eine gute Wahl. Ich würde entweder das stark komprimierte fstFormat oder das stark komprimierte Format verwenden, parquetje nachdem, ob ich den Kompromiss zwischen Geschwindigkeit oder Dateigröße benötigte.


0

Anstelle der herkömmlichen read.table ist fread meiner Meinung nach eine schnellere Funktion. Wenn Sie zusätzliche Attribute angeben, z. B. nur die erforderlichen Spalten auswählen, Spalten und Zeichenfolgen als Faktoren angeben, wird der Zeitaufwand für den Import der Datei verkürzt.

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

Ich habe alles oben versucht und [readr] [1] den besten Job gemacht. Ich habe nur 8 GB RAM

Schleife für 20 Dateien, jeweils 5 GB, 7 Spalten:

read_fwf(arquivos[i],col_types = "ccccccc",fwf_cols(cnpj = c(4,17), nome = c(19,168), cpf = c(169,183), fantasia = c(169,223), sit.cadastral = c(224,225), dt.sitcadastral = c(226,233), cnae = c(376,382)))
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.