Trimmen einer riesigen CSV-Datei (3,5 GB) zum Einlesen in R.

87

Ich habe also eine Datendatei (durch Semikolons getrennt), die viele Details und unvollständige Zeilen enthält (was dazu führt, dass Access und SQL ersticken). Der Datensatz auf Kreisebene ist seit 40 Jahren in Segmente, Untersegmente und Untersegmente unterteilt (für insgesamt ~ 200 Faktoren). Kurz gesagt, es ist riesig und es wird nicht in die Erinnerung passen, wenn ich versuche, es einfach zu lesen.

Meine Frage lautet also, da ich alle Landkreise, aber nur ein einziges Jahr (und nur die höchste Segmentebene ... die am Ende zu etwa 100.000 Zeilen führen möchte) möchte, was der beste Weg wäre, um dies zu erreichen dieses Rollup in R?

Momentan versuche ich, irrelevante Jahre mit Python zu beenden, um das Dateigrößenlimit zu umgehen, indem ich jeweils eine Zeile lese und arbeite, aber ich würde eine Nur-R-Lösung bevorzugen (CRAN-Pakete OK). Gibt es eine ähnliche Möglichkeit, Dateien in R Stück für Stück einzulesen?

Irgendwelche Ideen wären sehr dankbar.

Aktualisieren:

  • Einschränkungen
    • Muss meine Maschine benutzen , also keine EC2-Instanzen
    • So nur R-möglich wie möglich. Geschwindigkeit und Ressourcen spielen in diesem Fall keine Rolle ... vorausgesetzt, meine Maschine explodiert nicht ...
    • Wie Sie unten sehen können, enthalten die Daten gemischte Typen, mit denen ich später arbeiten muss
  • Daten
    • Die Daten sind 3,5 GB groß, mit etwa 8,5 Millionen Zeilen und 17 Spalten
    • Ein paar tausend Zeilen (~ 2k) sind fehlerhaft, mit nur einer Spalte anstelle von 17
      • Diese sind völlig unwichtig und können fallengelassen werden
    • Ich brauche nur ~ 100.000 Zeilen aus dieser Datei (siehe unten)

Datenbeispiel:

County; State; Year; Quarter; Segment; Sub-Segment; Sub-Sub-Segment; GDP; ...
Ada County;NC;2009;4;FIRE;Financial;Banks;80.1; ...
Ada County;NC;2010;1;FIRE;Financial;Banks;82.5; ...
NC  [Malformed row]
[8.5 Mill rows]

Ich möchte einige Spalten herausschneiden und zwei von 40 verfügbaren Jahren (2009-2010 von 1980-2020) auswählen, damit die Daten in R passen:

County; State; Year; Quarter; Segment; GDP; ...
Ada County;NC;2009;4;FIRE;80.1; ...
Ada County;NC;2010;1;FIRE;82.5; ...
[~200,000 rows]

Ergebnisse:

Nachdem ich an allen gemachten Vorschlägen herumgebastelt hatte, entschied ich, dass readLines, vorgeschlagen von JD und Marek, am besten funktionieren würde. Ich gab Marek den Scheck, weil er eine Beispielimplementierung gab.

Ich habe eine leicht angepasste Version von Mareks Implementierung für meine endgültige Antwort hier reproduziert und strsplit und cat verwendet, um nur die gewünschten Spalten beizubehalten.

Es sollte auch beachtet werden, dass dies VIEL weniger effizient ist als Python ... wie in, Python durchläuft die 3,5-GB-Datei in 5 Minuten, während R ungefähr 60 dauert ... aber wenn Sie nur R haben, dann ist dies das Ticket.

## Open a connection separately to hold the cursor position
file.in <- file('bad_data.txt', 'rt')
file.out <- file('chopped_data.txt', 'wt')
line <- readLines(file.in, n=1)
line.split <- strsplit(line, ';')
# Stitching together only the columns we want
cat(line.split[[1]][1:5], line.split[[1]][8], sep = ';', file = file.out, fill = TRUE)
## Use a loop to read in the rest of the lines
line <- readLines(file.in, n=1)
while (length(line)) {
  line.split <- strsplit(line, ';')
  if (length(line.split[[1]]) > 1) {
    if (line.split[[1]][3] == '2009') {
        cat(line.split[[1]][1:5], line.split[[1]][8], sep = ';', file = file.out, fill = TRUE)
    }
  }
  line<- readLines(file.in, n=1)
}
close(file.in)
close(file.out)

