Szybkie odczytywanie bardzo dużych tabel jako ramek danych

Mam bardzo duże tabele (30 milionów wierszy), które chciałbym załadować jako ramki danych w R. read.table() ma wiele wygodnych funkcji, ale wydaje się, że w implementacji jest wiele logiki, która spowolniłaby działanie. W moim przypadku zakładam, że znam typy kolumn z wyprzedzeniem, tabela nie zawiera nagłówków kolumn ani nazw wierszy i nie ma żadnych patologicznych znaków, którymi muszę się martwić.

Wiem, że czytanie w tabeli jako lista używanie scan() może być dość szybkie, np.:

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

Ale niektóre z moich prób przekonwertowania tego na ramkę danych wydają się zmniejszać wydajność powyższego o współczynnik 6:

df <- as.data.frame(scan('myfile',sep='\t',list(url='',popularity=0,mintime=0,maxtime=0))))
Czy jest na to lepszy sposób? A może zupełnie inne podejście do problemu?
Author: Uwe Keim, 2009-11-13

8 answers

W 1998 roku został wybrany do Izby Gmin.]}

Ta odpowiedź jest Stara, A R ruszył dalej. Podkręcanie read.table bieganie nieco szybciej ma cenne niewiele korzyści. Twoje opcje to:

  1. Za pomocą fread w data.table aby zaimportować dane z plików CSV / tab-delimited bezpośrednio do R. Zobacz odpowiedź mnel.

  2. Za pomocą read_table w readr (od kwietnia 2015). To działa podobnie jak fread powyżej. readme w linku wyjaśnia różnicę między dwiema funkcjami (readr obecnie twierdzi ,że jest "1,5-2x wolniejsza" niż data.table::fread).

  3. read.csv.raw od iotools zapewnia trzecią opcję szybkiego odczytu plików CSV.

  4. Staramy się przechowywać jak najwięcej danych w bazach danych, a nie plikach płaskich. (Oprócz tego, że jest lepszym trwałym nośnikiem danych, dane są przekazywane do i z R w formacie binarnym, który jest szybszy.) read.csv.sql w sqldf pakiet, jak opisano w odpowiedzi JD Long, importuje dane do tymczasowej bazy danych SQLite, a następnie wczytuje je do R. Zobacz także: RODBC pakiet, a odwrotna część zależy od DBI pakiet strona. MonetDB.R daje typ danych, który udaje ramkę danych, ale tak naprawdę jest MonetDB pod spodem, zwiększając wydajność. Import danych z its monetdb.read.csv funkcja. dplyr pozwala możesz pracować bezpośrednio z danymi przechowywanymi w kilku rodzajach baz danych.

  5. Przechowywanie danych w formatach binarnych może być również przydatne do poprawy wydajności. Użycie saveRDS/readRDS (patrz niżej),h5 lub rhdf5 pakiety dla formatu HDF5 lub write_fst/read_fst z fst paczka.


Oryginalna odpowiedź

Jest kilka prostych rzeczy do wypróbowania, niezależnie od tego, czy używasz read.tabela lub skan.

  1. Set nrows=liczba rekordów w Twoich danych (nmax W scan).

  2. Upewnij się, że comment.char="", aby wyłączyć interpretację komentarzy.

  3. Jawnie Definiuj klasy każdej kolumny używając colClasses W read.table.

  4. Ustawienie multi.line=FALSE może również poprawić wydajność skanowania.

Jeśli żadna z tych rzeczy nie działa, użyj jednego z pakietów profilujących, aby określić, które linie spowalniają wszystko. Być może możesz napisać skróconą wersję read.table Na podstawie wyników.

Inną alternatywą jest filtrowanie danych przed odczytaniem ich do R.

Lub, jeśli problem polega na tym, że musisz czytać je regularnie, użyj tych metod, aby odczytać dane raz, a następnie zapisz ramkę danych jako binarny obiekt blob z save saveRDS, następnie następnym razem można go odzyskać szybciej z load readRDS.

 365
Author: Richie Cotton,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-02-12 09:35:30

Oto przykład, który wykorzystuje fread z data.table 1.8.7

Przykłady pochodzą ze strony pomocy do fread, z czasem na moim 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]

Standardowy odczyt.tabela

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

Zoptymalizowany odczyt.tabela

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

W podsumowaniu:

##    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
 256
Author: mnel,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-02-25 01:56:36

Początkowo nie widziałem tego pytania i zadałem podobne pytanie kilka dni później. Zamierzam zdjąć moje poprzednie pytanie, ale pomyślałem, że dodam tutaj odpowiedź, aby wyjaśnić, jak użyłem sqldf(), aby to zrobić.

