2015-07-09 7 views
11

Ich habe 900000 CSV-Dateien, die ich in einem großen data.table kombinieren möchte. Für diesen Fall habe ich eine for loop erstellt, die jede Datei einzeln liest und sie der data.table hinzufügt. Das Problem ist, dass es langsamer wird und die Menge an Zeit exponentiell zunimmt. Es wäre toll, wenn mir jemand helfen könnte, den Code schneller laufen zu lassen. Jede der csv-Dateien hat 300 Zeilen und 15 Spalten. Der Code verwende ich so weit:Lesen mehrerer csv-Dateien schneller in data.table R

library(data.table) 
setwd("~/My/Folder") 

WD="~/My/Folder" 
data<-data.table(read.csv(text="X,Field1,PostId,ThreadId,UserId,Timestamp,Upvotes,Downvotes,Flagged,Approved,Deleted,Replies,ReplyTo,Content,Sentiment")) 

csv.list<- list.files(WD) 
k=1 

for (i in csv.list){ 
    temp.data<-read.csv(i) 
    data<-data.table(rbind(data,temp.data)) 

    if (k %% 100 == 0) 
    print(k/length(csv.list)) 

    k<-k+1 
} 
+9

R nicht das richtige Werkzeug sein kann; Siehe Spacedmans Antwort hier, zum Beispiel http://stackoverflow.com/a/11433740/210673 – Aaron

+1

Es mag Blasphemie in einer [tag: r] -Frage sein, aber 'csvstack' kann schnelle Arbeit der Kombination machen: http: // csvkit.readthedocs.org/de/0.9.1/scripts/csvstack.html ('pip install csvkit'). Sie werden definitiv 'data.table :: fread' für diese resultierende GIANT-CSV-Datei verwenden wollen. – hrbrmstr

+1

Zwei Punkte: sogar mit einer ungefähren Größe von nur 4 Byte für jeden einzelnen Eintrag wird die endgültige Größe im Speicher 4 Bytes * 15 Spalten * 300 Zeilen * 900000 Dateien/1024^3> = 15 GB. Mit 'rbind()' und anderen speicherintensiven Kopiermethoden wird der Betrag verdoppelt. –

Antwort

0

Ein Vorschlag wäre, sie in Gruppen von 10 zunächst zu fusionieren oder so, und dann diese Gruppen verschmelzen, und so weiter. Das hat den Vorteil, dass wenn einzelne Merges fehlschlagen, Sie nicht die ganze Arbeit verlieren. Die Art und Weise, wie Sie es tun, führt jetzt nicht nur zu einer exponentiell verlangsamten Ausführung, sondern macht es auch notwendig, dass Sie jedes Mal, wenn Sie scheitern, von vorn beginnen müssen.

Auf diese Weise wird auch die durchschnittliche Größe der Datenrahmen bei den rbind Anrufe verringern, da die Mehrheit von ihnen wird an kleine Datenrahmen und nur ein paar große am Ende angehängt werden. Dies sollte den Großteil der exponentiell wachsenden Ausführungszeit eliminieren.

Ich denke, egal was Sie tun, es wird eine Menge Arbeit sein.

2

Sie erweitern Ihre Datentabelle in einer for-Schleife - aus diesem Grund dauert es ewig. Wenn Sie die for-Schleife beibehalten möchten, erstellen Sie zunächst einen leeren Datenrahmen (vor der Schleife) mit den erforderlichen Dimensionen (Zeilen x Spalten) und legen Sie ihn im RAM ab.

Schreiben Sie dann in jede Iteration in diesen leeren Rahmen.

Andernfalls verwenden Sie rbind.fill aus dem Paket plyr - und vermeiden Sie die Schleife altohegter. Zur Nutzung rbind.fill:

require(plyr) 
data <- rbind.fill(df1, df2, df3, ... , dfN) 

Um die Namen der df des passieren, man könnte/sollte eine Funktion anwenden verwenden.

+0

Sie sind richtig, aktualisiert. – Repmat

+1

Können Sie erklären, wie Sie rbind.fill richtig verwenden? – Carlo

+0

Während der Initialisierung des endgültigen Datenrahmens wäre es besser, wenn es eine Einschränkung gegenüber der "data.table" -Abgabe von Daten gäbe. Die Tabelle wird nicht die beste Lösung sein. –

0

Einige Dinge unter der Annahme prüfen Sie alle eingegebenen Daten vertrauen können und dass jeder Datensatz ist sicher einzigartig sein:

  • Betrachten Sie die Tabelle erstellen, ohne Indizes in importiert. Wenn die Indizes riesig werden, wächst die Zeit, die benötigt wird, um sie während des Imports zu verwalten - es klingt also so, als würde dies passieren. Wenn dies Ihr Problem ist, würde es lange dauern, Indizes später zu erstellen.

  • Alternativ können Sie mit der Menge der Daten, die Sie diskutieren, eine Methode zum Partitionieren der Daten (oft über Datumsbereiche) in Betracht ziehen. Abhängig von Ihrer Datenbank können Sie dann individuell indizierte Partitionen haben - was den Indexaufwand verringert.

  • Wenn Ihr Democode nicht in ein Dienstprogramm zum Importieren von Datenbankdateien aufgelöst wird, verwenden Sie ein solches Dienstprogramm.

  • Es kann sich lohnen, Dateien vor dem Import in größere Datensätze zu verarbeiten. Sie könnten damit experimentieren, indem Sie beispielsweise vor dem Laden 100 Dateien in eine größere Datei zusammenfassen und Zeiten vergleichen.

