Przycinanie dużego (3,5 GB) pliku csv do odczytu do R

87

Mam więc plik danych (oddzielony średnikami), który zawiera dużo szczegółów i niekompletne wiersze (prowadząc Access i SQL do dławienia). Jest to zestaw danych na poziomie hrabstwa podzielony na segmenty, podsegmenty i podsegmenty (łącznie ~ 200 czynników) przez 40 lat. Krótko mówiąc, jest ogromny i nie zmieści się w pamięci, jeśli spróbuję go po prostu przeczytać.

Więc moje pytanie jest takie, biorąc pod uwagę, że chcę wszystkie hrabstwa, ale tylko jeden rok (i tylko najwyższy poziom segmentu ... prowadzący do około 100 000 wierszy na końcu), jaki byłby najlepszy sposób na uzyskanie ten pakiet zbiorczy w R?

Obecnie staram się odciąć nieistotne lata z Pythonem, omijając limit rozmiaru pliku, czytając i działając w jednej linii na raz, ale wolałbym rozwiązanie tylko R (pakiety CRAN OK). Czy istnieje podobny sposób wczytywania w plikach fragmentu na raz w R?

Wszelkie pomysły będą mile widziane.

Aktualizacja:

  • Ograniczenia
    • Musi używać mojego komputera, więc nie ma instancji EC2
    • Jak najbardziej R-only. Szybkość i zasoby nie są w tym przypadku problemem ... pod warunkiem, że mój komputer nie eksploduje ...
    • Jak widać poniżej, dane zawierają typy mieszane, na których będę musiał później operować
  • Dane
    • Dane mają pojemność 3,5 GB, około 8,5 miliona wierszy i 17 kolumn
    • Kilka tysięcy wierszy (~ 2k) jest zniekształconych, a tylko jedna kolumna zamiast 17
      • Są całkowicie nieistotne i można je porzucić
    • Potrzebuję tylko ~ 100 000 wierszy z tego pliku (patrz poniżej)

Przykład danych:

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]

Chcę wyciąć kilka kolumn i wybrać dwa z 40 dostępnych lat (2009-2010 od 1980-2020), aby dane pasowały do ​​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]

Wyniki:

Po majstrowaniu przy wszystkich sugestiach zdecydowałem, że najlepiej sprawdzi się readLines, zaproponowane przez JD i Marka. Dałem Markowi czek, bo dał przykładową realizację.

Aby uzyskać ostateczną odpowiedź, odtworzyłem nieco dostosowaną wersję implementacji Marka, używając strsplit i cat, aby zachować tylko wybrane kolumny.

Należy również zauważyć, że jest to DUŻO mniej wydajne niż Python ... tak jak w przypadku, Python przeskakuje plik 3,5 GB w 5 minut, podczas gdy R zajmuje około 60 ... ale jeśli wszystko, co masz, to R, to jest to bilet.

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

Błędy według podejścia:

  • sqldf
    • Z pewnością tego będę używał do tego typu problemów w przyszłości, jeśli dane są dobrze sformułowane. Jeśli jednak tak nie jest, dławiki SQLite.
  • MapReduce
    • Szczerze mówiąc, doktorzy trochę mnie onieśmielali, więc nie próbowałem tego. Wyglądało na to, że wymagało to, aby obiekt był również w pamięci, co podważyłoby sedno sprawy, gdyby tak było.
  • bigmemory
    • To podejście jest czysto połączone z danymi, ale może obsługiwać tylko jeden typ na raz. W rezultacie wszystkie moje wektory postaci spadły, gdy zostały umieszczone w dużej tabeli. Jeśli jednak muszę zaprojektować duże zestawy danych na przyszłość, rozważałbym użycie liczb tylko po to, aby utrzymać tę opcję przy życiu.
  • skanowanie
    • Scan wydawał się mieć podobne problemy jak duża pamięć, ale z całą mechaniką readLines. Krótko mówiąc, tym razem po prostu nie pasowało do rachunku.
