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

StackOverflow https://stackoverflow.com/questions/1727772

  •  19-09-2019
  •  | 
  •  

Вопрос

У меня есть очень большие таблицы (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))))

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

Это было полезно?

Решение

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

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

  1. Используя fread в data.table для импорта данных из файлов csv / с разделителями в виде табуляции непосредственно в R.Видишь ответ мнела.

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

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

  4. Попытка сохранить как можно больше данных в базах данных, а не в плоских файлах.(Помимо того, что R является лучшим постоянным носителем данных, данные передаются на него и обратно в двоичном формате, что быстрее.) read.csv.sql в sqldf упаковка, как описано в Ответ Джей Ди Лонга, импортирует данные во временную базу данных 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.

Другие советы

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

стандартное чтение.таблица

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

оптимизированное чтение.таблица

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

фрейд

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 / ффдф

 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

Изначально я не видел этого вопроса и задал аналогичный вопрос несколько дней спустя.Я собираюсь опустить свой предыдущий вопрос, но я подумал, что добавлю ответ здесь, чтобы объяснить, как я использовал 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'))

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

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

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

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

Это было ранее спрошенный на Р-Помощь, так что это стоит пересмотреть.

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

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

Альтернативой является использование vroom посылка.Теперь о КРАНЕ.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

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

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

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"))
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top