Fehler nach Ansatz:

  • sqldf
    • Dies ist definitiv das, was ich in Zukunft für diese Art von Problem verwenden werde, wenn die Daten wohlgeformt sind. Wenn dies nicht der Fall ist, erstickt SQLite.
  • Karte verkleinern
    • Um ehrlich zu sein, haben mich die Dokumente ein wenig eingeschüchtert, so dass ich nicht dazu gekommen bin, es zu versuchen. Es sah so aus, als müsste sich das Objekt ebenfalls im Speicher befinden, was den Punkt zunichte machen würde, wenn dies der Fall wäre.
  • Bigmemory
    • Dieser Ansatz ist sauber mit den Daten verknüpft, kann jedoch jeweils nur einen Typ verarbeiten. Infolgedessen fielen alle meine Zeichenvektoren, wenn sie in eine große Tabelle eingefügt wurden. Wenn ich jedoch große Datenmengen für die Zukunft entwerfen muss, würde ich nur Zahlen verwenden, um diese Option am Leben zu erhalten.
  • Scan
    • Scan schien ähnliche Probleme mit dem Typ zu haben wie großer Speicher, aber mit allen Mechanismen von readLines. Kurz gesagt, diesmal passte es einfach nicht.
FTWynn
quelle
3
Wenn Ihre Kriterien einfach genug sind, können Sie wahrscheinlich davonkommen , eine abgespeckte Version der CSV zu verwenden sedund / oder awkzu erstellen, die Sie direkt einlesen können. Da dies eher eine Problemumgehung als eine Antwort ist, werde ich es als Kommentar hinterlassen.
Hank Gay
Ich stimme mit Hank - Sie das richtige Werkzeug für den Job verwendet werden soll, und wenn es einfache Datenbereinigung ist / Entfernen von irrelevanten Zeilen / Spalten - Befehl Tools Linie Strom wie Sortier- / sed / awk sind groß und werden sein Weg weniger ressourcenintensiv als R oder Python - wenn Sie ein Beispiel Ihres Dateiformats geben, könnten wir wahrscheinlich ein Beispiel geben
Aaron Statham
Toll. Lassen Sie uns wissen, was Sie entdecken.
Shane
@Hank & Aaron: Ich bin im Allgemeinen alle dafür, das richtige Tool für den Job zu verwenden, aber da dies auf einem Windows-Computer bei der Arbeit ist und ich R lerne, dachte ich, es wäre eine gute Übung, auf Best Practices zu verzichten und versuchen Sie dies als R-only, wenn möglich.
FTWynn
2
Weitere Informationen finden Sie im Paket data.table R. Die freadFunktion ist viel schneller als read.table. Verwenden Sie so etwas wie x = fread(file_path_here, data.table=FALSE), um es als data.frameObjekt zu laden .
Paläo13

Antworten:

39

Mein Versuch mit readLines. Dieser Teil eines Codes wird csvmit ausgewählten Jahren erstellt.

file_in <- file("in.csv","r")
file_out <- file("out.csv","a")
x <- readLines(file_in, n=1)
writeLines(x, file_out) # copy headers

B <- 300000 # depends how large is one pack
while(length(x)) {
    ind <- grep("^[^;]*;[^;]*; 20(09|10)", x)
    if (length(ind)) writeLines(x[ind], file_out)
    x <- readLines(file_in, n=B)
}
close(file_in)
close(file_out)
Marek
quelle
Das ist fast genau das, was ich gerade geschrieben habe. Ich spüre, dass dies angesichts von Speicherbeschränkungen, gemischten Typen und fehlerhaften Zeilen auch die beste Antwort sein wird.
FTWynn
10

Ich bin kein Experte in diesem Bereich, aber Sie könnten MapReduce ausprobieren , was im Grunde bedeuten würde, einen "Divide and Conquer" -Ansatz zu wählen. R hat hierfür mehrere Optionen, darunter:

  1. mapReduce (reines R)
  2. RHIPE (das Hadoop verwendet ); In Beispiel 6.2.2 in der Dokumentation finden Sie ein Beispiel für Teilmengen von Dateien

Alternativ bietet R mehrere Pakete für große Datenmengen, die außerhalb des Speichers (auf die Festplatte) gehen. Sie könnten wahrscheinlich den gesamten Datensatz in ein bigmemoryObjekt laden und die Reduzierung vollständig in R durchführen. Eine Reihe von Tools finden Sie unter http://www.bigmemory.org/ .

Shane
quelle
Guter Vorschlag, aber ich habe nicht viel Erfahrung mit MapReduce und seiner Art. Ich muss es nachlesen.
FTWynn
bigmemoryIn diesem Fall ist es möglicherweise einfacher, es zuerst zu versuchen.
Shane
10

