Быстрое чтение очень больших таблиц в виде фреймов данных
Вопрос
У меня есть очень большие таблицы (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
бег немного быстрее приносит очень мало пользы.Ваши варианты следующие:
Используя
fread
вdata.table
для импорта данных из файлов csv / с разделителями в виде табуляции непосредственно в R.Видишь ответ мнела.Используя
read_table
вreadr
(на CRAN от апреля 2015 года).Это работает очень похожеfread
выше.В прочитай меня в ссылке объясняется разница между двумя функциями (readr
в настоящее время утверждает, что работает "в 1,5-2 раза медленнее", чемdata.table::fread
).read.csv.raw
Отiotools
предоставляет третий вариант для быстрого чтения CSV-файлов.Попытка сохранить как можно больше данных в базах данных, а не в плоских файлах.(Помимо того, что R является лучшим постоянным носителем данных, данные передаются на него и обратно в двоичном формате, что быстрее.)
read.csv.sql
вsqldf
упаковка, как описано в Ответ Джей Ди Лонга, импортирует данные во временную базу данных SQLite , а затем считывает их в R.Смотрите также:вRODBC
упаковке, и обратная часть зависит отDBI
упаковка Страница.MonetDB.R
предоставляет вам тип данных, который выдает себя за фрейм данных, но на самом деле является MonetDB под ним, повышая производительность.Импортируйте данные с помощью своегоmonetdb.read.csv
функция.dplyr
позволяет вам напрямую работать с данными, хранящимися в нескольких типах баз данных.Хранение данных в двоичных форматах также может быть полезно для повышения производительности.Использование
saveRDS
/readRDS
(смотрите ниже), вh5
илиrhdf5
пакеты для формата HDF5, илиwrite_fst
/read_fst
изfst
посылка.
Оригинальный ответ
Есть пара простых вещей, которые нужно попробовать, независимо от того, используете ли вы read.table или scan.
Установить
nrows
=количество записей в ваших данных (nmax
вscan
).Убедитесь , что
comment.char=""
чтобы отключить интерпретацию комментариев.Явно определите классы каждого столбца, используя
colClasses
вread.table
.Настройка
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.frame
s - это просто списки с нужными атрибутами, поэтому, если у вас есть большие данные, которые вы не хотите использовать 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"))