Быстрое чтение очень больших таблиц как данных - PullRequest
473 голосов
/ 13 ноября 2009

У меня есть очень большие таблицы (30 миллионов строк), которые я хотел бы загрузить в виде фреймов данных в R. read.table() имеет много удобных функций, но, похоже, в реализации много логики, которая бы помедленнее. В моем случае я предполагаю, что знаю типы столбцов заранее, таблица не содержит заголовков столбцов или имен строк и не содержит патологических символов, о которых мне нужно беспокоиться.

Я знаю, что чтение таблицы в виде списка с использованием scan() может быть довольно быстрым, например ::10000

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

Но некоторые из моих попыток преобразовать это в фрейм данных, похоже, снижают производительность вышеупомянутого в 6 раз:

df <- as.data.frame(scan('myfile',sep='\t',list(url='',popularity=0,mintime=0,maxtime=0))))

Есть ли лучший способ сделать это? Или, может быть, совершенно другой подход к проблеме?

Ответы [ 9 ]

390 голосов
/ 13 ноября 2009

Обновление, спустя несколько лет

Этот ответ старый, и R перешел. Тонкая настройка read.table для более быстрой работы имеет очень мало преимуществ. Ваши варианты:

  1. Использование fread в data.table для импорта данных из файлов с разделителями csv / tab напрямую в R. См. ответ mnel .

  2. Использование read_table в readr (в CRAN с апреля 2015 г.). Это работает так же, как fread выше. readme в ссылке объясняет разницу между этими двумя функциями (readr в настоящее время претендует на «1.5-2x медленнее», чем data.table::fread).

  3. read.csv.raw из iotools предоставляет третий вариант для быстрого чтения файлов CSV.

  4. Попытка сохранить как можно больше данных в базах данных, а не в простых файлах. (Помимо того, что это лучший постоянный носитель данных, данные передаются в и из R в двоичном формате, что быстрее.) read.csv.sql в пакете sqldf, как описано в ответ JD Long , импортирует данные во временную базу данных SQLite, а затем считывает их в R. См. также: пакет RODBC, а также раздел, обратный зависимости которого DBI пакет с. MonetDB.R дает вам тип данных, который выглядит как фрейм данных, но на самом деле представляет собой MonetDB, что повышает производительность. Импортируйте данные с помощью функции monetdb.read.csv. dplyr позволяет напрямую работать с данными, хранящимися в базе данных нескольких типов.

  5. Хранение данных в двоичных форматах также может быть полезно для повышения производительности. Используйте saveRDS / readRDS (см. Ниже), пакеты h5 или rhdf5 для формата HDF5 или write_fst / read_fst из fst пакет.


Оригинальный ответ

Есть пара простых вещей, которые вы можете попробовать, используете ли вы read.table или scan.

  1. Установить nrows = количество записей в ваших данных (nmax в scan).

  2. Убедитесь, что comment.char="" отключил интерпретацию комментариев.

  3. Явно определяйте классы каждого столбца, используя colClasses в read.table.

  4. Настройка multi.line=FALSE может также улучшить производительность при сканировании.

Если ничего из этого не работает, используйте один из пакетов профилирования , чтобы определить, какие строки замедляют работу. Возможно, вы можете написать сокращенную версию read.table на основе результатов.

Другой альтернативой является фильтрация ваших данных перед тем, как вы прочитаете их в R.

Или, если проблема в том, что вы должны регулярно читать его, затем используйте эти методы для считывания данных за один раз, затем сохраните кадр данных как двоичный двоичный объект с save saveRDS, затем в следующий раз вы сможете получить его быстрее с помощью load readRDS.

266 голосов
/ 25 февраля 2013

Вот пример, который использует fread из data.table 1.8.7

Примеры приведены со страницы справки на fread с указанием времени на моем 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]

стандартный read.table

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

оптимизированный read.table

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

В итоге:

##    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
246 голосов
/ 30 ноября 2009

Изначально я не видел этот вопрос и через несколько дней задал похожий вопрос. Я собираюсь снять свой предыдущий вопрос, но я решил добавить сюда ответ, чтобы объяснить, как я использовал sqldf() для этого.

Было небольшое обсуждение относительно лучшего способа импорта 2 ГБ или более текстовых данных во фрейм данных R. Вчера я написал сообщение в блоге об использовании sqldf() для импорта данных в SQLite в качестве промежуточной области, а затем всасывания их из SQLite в R. Это очень хорошо работает для меня. Мне удалось получить 2 ГБ (3 столбца, 40 мм строки) данных за <5 минут. В отличие от этого, команда <code>read.csv выполнялась всю ночь и никогда не выполнялась.

Вот мой тестовый код:

Настройка данных теста:

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

Я перезапустил R перед запуском следующей процедуры импорта:

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