Gibt es eine ähnliche Möglichkeit, Dateien in R Stück für Stück einzulesen?

Ja. Die Funktion readChar () liest einen Zeichenblock ein, ohne davon auszugehen, dass sie nullterminiert sind. Wenn Sie Daten gleichzeitig in einer Zeile lesen möchten, können Sie readLines () verwenden . Wenn Sie einen Block oder eine Zeile lesen, eine Operation ausführen und dann die Daten ausschreiben, können Sie das Speicherproblem vermeiden. Wenn Sie jedoch eine große Speicherinstanz auf Amazon EC2 starten möchten, können Sie bis zu 64 GB RAM erhalten. Das sollte Ihre Datei und viel Platz zum Bearbeiten der Daten enthalten.

Wenn Sie mehr Geschwindigkeit benötigen, ist die Empfehlung von Shane, Map Reduce zu verwenden, sehr gut. Wenn Sie jedoch eine große Speicherinstanz auf EC2 verwenden, sollten Sie sich das Multicore-Paket ansehen, um alle Kerne auf einem Computer zu verwenden.

Wenn Sie viele Gigs mit begrenzten Daten in R lesen möchten, sollten Sie zumindest das sqldf-Paket untersuchen, mit dem Sie direkt aus R in sqldf importieren und dann die Daten aus R heraus verarbeiten können. Ich habe festgestellt, dass sqldf eines davon ist der schnellsten Möglichkeiten, Datenmengen in R zu importieren, wie in dieser vorherigen Frage erwähnt .

JD Long
quelle
Ich werde eine EC2-Instanz im Auge behalten, aber im Moment muss ich mich an meinen Desktop halten und es sind 2 GB RAM. sqldf scheint definitiv das zu sein, was ich mir vorgestellt habe. Es drosselt jedoch auch die fehlerhaften Zeilen (es sollten 17 Spalten vorhanden sein, aber ein paar tausend Zeilen haben nur eine). Erfordert dies eine andere Vorverarbeitungsmethode oder fehlt mir eine Option?
FTWynn
6

Es gibt ein brandneues Paket namens colbycol, mit dem Sie nur die gewünschten Variablen aus riesigen Textdateien einlesen können:

http://colbycol.r-forge.r-project.org/

Es gibt alle Argumente an read.table weiter, daher sollte die Kombination eine ziemlich enge Teilmenge ermöglichen.

Ari B. Friedman
quelle
6

Das ffPaket ist eine transparente Möglichkeit, mit großen Dateien umzugehen.

Möglicherweise sehen Sie die Paket- Website und / oder eine Präsentation darüber.

ich hoffe das hilft

Ali
quelle
5

Sie können Daten in die SQLite-Datenbank importieren und dann mithilfe von RSQLite Teilmengen auswählen.

Marek
quelle
Ein guter Plan, aber da dies im Wesentlichen das ist, was sqldf hinter den Kulissen tut, würde ich das vorziehen. Es sei denn, es gibt einen besseren Weg, um mit den fehlerhaften Zeilen umzugehen, wenn Sie gerade RSQLite verwenden?
FTWynn
5

Was ist mit readrund der read_*_chunkedFamilie?

Also in deinem Fall:

testfile.csv

County; State; Year; Quarter; Segment; Sub-Segment; Sub-Sub-Segment; GDP
Ada County;NC;2009;4;FIRE;Financial;Banks;80.1
Ada County;NC;2010;1;FIRE;Financial;Banks;82.5
lol
Ada County;NC;2013;1;FIRE;Financial;Banks;82.5

Tatsächlicher Code

require(readr)
f <- function(x, pos) subset(x, Year %in% c(2009, 2010))
read_csv2_chunked("testfile.csv", DataFrameCallback$new(f), chunk_size = 1)

Dies gilt ffür jeden Block, wobei die Spaltennamen gespeichert und am Ende die gefilterten Ergebnisse kombiniert werden. Sehen Sie, ?callbackwoher dieses Beispiel stammt.

Das führt zu:

# A tibble: 2 × 8
      County State  Year Quarter Segment `Sub-Segment` `Sub-Sub-Segment`   GDP
*      <chr> <chr> <int>   <int>   <chr>         <chr>             <chr> <dbl>
1 Ada County    NC  2009       4    FIRE     Financial             Banks   801
2 Ada County    NC  2010       1    FIRE     Financial             Banks   825

