Ritagliare un enorme file csv (3,5 GB) da leggere in R


87

Quindi ho un file di dati (separato da punto e virgola) che ha molti dettagli e righe incomplete (che portano Access e SQL a soffocare). È un set di dati a livello di contea suddiviso in segmenti, sottosegmenti e sottosegmenti (per un totale di ~ 200 fattori) per 40 anni. In breve, è enorme e non entrerà nella memoria se provo a leggerlo semplicemente.

Quindi la mia domanda è questa, dato che voglio tutte le contee, ma solo un anno (e solo il livello più alto di segmento ... che porta a circa 100.000 righe alla fine), quale sarebbe il modo migliore per ottenere questo rollup in R?

Attualmente sto cercando di eliminare anni irrilevanti con Python, aggirando il limite di dimensione del file leggendo e operando su una riga alla volta, ma preferirei una soluzione solo R (pacchetti CRAN OK). Esiste un modo simile per leggere nei file un pezzo alla volta in R?

Tutte le idee notevolmente sarebbero apprezzate.

Aggiornare:

  • Vincoli
    • Ha bisogno di usare la mia macchina, quindi nessuna istanza EC2
    • Solo R possibile. La velocità e le risorse non sono un problema in questo caso ... a condizione che la mia macchina non esploda ...
    • Come puoi vedere di seguito, i dati contengono tipi misti, su cui devo operare in seguito
  • Dati
    • I dati sono di 3,5 GB, con circa 8,5 milioni di righe e 17 colonne
    • Un paio di migliaia di righe (~ 2k) non sono corretti, con una sola colonna invece di 17
      • Questi sono del tutto irrilevanti e possono essere eliminati
    • Ho solo bisogno di ~ 100.000 righe da questo file (vedi sotto)

Esempio di dati:

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]

Voglio tagliare alcune colonne e scegliere due dei 40 anni disponibili (2009-2010 dal 1980-2020), in modo che i dati possano rientrare in R:

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]

Risultati:

Dopo aver armeggiato con tutti i suggerimenti fatti, ho deciso che readLines, suggerito da JD e Marek, avrebbe funzionato meglio. Ho dato il controllo a Marek perché ha fornito un'implementazione di esempio.

Ho riprodotto una versione leggermente adattata dell'implementazione di Marek per la mia risposta finale qui, usando strsplit e cat per mantenere solo le colonne che voglio.

Va anche notato che questo è MOLTO meno efficiente di Python ... come in, Python chomps attraverso il file da 3,5 GB in 5 minuti mentre R impiega circa 60 ... ma se tutto ciò che hai è R, questo è il biglietto.

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

Fallimenti per approccio:

  • sqldf
    • Questo è sicuramente ciò che userò per questo tipo di problema in futuro se i dati sono ben formati. Tuttavia, se non lo è, SQLite soffoca.
  • Riduci mappa
    • Ad essere onesti, i documenti mi hanno intimidito un po 'su questo, quindi non sono riuscito a provarlo. Sembrava che richiedesse che anche l'oggetto fosse nella memoria, il che avrebbe vanificato il punto se fosse così.
  • bigmemory
    • Questo approccio è chiaramente collegato ai dati, ma può gestire solo un tipo alla volta. Di conseguenza, tutti i miei vettori di caratteri cadevano quando inseriti in un big.table. Se ho bisogno di progettare set di dati di grandi dimensioni per il futuro, prenderei in considerazione l'utilizzo di numeri solo per mantenere attiva questa opzione.
  • scansione
    • La scansione sembrava avere problemi di tipo simili a quelli della grande memoria, ma con tutti i meccanismi di readLines. In breve, questa volta non si adattava al conto.

3
Se i tuoi criteri sono abbastanza semplici, puoi probabilmente farla franca usando sede / o awkcreare una versione ridotta del CSV che puoi leggere direttamente. Poiché questa è più una soluzione alternativa che una risposta, la lascio come commento.
Hank Gay