FTWynn
źródło
3
Jeśli twoje kryteria są wystarczająco proste, prawdopodobnie możesz uciec przy użyciu sedi / lub awkutworzeniu skróconej wersji pliku CSV, którą możesz przeczytać bezpośrednio. Ponieważ jest to raczej obejście niż odpowiedź, zostawię to w komentarzu.
Hank Gay
Zgadzam się z Hankiem - powinieneś użyć odpowiedniego narzędzia do tego zadania, a jeśli jest to proste czyszczenie danych / usuwanie nieistotnych wierszy / kolumn, narzędzia strumienia poleceń, takie jak sort / sed / awk, są świetne i będą wymagały znacznie mniej zasobów niż R lub python - jeśli podasz przykładowy format plików, prawdopodobnie moglibyśmy podać przykład
Aaron Statham
Świetny. Daj nam znać, co odkryłeś.
Shane
@Hank i Aaron: Generalnie jestem zwolennikiem używania odpowiedniego narzędzia do pracy, ale biorąc pod uwagę, że jest to na komputerze z systemem Windows w pracy i uczę się R na bieżąco, doszedłem do wniosku, że byłoby to dobre ćwiczenie, aby zrezygnować z najlepszych praktyk i spróbuj tego jako R-only, jeśli to możliwe.
FTWynn
2
W przyszłości zapoznaj się z pakietem data.table R. freadFunkcja jest znacznie szybsze niż read.table. Użyj czegoś podobnego x = fread(file_path_here, data.table=FALSE)do załadowania go jako data.frameobiektu.
paleo13

Odpowiedzi:

39

Moja próba z readLines. Ten fragment kodu tworzy się csvz wybranymi latami.

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
źródło
To prawie dokładnie to, co właśnie pisałem. Czuję, że będzie to również najlepsza odpowiedź, biorąc pod uwagę ograniczenia pamięci, mieszane typy i zniekształcone wiersze.
FTWynn
10

Nie jestem w tym ekspertem, ale możesz rozważyć wypróbowanie MapReduce , co w zasadzie oznaczałoby przyjęcie podejścia „dziel i rządź”. R ma kilka opcji, w tym:

  1. mapReduce (czyste R)
  2. RHIPE (który używa Hadoop ); patrz przykład 6.2.2 w dokumentacji, aby zapoznać się z przykładem plików podzbiorów

Alternatywnie, R udostępnia kilka pakietów do obsługi dużych danych, które wychodzą poza pamięć (na dysk). Prawdopodobnie mógłbyś załadować cały zbiór danych do bigmemoryobiektu i dokonać całkowitej redukcji w R. Zobacz http://www.bigmemory.org/, aby zapoznać się z zestawem narzędzi do obsługi tego.

Shane
źródło
Dobra sugestia, ale nie mam dużego doświadczenia z MapReduce i podobnymi. Muszę to przeczytać.
FTWynn
bigmemoryw takim przypadku łatwiej będzie Ci najpierw spróbować.
Shane
10

Czy istnieje podobny sposób wczytywania w plikach fragmentu na raz w R?

Tak. Funkcja readChar () odczyta blok znaków bez zakładania, że ​​są zakończone znakiem null. Jeśli chcesz czytać dane w jednym wierszu, możesz użyć funkcji readLines () . Jeśli czytasz blok lub linię, wykonaj operację, a następnie wypisz dane, możesz uniknąć problemu z pamięcią. Chociaż jeśli masz ochotę odpalić dużą instancję pamięci na EC2 Amazona, możesz uzyskać do 64 GB pamięci RAM. To powinno pomieścić plik i dużo miejsca na manipulowanie danymi.

Jeśli potrzebujesz większej prędkości, zalecenie Shane'a, aby użyć Map Reduce, jest bardzo dobre. Jeśli jednak zdecydujesz się na użycie dużej instancji pamięci na EC2, powinieneś przyjrzeć się pakietowi wielordzeniowemu, który używa wszystkich rdzeni na komputerze.

Jeśli chcesz wczytać wiele gigabajtów rozdzielonych danych do R, powinieneś przynajmniej zbadać pakiet sqldf, który pozwala na import bezpośrednio do sqldf z R, a następnie operowanie na danych z poziomu R. Odkryłem, że sqldf to jeden najszybszych sposobów importowania gigantycznych danych do R, jak wspomniano w poprzednim pytaniu .

JD Long
źródło
Będę pamiętał o instancji EC2, ale w tej chwili muszę trzymać się pulpitu i jest to 2 GB pamięci RAM. sqldf zdecydowanie wydaje się być tym, co miałem na myśli. Jednak dławi się również na zniekształconych wierszach (powinno być 17 kolumn, ale kilka tysięcy wierszy ma tylko jedną). Czy to wymaga innej metody przetwarzania wstępnego, czy też brakuje mi opcji?
FTWynn
6

Jest zupełnie nowy pakiet o nazwie colbycol, który pozwala odczytywać tylko wybrane zmienne z ogromnych plików tekstowych:

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

Przekazuje wszystkie argumenty do read.table, więc kombinacja powinna pozwolić ci dość ciasne podzbiór.

Ari B. Friedman
źródło
5

Możesz zaimportować dane do bazy danych SQLite, a następnie użyć RSQLite do wybrania podzbiorów.