Sie können sogar erhöhen, chunk_sizeaber in diesem Beispiel gibt es nur 4 Zeilen.

Rentrop
quelle
4

Haben Sie Bigmemory angesehen ? Schauen Sie sich dies und das an .

George Dontas
quelle
Gute Idee. Ich werde es untersuchen.
FTWynn
3

Möglicherweise können Sie auf MySQL oder PostgreSQL migrieren, um sich vor MS Access-Einschränkungen zu schützen.

Es ist recht einfach, R mit einem DBI- basierten Datenbankconnector (verfügbar auf CRAN) mit diesen Systemen zu verbinden .

Scholle
quelle
Touche für die Verwendung besserer Datenbank-Tools, aber da dies einen administrativen Aufwand bedeuten würde (ich muss diese Verwaltungsvorschriften in großen Unternehmen lieben), versuche ich, mich an das zu halten, was ich habe. Außerdem strebe ich so wenig Konvertierungen wie möglich zwischen den Textdateien an, die ich erhalte.
FTWynn
3

scan () hat sowohl ein nlines-Argument als auch ein skip-Argument. Gibt es einen Grund, warum Sie dies einfach verwenden können, um einen Teil der Zeilen zu lesen und das Datum zu überprüfen, um festzustellen, ob es angemessen ist? Wenn die Eingabedatei nach Datum sortiert ist, können Sie einen Index speichern, der Ihnen sagt, wie Ihr Sprung und Ihre Zeilen lauten sollten, um den Prozess in Zukunft zu beschleunigen.

frankc
quelle
Ich werde es überprüfen, aber die Datei wird nicht nach etwas Hilfreichem wie Datum sortiert. Die Anbieter scheinen zu denken, dass es wichtiger ist, nach der Region zu sortieren, in der sich ein bestimmter Landkreis befindet. /
Seufz
Ich denke, Sie haben seinen Vorschlag falsch verstanden: Lesen Sie Ihre Datei Block für Block und extrahieren Sie nur die Zeilen, die Sie aus jedem Block benötigen. Die Dateien müssen nicht bestellt werden.
Karl Forner
1

Heutzutage sind 3,5 GB nicht wirklich so groß, ich kann für 2,80 USD / Stunde auf einen Computer mit 244 GB RAM (r3.8xlarge) in der Amazon Cloud zugreifen. Wie viele Stunden werden Sie brauchen, um herauszufinden, wie Sie das Problem mithilfe von Big-Data-Lösungen lösen können? Wie viel ist deine Zeit wert? Ja, Sie werden ein oder zwei Stunden brauchen, um herauszufinden, wie Sie AWS verwenden. Sie können jedoch die Grundlagen auf einer kostenlosen Ebene erlernen, die Daten hochladen und die ersten 10.000 Zeilen in R lesen, um zu überprüfen, ob sie funktionieren, und dann können Sie a starten große Speicherinstanz wie r3.8xlarge und alles einlesen! Nur mein 2c.

Sean
quelle
0

Jetzt, 2017, würde ich vorschlagen, sich für Spark und SparkR zu entscheiden.

  • Die Syntax kann auf einfache, eher dplyr-ähnliche Weise geschrieben werden

  • es passt ganz gut zu kleinem Speicher (klein im Sinne von 2017)

Es kann jedoch eine einschüchternde Erfahrung sein, loszulegen ...

Ott Toomet
quelle
-3

Ich würde mich für eine Datenbank entscheiden und dann einige Fragen stellen, um die benötigten Beispiele über DBI zu extrahieren

Bitte vermeiden Sie den Import einer 3,5-GB-CSV-Datei in SQLite. Oder überprüfen Sie mindestens, ob Ihre RIESIGE Datenbank den SQLite-Grenzwerten entspricht ( http://www.sqlite.org/limits.html)

Es ist eine verdammt große DB, die du hast. Ich würde mich für MySQL entscheiden, wenn Sie Geschwindigkeit brauchen. Warten Sie jedoch viele Stunden, bis der Import abgeschlossen ist. Es sei denn, Sie haben unkonventionelle Hardware oder schreiben aus der Zukunft ...

Amazon EC2 könnte auch eine gute Lösung sein, um einen Server mit R und MySQL zu instanziieren.

meine zwei bescheidenen Pfennige wert.

Liborio Francesco Cannici
quelle
18
Wie groß sind 3,5 GB für SQLite? Solange Sie ein geeignetes Dateisystem verwenden, sollte es kein Problem geben (ich verwende regelmäßig> 30 GB SQLite-Datenbank für Einzelbenutzeranwendungen)
Aaron Statham