Sono d'accordo con Hank - è necessario utilizzare lo strumento giusto per il lavoro, e se si tratta di semplice pulizia dei dati / eliminazione di righe irrilevanti / comando colonne strumenti del flusso di linea come sorta / sed / awk sono grandi e stanno per essere così meno risorse rispetto R o python - se fornisci un campione del formato dei tuoi file potremmo probabilmente fornire un esempio
Aaron Statham

Grande. Fateci sapere cosa scoprite.
Shane

@Hank e Aaron: in genere sono d'accordo nell'usare lo strumento giusto per il lavoro, ma dato che questo è su una macchina Windows al lavoro e sto imparando R mentre vado, ho pensato che sarebbe stato un buon esercizio rinunciare alle migliori pratiche e provalo come R solo se possibile.
FTWynn

2
Per riferimento futuro, controlla il pacchetto R data.table. La freadfunzione è molto più veloce di read.table. Usa qualcosa di simile x = fread(file_path_here, data.table=FALSE)per caricarlo come data.frameoggetto.
paleo13

Risposte:


39

Il mio tentativo con readLines. Questo pezzo di codice viene creato csvcon anni selezionati.

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)

Questo è quasi esattamente quello che stavo scrivendo. Sento che questa sarà anche la risposta migliore, dati i vincoli di memoria, i tipi misti e le righe malformate.
FTWynn

10

Non sono un esperto in questo, ma potresti prendere in considerazione l' idea di provare MapReduce , che in pratica significherebbe adottare un approccio "divide et impera". R ha diverse opzioni per questo, tra cui:

  1. mapReduce (R puro)
  2. RHIPE (che utilizza Hadoop ); vedere l'esempio 6.2.2 nella documentazione per un esempio di sottoinsiemi di file

In alternativa, R fornisce diversi pacchetti per gestire dati di grandi dimensioni che vanno al di fuori della memoria (su disco). Probabilmente potresti caricare l'intero set di dati in un bigmemoryoggetto e ridurlo completamente all'interno di R. Vedi http://www.bigmemory.org/ per una serie di strumenti per gestirlo.


Buon suggerimento, ma non ho molta esperienza con MapReduce e simili. Dovrò documentarmi.
FTWynn

bigmemorypotrebbe essere più facile provare prima, in questo caso.
Shane

10

Esiste un modo simile per leggere nei file un pezzo alla volta in R?

Sì. La funzione readChar () leggerà un blocco di caratteri senza presumere che terminino con null. Se vuoi leggere i dati in una riga alla volta puoi usare readLines () . Se leggi un blocco o una riga, esegui un'operazione, quindi scrivi i dati, puoi evitare il problema di memoria. Anche se hai voglia di avviare un'istanza di memoria di grandi dimensioni su EC2 di Amazon, puoi ottenere fino a 64 GB di RAM. Questo dovrebbe contenere il tuo file e molto spazio per manipolare i dati.

Se hai bisogno di più velocità, il consiglio di Shane di usare Map Reduce è molto buono. Tuttavia, se segui la strada per utilizzare un'istanza di grande memoria su EC2, dovresti guardare il pacchetto multicore per l'utilizzo di tutti i core su una macchina.

Se ti ritrovi a voler leggere molti gig di dati delimitati in R dovresti almeno cercare il pacchetto sqldf che ti permette di importare direttamente in sqldf da R e quindi operare sui dati dall'interno di R. Ho trovato sqldf come uno dei modi più veloci per importare gig di dati in R, come menzionato nella domanda precedente .


Terrò a mente un'istanza EC2, ma al momento devo restare sul mio desktop ed è 2 GB di RAM. sqldf sembra decisamente quello che avevo in mente. Tuttavia, si blocca anche sulle righe malformate (dovrebbero esserci 17 colonne, ma un paio di migliaia di righe ne hanno solo una). Questo richiede qualche altro metodo di pre-elaborazione o c'è un'opzione che mi manca?
FTWynn

6

C'è un nuovissimo pacchetto chiamato colbycol che ti consente di leggere solo le variabili che desideri da enormi file di testo:

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

Passa qualsiasi argomento a read.table, quindi la combinazione dovrebbe consentirti di creare un sottoinsieme piuttosto stretto.


6

Il ffpacchetto è un modo trasparente per gestire file di grandi dimensioni.