Im Fall, dass Sie keine Partitionen verwenden können (abhängig von der Umgebung und der Erfahrung der Datenbank Personal) Sie ein Haus gebraut Methode der seperating Daten in verschiedenen Tabellen verwenden können. Zum Beispiel data201401 bis data201412. Sie müssen jedoch Ihre eigenen Dienstprogramme zur Abfrage über Grenzen hinweg rollen.

Obwohl dies definitiv keine bessere Option ist, ist es etwas, was Sie im Notfall tun könnten - und es würde Ihnen erlauben, veraltete Datensätze einfach und ohne Anpassung der zugehörigen Indizes zu beenden. Sie können auch vorverarbeitete eingehende Daten bei Bedarf mit "Partition" laden.

1

Ich gehe mit @Repmat als Ihre aktuelle Lösung mit rbind() kopiert die gesamte data.table im Speicher jedes Mal, wenn es aufgerufen wird (deshalb Zeit wächst exponentiell). Eine andere Möglichkeit wäre, eine leere CSV-Datei mit nur den Kopfzeilen zu erstellen und dann einfach die Daten aller Ihrer Dateien an diese CSV-Datei anzuhängen.

write.table(fread(i), file = "your_final_csv_file", sep = ";", 
      col.names = FALSE, row.names=FALSE, append=TRUE, quote=FALSE) 

Auf diese Weise müssen Sie keine Sorge um die Daten an die richtigen Indizes in Ihrer data.table setzen. Auch als Hinweis: fread() ist der Data.table-Dateileser, der viel schneller ist als read.csv.

Im Allgemeinen wäre R nicht meine erste Wahl für diese Daten Munging Aufgaben.

3

Wie von @Repmat vorgeschlagen, verwenden Sie rbind.fill. Wie von @Christian Borck vorgeschlagen, verwenden Sie fread für schnellere Lesevorgänge.

require(data.table) 
require(plyr) 

files <- list.files("dir/name") 
df <- rbind.fill(lapply(files, fread, header=TRUE)) 

Alternativ könnten Sie do.call, aber rbind.fill ist schneller (http://www.r-bloggers.com/the-rbinding-race-for-vs-do-call-vs-rbind-fill/)

df <- do.call(rbind, lapply(files, fread, header=TRUE)) 

Oder Sie data.table Paket verwenden könnte, see this

+0

fread und rbind.fill funktionieren super! Ich benutze diese präzise Kombination, um riesige Listen von Dateien aus dem Netz zu kratzen, also kann ich dafür bürgen! –

+1

Ist 'rbind.fill' besser als' rbind (..., fill = TRUE) '? – GSee

+0

Ja, fast alles, was man sich vorstellen kann, ist schneller als rbind. Aber Sie werden den Unterschied nicht bemerken, für kleine Dateien oder für eine kleine Anzahl von Dateien. – Repmat

7

Vorausgesetzt, Ihre Dateien konventionelle csv, würde ich data.table::fread verwenden, da es schneller ist. Wenn Sie auf einem Linux-ähnlichen Betriebssystem arbeiten, würde ich die Tatsache verwenden, dass es Shell-Befehle erlaubt. Vorausgesetzt, Ihre Eingabedateien die einzigen CSV-Dateien im Ordner ich tun würde:

dt <- fread("tail -n-1 -q ~/My/Folder/*.csv") 

Sie müssen die Spaltennamen manuell einstellen danach.

Wenn Sie in R Dinge halten wollte, würde ich lapply und rbindlist:

lst <- lapply(csv.list, fread) 
dt <- rbindlist(lst) 

Sie auch plyr::ldply verwenden:

dt <- setDT(ldply(csv.list, fread)) 

Dies hat den Vorteil, dass Sie .progress = "text" verwenden können um den Lesefortschritt abzulesen.

Alle oben genannten davon ausgehen, dass die Dateien alle das gleiche Format haben und eine Kopfzeile haben.

+0

'rbindlist (lst)' sollte outperform 'do.call (" rbind ", lst)' – GSee

+0

@GSee danke. Herausgegeben über –

+1

@Frank oops. Danke, dass du das aufgehoben hast. –

2

Aufbauend auf Nick Kennedy's answer mit plyr::ldply gibt es ungefähr 50% Geschwindigkeitserhöhung durch Aktivieren der .parallel Option beim Lesen von 400 csv-Dateien jeweils etwa 30-40 MB.

Original-Antwort mit Fortschrittsbalken

dt <- setDT(ldply(csv.list, fread, .progress="text") 

Aktivieren .parallel auch mit einem Textfortschrittsbalken

library(plyr) 
library(data.table) 
library(doSNOW) 

cl <- makeCluster(4) 
registerDoSNOW(cl) 

pb <- txtProgressBar(max=length(csv.list), style=3) 
pbu <- function(i) setTxtProgressBar(pb, i) 
dt <- setDT(ldply(csv.list, fread, .parallel=TRUE, .paropts=list(.options.snow=list(progress=pbu)))) 

stopCluster(cl)