Nie było trochę dyskusji na temat najlepszego sposobu zaimportowania 2 GB lub więcej danych tekstowych do ramki danych R. Wczoraj napisałem post na blogu o używaniu sqldf() do importowania danych do SQLite jako obszaru postojowego, a następnie ssania ich z SQLite do R. To działa naprawdę dobrze dla mnie. Udało mi się pobrać 2GB (3 kolumny, 40mm wiersze) danych w read.csv działała całą noc i nigdy nie została ukończona.

Oto Mój kod testowy:

Ustaw dane testowe:

bigdf <- data.frame(dim=sample(letters, replace=T, 4e7), fact1=rnorm(4e7), fact2=rnorm(4e7, 20, 50))
write.csv(bigdf, 'bigdf.csv', quote = F)

Uruchomiłem ponownie R Przed uruchomieniem następującej procedury importu:

library(sqldf)
f <- file("bigdf.csv")
system.time(bigdf <- sqldf("select * from f", dbname = tempfile(), file.format = list(header = T, row.names = F)))

Pozwoliłem, aby następująca linia działała całą noc, ale nigdy nie została ukończona:

system.time(big.df <- read.csv('bigdf.csv'))
 243
Author: JD Long,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-11-21 21:36:20

O dziwo, nikt nie odpowiedział na dolną część pytania od lat, nawet jeśli jest to ważne -- data.frameS są po prostu listami z odpowiednimi atrybutami, więc jeśli masz duże dane, nie chcesz używać as.data.frame lub podobnych do listy. O wiele szybciej jest po prostu "zamienić" listę w ramkę danych w miejscu:

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

To nie tworzy kopii danych, więc jest natychmiastowa(w przeciwieństwie do wszystkich innych metod). Zakłada ona, że odpowiednio Ustawiłeś names() na liście.

[Jak dla Ładowanie dużych danych do R -- osobiście wrzucam je kolumną do plików binarnych i używam readBin() - jest to zdecydowanie najszybsza Metoda (inna niż mm) i jest ograniczona tylko szybkością dysku. Parsowanie plików ASCII jest z natury powolne (nawet w C) w porównaniu z danymi binarnymi.]

 70
Author: Simon Urbanek,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-02-24 21:42:54

To było wcześniej zapytane na R-Help, warto to przejrzeć.

Jedną z sugestii było użycie readChar(), a następnie wykonanie manipulacji łańcuchami na wyniku za pomocą strsplit() i substr(). Widać, że logika związana z readChar jest znacznie mniejsza niż read.stolik.

Nie wiem, czy pamięć jest tutaj problemem, ale możesz również rzucić okiem naPakiet HadoopStreaming . Ten używa Hadoop , który jest frameworkiem MapReduce zaprojektowanym do obsługi dużych zbiorów danych. W tym celu należy użyć funkcji hsTableReader. Jest to przykład (ale ma krzywą uczenia się, aby nauczyć się Hadoop): {]}

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)

Podstawową ideą jest rozbicie importu danych na kawałki. Snow) i uruchomić import danych równolegle poprzez segmentację pliku, ale najprawdopodobniej w przypadku dużych zestawów danych, które nie pomogą, ponieważ napotkasz ograniczenia pamięci, dlatego map-reduce jest bardzo przydatny. lepsze podejście.

 30
Author: Shane,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-10-11 09:34:14

Drobne dodatkowe punkty warte wspomnienia. Jeśli masz bardzo duży plik, możesz w locie obliczyć liczbę wierszy (jeśli nie ma nagłówka) używając (gdzie bedGraph jest nazwą Twojego pliku w katalogu roboczym):

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

Możesz użyć tego albo w 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
 5
Author: Stephen Henderson,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-11-28 17:20:55

Często myślę, że dobrą praktyką jest trzymanie większych baz danych wewnątrz bazy (np. Postgres). Nie używam niczego znacznie większego niż (nrow * ncol) Ncell = 10M, co jest dość małe; ale często uważam, że chcę r tworzyć i przechowywać wykresy intensywne pamięci tylko podczas zapytań z wielu baz danych. W przyszłości laptopów o pojemności 32 GB niektóre tego typu problemy z pamięcią znikną. Ale urok używania bazy danych do przechowywania danych, a następnie używania pamięci R do wynikowe wyniki zapytań i wykresy nadal mogą być przydatne. Niektóre zalety to:

(1) dane pozostają załadowane w bazie danych. Po włączeniu laptopa po prostu ponownie łączysz się w pgadmin z wybranymi bazami danych.

(2) prawdą jest, że R może wykonać o wiele więcej sprytnych operacji statystycznych i graficznych niż SQL. Ale myślę, że SQL jest lepiej zaprojektowany do odpytywania dużych ilości danych niż 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)
 4
Author: rferrisx,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-08-24 15:54:25

Zamiast konwencjonalnego odczytu.stół uważam, że fread jest szybszą funkcją. Określenie dodatkowych atrybutów, takich jak select only the required columns, określenie colclasses i string jako czynników skraca czas potrzebny na zaimportowanie pliku.

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
Author: Aayush Agrawal,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-04-18 07:22:01