Marek
źródło
Dobry plan, ale ponieważ to zasadniczo to, co sqldf robi za kulisami, wolałbym to. Chyba że istnieje lepszy sposób obsługi zniekształconych wierszy, jeśli używasz prostego RSQLite?
FTWynn
5

A co z używaniem readri read_*_chunkedrodziną?

Więc w twoim przypadku:

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

Rzeczywisty kod

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

Dotyczy fto każdego fragmentu, pamiętając nazwy kolumn i łącząc na końcu przefiltrowane wyniki. Zobacz, ?callbackco jest źródłem tego przykładu.

To skutkuje:

# 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

Możesz nawet zwiększyć, chunk_sizeale w tym przykładzie są tylko 4 linie.

Rentrop
źródło
4

Czy rozważałeś wielką pamięć ? Sprawdź to i to .

George Dontas
źródło
Dobry pomysł. Przyjrzę się temu.
FTWynn
3

Być może możesz przejść na MySQL lub PostgreSQL, aby uchronić się przed ograniczeniami MS Access.

Dość łatwo jest podłączyć R do tych systemów za pomocą złącza bazy danych opartego na DBI (dostępne w CRAN).

Kra
źródło
Touche do korzystania z lepszych narzędzi do baz danych, ale ponieważ wymagałoby to kłopotów administracyjnych (pokocham te przepisy administracyjne w dużych firmach), staram się trzymać tego, co mam. Ponadto dążę do jak najmniejszej liczby konwersji między otrzymanym plikiem tekstowym.
FTWynn
3

scan () ma zarówno argument nlines, jak i argument pomijania. Czy jest jakiś powód, dla którego możesz po prostu użyć tego do czytania fragmentów wierszy na raz, sprawdzając datę, aby zobaczyć, czy jest odpowiednia? Jeśli plik wejściowy jest uporządkowany według daty, możesz zapisać indeks, który mówi ci, jakie powinno być pominięcie i liczba linii, co przyspieszy proces w przyszłości.

frankc
źródło
Sprawdzę to, ale plik nie jest uporządkowany według niczego przydatnego, takiego jak data. Dostawcy wydają się sądzić, że ważniejsze jest sortowanie według regionu, w którym znajduje się dany hrabstwo. / Westchnienie ...
FTWynn
Myślę, że źle zrozumiałeś jego propozycję: przeczytaj fragment pliku po kawałku i wyodrębnij tylko potrzebne wiersze z każdego fragmentu. Pliki nie muszą być zamawiane.
Karl Forner,
1

Obecnie 3,5 GB po prostu nie jest tak duże, mogę uzyskać dostęp do maszyny z 244 GB pamięci RAM (r3,8xlarge) w chmurze Amazon za 2,80 USD / godzinę. Ile godzin zajmie Ci wymyślenie, jak rozwiązać problem za pomocą rozwiązań typu big data? Ile wart jest Twój czas? Tak, zajmie ci to godzinę lub dwie, zanim dowiesz się, jak korzystać z AWS - ale możesz nauczyć się podstaw na bezpłatnym poziomie, przesłać dane i przeczytać pierwsze 10 tys. Wierszy do R, aby sprawdzić, czy zadziałało, a następnie możesz uruchomić duża instancja pamięci, taka jak r3.8xlarge i czytaj to wszystko! Tylko mój 2c.

Sean
źródło
0

Teraz, 2017, sugerowałbym pójść na spark i sparkR.

  • składnię można zapisać w prosty, raczej podobny do dplyr sposób

  • całkiem dobrze pasuje do małej pamięci (małej jak na rok 2017)

Jednak rozpoczęcie może być przerażającym doświadczeniem ...

Ott Toomet
źródło
-3

Poszedłbym po DB, a następnie zadałbym kilka zapytań, aby wyodrębnić potrzebne próbki przez DBI

Unikaj importowania pliku csv o rozmiarze 3,5 GB do SQLite. Lub przynajmniej dwukrotnie sprawdź, czy twoja OGROMNA baza danych mieści się w limitach SQLite, http://www.sqlite.org/limits.html

Masz cholernie duży DB. Wybrałbym MySQL, jeśli potrzebujesz szybkości. Ale przygotuj się na czekanie wiele godzin na zakończenie importu. Chyba że masz jakiś niekonwencjonalny sprzęt lub piszesz z przyszłości ...

Amazon EC2 może być dobrym rozwiązaniem również do tworzenia instancji serwera z R i MySQL.

warte moje dwa skromne grosze.

Liborio Francesco Cannici
źródło
18
Jaka jest pojemność 3,5 Gb dla sqlite? Tak długo, jak używasz odpowiedniego systemu plików, nie powinno być problemu (regularnie używam> 30 GB sqlite dbs dla aplikacji dla jednego użytkownika)
Aaron Statham