Я позволил следующей строке работать всю ночь, но она так и не завершилась:

system.time(big.df <- read.csv('bigdf.csv'))
71 голосов
/ 20 декабря 2012

Как ни странно, никто не отвечал на нижнюю часть вопроса в течение многих лет, хотя это важный вопрос - data.frame это просто списки с правильными атрибутами, поэтому, если у вас большие данные, вы не хотите использовать as.data.frame или аналогичный для списка. Гораздо быстрее просто «превратить» список в фрейм данных на месте:

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

Это не делает копию данных, поэтому она немедленная (в отличие от всех других методов). Предполагается, что вы уже установили names() в списке соответственно.

[Что касается загрузки больших данных в R - лично я дам их по столбцам в двоичные файлы и использую readBin() - это самый быстрый метод (кроме mmapping), который ограничен только скоростью диска. Анализ файлов ASCII по своей сути медленен (даже в C) по сравнению с двоичными данными.]

30 голосов
/ 13 ноября 2009

Ранее запрашивалось в R-Help , так что стоит рассмотреть.

Было предложено использовать readChar(), а затем выполнить строковые манипуляции с результатом с strsplit() и substr(). Вы можете видеть, что логика readChar намного меньше, чем read.table.

Я не знаю, является ли здесь проблема с памятью, но вы, возможно, также захотите взглянуть на пакет HadoopStreaming . В этом используется Hadoop , который представляет собой инфраструктуру MapReduce, предназначенную для работы с большими наборами данных. Для этого вы бы использовали функцию hsTableReader. Это пример (но у него есть кривая обучения, чтобы выучить 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)

Основная идея здесь - разбить импорт данных на куски. Вы могли бы даже пойти так далеко, чтобы использовать одну из параллельных структур (например, Snow) и запустить импорт данных параллельно, сегментируя файл, но, скорее всего, для больших наборов данных, которые не помогут, поскольку вы столкнетесь с ограничениями памяти, именно поэтому карта-уменьшение является лучшим подходом.

5 голосов
/ 08 мая 2019

Альтернативой является использование пакета vroom. Теперь на CRAN. vroom не загружает весь файл, он индексирует, где находится каждая запись, и читается позже, когда вы его используете.

Платите только за то, что вы используете.

См. Введение в vroom , Начало работы с vroom и эталонами vroom .

Основной обзор заключается в том, что начальное чтение огромного файла будет намного быстрее, а последующие изменения данных могут быть немного медленнее. Поэтому, в зависимости от того, что вы используете, это может быть лучшим вариантом.

См. Упрощенный пример из тестов vroom ниже, ключевые моменты, которые нужно увидеть, - это сверхбыстрое время считывания, но незначительное сокращение операций, таких как агрегирование и т.

package                 read    print   sample   filter  aggregate   total
read.delim              1m      21.5s   1ms      315ms   764ms       1m 22.6s
readr                   33.1s   90ms    2ms      202ms   825ms       34.2s
data.table              15.7s   13ms    1ms      129ms   394ms       16.3s
vroom (altrep) dplyr    1.7s    89ms    1.7s     1.3s    1.9s        6.7s
5 голосов
/ 28 ноября 2013

Незначительные дополнительные моменты, которые стоит упомянуть. Если у вас очень большой файл, вы можете на лету рассчитать количество строк (если заголовок отсутствует), используя (где bedGraph - имя вашего файла в вашем рабочем каталоге):

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

Затем вы можете использовать это либо в 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
4 голосов
/ 22 августа 2014

Часто я думаю, что это хорошая практика - хранить большие базы данных внутри базы данных (например, Postgres). Я не использую ничего слишком большого, чем (nrow * ncol) ncell = 10M, что довольно мало; но я часто нахожу, что хочу, чтобы R создавал и содержал графики, интенсивно использующие память, только когда я выполняю запросы из нескольких баз данных В будущем 32-ГБ ноутбуки некоторые из этих типов проблем с памятью исчезнут. Но привлекательность использования базы данных для хранения данных и последующего использования памяти R для получения результатов запроса и графиков все еще может быть полезной. Некоторые преимущества:

(1) Данные остаются загруженными в вашу базу данных. Вы просто заново подключаетесь в pgadmin к нужным базам данных, когда снова включаете свой ноутбук.

(2) Это правда, что R может выполнять намного больше изящных статистических и графических операций, чем SQL. Но я думаю, что SQL лучше подходит для запросов больших объемов данных, чем 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)
0 голосов
/ 18 апреля 2015

Вместо обычного read.table я чувствую, что fread - более быстрая функция. Задание дополнительных атрибутов, таких как выбор только обязательных столбцов, указание классов и строк в качестве факторов уменьшит время, необходимое для импорта файла.

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"))
...