Подтвердить что ты не робот

Быстрое чтение очень больших таблиц в качестве данных

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

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

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

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

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

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

4b9b3361

Ответ 1

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

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

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

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

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

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

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


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

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

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

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

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

  4. Установка multi.line=FALSE также может повысить производительность сканирования.

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

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

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

Ответ 2

Вот пример, который использует 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

Ответ 3

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

Был немного обсуждения относительно наилучшего способа импортировать 2 ГБ или более текстовых данных в фрейм данных R. Вчера я написал сообщение об использовании sqldf() для импорта данных в SQLite в качестве промежуточной области, а затем сосать его из SQLite в R. Это очень хорошо работает для меня. Я смог вытащить 2 ГБ (3 столбца, 40 мм строк) данных в < 5 минут. Напротив, команда 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'))

Ответ 4

Странно, что никто не ответил на нижнюю часть вопроса в течение многих лет, даже если это важно - 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) по сравнению с двоичными данными.]

Ответ 5

Ранее это было в 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)

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

Ответ 6

Небольшие дополнительные очки, о которых стоит упомянуть. Если у вас очень большой файл, вы можете на лету рассчитать количество строк (если нет заголовка), используя (где 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

Ответ 7

Часто я считаю, что просто хорошая практика - хранить большие базы данных в базе данных (например, 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)

Ответ 8

Альтернативой является использование пакета 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

Ответ 9

Вместо обычного 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"))