У меня очень большие таблицы (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
для работы немного быстрее принесет read.table
пользу. Ваши варианты:
Использование fread
в data.table
для импорта данных из файлов csv/tab-delimited непосредственно в R. См. Ответ mnel.
Использование read_table
в readr
(на CRAN с апреля 2015 года). Это работает так же, как fread
выше. Ссылка readme в ссылке объясняет разницу между двумя функциями (readr
настоящее время утверждает, что она "на 1,5-2 раза медленнее", чем data.table::fread
).
read.csv.raw
от iotools
предоставляет третий вариант для быстрого чтения файлов CSV.
Попытка хранить как можно больше данных в базах данных, а не в плоских файлах. (А также как лучший постоянный носитель данных, данные передаются в и из R в двоичном формате, что происходит быстрее.) read.csv.sql
в пакете sqldf
, как описано в JD Long answer, импортирует данные во временный SQLite, а затем читает его в R. См. Также: пакет RODBC
, а также раздел с обратным RODBC
на RODBC
пакета DBI
. MonetDB.R
дает вам тип данных, который претендует на роль фрейма данных, но на самом деле является MonetDB снизу, что повышает производительность. Импортируйте данные с monetdb.read.csv
функции monetdb.read.csv
. dplyr
позволяет работать непосредственно с данными, хранящимися в нескольких типах баз данных.
Сохранение данных в двоичных форматах также может быть полезно для повышения производительности. Используйте saveRDS
/readRDS
(см ниже), h5
или rhdf5
пакеты формата HDF5 или write_fst
/read_fst
от fst
пакета.
Оригинальный ответ
Есть несколько простых вещей, чтобы попробовать, используете ли вы read.table или сканирование.
Set 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
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
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
fread
. Попытался прочитать его с помощью базовых функций R, и это заняло около 15 часов.
Я не рассматривал этот вопрос на начальном этапе и задал аналогичный вопрос несколько дней спустя. Я собираюсь снять свой предыдущий вопрос, но я подумал, что добавлю здесь ответ, чтобы объяснить, как я использовал 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'))
read.csv.sql
Странно, что никто не ответил на нижнюю часть вопроса в течение многих лет, даже если это важно - 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) по сравнению с двоичными данными.]
tracmem
предполагает, что attr<-
и class<-
делают копии внутри. bit::setattr
или data.table::setattr
не будут.
df=scan(...); names(df)=...; attr...; class...
- см. tracemem()
(протестировано в R 2.15.2)
Ранее это было в 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 является лучшим подходом.
Небольшие дополнительные очки, о которых стоит упомянуть. Если у вас очень большой файл, вы можете на лету рассчитать количество строк (если нет заголовка), используя (где 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
Часто я считаю, что просто хорошая практика - хранить большие базы данных в базе данных (например, 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 - это более быстрая функция. Указание дополнительных атрибутов, таких как выбор только необходимых столбцов, определение колласс и строки в качестве факторов, сократит время, необходимое для импорта файла.
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"))