Potresti vedere il sito web del pacchetto e / o una presentazione su di esso.

spero che questo possa essere d'aiuto


5

È possibile importare i dati nel database SQLite e quindi utilizzare RSQLite per selezionare i sottoinsiemi.


Un buon piano, ma poiché questo è essenzialmente ciò che sqldf fa dietro le quinte, lo preferirei. A meno che non ci sia un modo migliore per gestire le righe malformate se usi RSQLite diretto?
FTWynn

5

E per quanto riguarda l'uso readre la read_*_chunkedfamiglia?

Quindi nel tuo caso:

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

Codice effettivo

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

Questo vale fper ogni blocco, ricordando i nomi delle colonne e combinando i risultati filtrati alla fine. Vedi ?callbackqual è la fonte di questo esempio.

Questo risulta in:

# 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

Puoi anche aumentare, chunk_sizema in questo esempio ci sono solo 4 linee.



3

Forse puoi migrare a MySQL o PostgreSQL per evitare le limitazioni di MS Access.

È abbastanza facile collegare R a questi sistemi con un connettore database basato su DBI (disponibile su CRAN).


Touche per l'utilizzo di strumenti di database migliori, ma poiché ciò comporterebbe una seccatura amministrativa (devo amare quei regolamenti amministrativi nelle grandi aziende), sto cercando di restare con quello che ho. Inoltre, cerco di ottenere il minor numero possibile di conversioni tra il file di testo che ricevo.
FTWynn

3

scan () ha sia un argomento nlines che un argomento skip. C'è qualche motivo per cui puoi semplicemente usarlo per leggere un pezzo di righe alla volta, controllando la data per vedere se è appropriato? Se il file di input è ordinato per data, puoi memorizzare un indice che ti dice quali dovrebbero essere i tuoi skip e nlines che accelererebbero il processo in futuro.


Lo controllerò, ma il file non è ordinato in base a qualcosa di utile come la data. I fornitori sembrano pensare che sia più importante ordinare in base alla regione in cui si trova una determinata contea. /
Sigh

Penso che tu abbia frainteso la sua proposta: leggi il tuo file pezzo per pezzo ed estrai solo le righe che ti servono da ogni pezzo. Non è necessario ordinare i file.
Karl Forner

1

In questi giorni, 3,5 GB non è poi così grande, posso accedere a una macchina con 244 GB di RAM (r3,8xlarge) sul cloud Amazon per $ 2,80 / ora. Quante ore ci vorranno per capire come risolvere il problema utilizzando soluzioni di tipo big data? Quanto vale il tuo tempo? Sì, ti ci vorranno un'ora o due per capire come utilizzare AWS, ma puoi apprendere le basi su un livello gratuito, caricare i dati e leggere le prime 10.000 righe in R per verificare che funzioni e poi puoi avviare un grande istanza di memoria come r3.8xlarge e leggi tutto! Solo il mio 2c.


0

Ora, 2017, suggerirei di optare per spark e sparkR.

  • la sintassi può essere scritta in un modo semplice e piuttosto simile a dplyr

  • si adatta abbastanza bene alla piccola memoria (piccola nel senso del 2017)

Tuttavia, potrebbe essere un'esperienza intimidatoria iniziare ...


-3

Vorrei scegliere un DB e quindi fare alcune query per estrarre i campioni necessari tramite DBI

Evita di importare un file csv da 3,5 GB in SQLite. O almeno ricontrolla che il tuo ENORME db rientri nei limiti di SQLite, http://www.sqlite.org/limits.html

È un dannato grande DB che hai. Vorrei scegliere MySQL se hai bisogno di velocità. Ma preparati ad aspettare molte ore prima che l'importazione finisca. A meno che tu non abbia hardware non convenzionale o stia scrivendo dal futuro ...

EC2 di Amazon potrebbe essere una buona soluzione anche per istanziare un server che esegue R e MySQL.

il mio valore di due umili penny.


18
Quanto sono grandi 3,5 GB per sqlite? Finché si utilizza un filesystem appropriato non dovrebbero esserci problemi (sto usando regolarmente dbs sqlite> 30Gb per applicazioni per utente singolo)
Aaron Statham
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.