У меня есть data.frame
, и я хочу его написать. Размеры моего data.frame
составляют 256 строк по 65536 столбцам. Каковы более быстрые альтернативы write.csv
?
Ускорение производительности write.table
Ответ 1
Если все ваши столбцы имеют один и тот же класс, преобразование в матрицу перед записью, обеспечивает почти 6-кратное ускорение. Кроме того, вы можете изучить write.matrix()
из пакета MASS
, хотя в этом примере он не оказался более быстрым. Возможно, я ничего не установил правильно:
#Fake data
m <- matrix(runif(256*65536), nrow = 256)
#AS a data.frame
system.time(write.csv(as.data.frame(m), "dataframe.csv"))
#----------
# user system elapsed
# 319.53 13.65 333.76
#As a matrix
system.time(write.csv(m, "matrix.csv"))
#----------
# user system elapsed
# 52.43 0.88 53.59
#Using write.matrix()
require(MASS)
system.time(write.matrix(m, "writematrix.csv"))
#----------
# user system elapsed
# 113.58 59.12 172.75
ИЗМЕНИТЬ
Чтобы устранить обеспокоенность, высказанную ниже, результаты, приведенные выше, не справедливы для data.frame, вот еще несколько результатов и времени, чтобы показать, что общее сообщение по-прежнему "конвертирует ваш объект данных в матрицу, если это возможно"., рассмотрите его. В качестве альтернативы пересмотреть, почему вам нужно записать файл размером 200 МБ + в формате CSV, если время имеет первостепенное значение ":
#This is a data.frame
m2 <- as.data.frame(matrix(runif(256*65536), nrow = 256))
#This is still 6x slower
system.time(write.csv(m2, "dataframe.csv"))
# user system elapsed
# 317.85 13.95 332.44
#This even includes the overhead in converting to as.matrix in the timing
system.time(write.csv(as.matrix(m2), "asmatrix.csv"))
# user system elapsed
# 53.67 0.92 54.67
Итак, ничего действительно не меняется. Чтобы это было обосновано, рассмотрите относительные затраты времени as.data.frame()
:
m3 <- as.matrix(m2)
system.time(as.data.frame(m3))
# user system elapsed
# 0.77 0.00 0.77
Таким образом, на самом деле это не очень важная информация или информация об искажениях, как это может показаться ниже. Если вы все еще не уверены, что использование write.csv()
на больших data.frames - это плохая идея, разумно, обратитесь к руководству в разделе Note
:
write.table can be slow for data frames with large numbers (hundreds or more) of
columns: this is inevitable as each column could be of a different class and so must be
handled separately. If they are all of the same class, consider using a matrix instead.
Наконец, подумайте о переходе к собственному объекту RData, если вы все еще теряете сон за сбережение быстрее.
system.time(save(m2, file = "thisisfast.RData"))
# user system elapsed
# 21.67 0.12 21.81
Ответ 2
data.table::fwrite()
был предоставлен Otto Seiskari и доступен в версиях 1.9.8+. Мэтт сделал дополнительные улучшения сверху (включая параллелизацию) и написал статью об этом. Пожалуйста, сообщите о любых проблемах в трекер.
Во-первых, здесь сравнение по тем же размерам, что и @chase выше (т.е. очень большое количество столбцов: 65 000 столбцов (!) x 256 строк) вместе с fwrite
и write_feather
, так что мы имеем некоторую согласованность между машинами. Обратите внимание на огромную разницу compress=FALSE
в базе R.
# -----------------------------------------------------------------------------
# function | object type | output type | compress= | Runtime | File size |
# -----------------------------------------------------------------------------
# save | matrix | binary | FALSE | 0.3s | 134MB |
# save | data.frame | binary | FALSE | 0.4s | 135MB |
# feather | data.frame | binary | FALSE | 0.4s | 139MB |
# fwrite | data.table | csv | FALSE | 1.0s | 302MB |
# save | matrix | binary | TRUE | 17.9s | 89MB |
# save | data.frame | binary | TRUE | 18.1s | 89MB |
# write.csv | matrix | csv | FALSE | 21.7s | 302MB |
# write.csv | data.frame | csv | FALSE | 121.3s | 302MB |
Обратите внимание, что fwrite()
выполняется параллельно. Показанное здесь время представлено на 13-дюймовом MacBook Pro с 2 ядрами и 1 потоком/ядром (+2 виртуальных потока через гиперпоточность), 512 ГБ SSD, кеш второго уровня 256 КБ/кэш и 4 МБ кэша L4. В зависимости от вашей спецификации системы, YMMV.
Я также пересматриваю тесты относительно более вероятных (и больших) данных:
library(data.table)
NN <- 5e6 # at this number of rows, the .csv output is ~800Mb on my machine
set.seed(51423)
DT <- data.table(
str1 = sample(sprintf("%010d",1:NN)), #ID field 1
str2 = sample(sprintf("%09d",1:NN)), #ID field 2
# varying length string field--think names/addresses, etc.
str3 = replicate(NN,paste0(sample(LETTERS,sample(10:30,1),T), collapse="")),
# factor-like string field with 50 "levels"
str4 = sprintf("%05d",sample(sample(1e5,50),NN,T)),
# factor-like string field with 17 levels, varying length
str5 = sample(replicate(17,paste0(sample(LETTERS, sample(15:25,1),T),
collapse="")),NN,T),
# lognormally distributed numeric
num1 = round(exp(rnorm(NN,mean=6.5,sd=1.5)),2),
# 3 binary strings
str6 = sample(c("Y","N"),NN,T),
str7 = sample(c("M","F"),NN,T),
str8 = sample(c("B","W"),NN,T),
# right-skewed (integer type)
int1 = as.integer(ceiling(rexp(NN))),
num2 = round(exp(rnorm(NN,mean=6,sd=1.5)),2),
# lognormal numeric that can be positive or negative
num3 = (-1)^sample(2,NN,T)*round(exp(rnorm(NN,mean=6,sd=1.5)),2))
# -------------------------------------------------------------------------------
# function | object | out | other args | Runtime | File size |
# -------------------------------------------------------------------------------
# fwrite | data.table | csv | quote = FALSE | 1.7s | 523.2MB |
# fwrite | data.frame | csv | quote = FALSE | 1.7s | 523.2MB |
# feather | data.frame | bin | no compression | 3.3s | 635.3MB |
# save | data.frame | bin | compress = FALSE | 12.0s | 795.3MB |
# write.csv | data.frame | csv | row.names = FALSE | 28.7s | 493.7MB |
# save | data.frame | bin | compress = TRUE | 48.1s | 190.3MB |
# -------------------------------------------------------------------------------
Итак, fwrite
в этом тесте ~ 2x быстрее, чем feather
. Это было выполнено на той же машине, что указано выше, при этом fwrite
работает параллельно на 2 ядрах.
feather
кажется довольно быстрым двоичным форматом, но пока не сжатие.
Здесь попытка показать, как fwrite
сравнивается по шкале:
NB: эталон был обновлен путем запуска базы R save()
с помощью compress = FALSE
(так как перо также не сжато).
Так что fwrite
является самым быстрым из всех этих данных (работает на 2 ядрах) плюс создает .csv
, который можно легко просмотреть, проверить и передать в grep
, sed
и т.д.
Код для воспроизведения:
require(data.table)
require(microbenchmark)
require(feather)
ns <- as.integer(10^seq(2, 6, length.out = 25))
DTn <- function(nn)
data.table(
str1 = sample(sprintf("%010d",1:nn)),
str2 = sample(sprintf("%09d",1:nn)),
str3 = replicate(nn,paste0(sample(LETTERS,sample(10:30,1),T), collapse="")),
str4 = sprintf("%05d",sample(sample(1e5,50),nn,T)),
str5 = sample(replicate(17,paste0(sample(LETTERS, sample(15:25,1),T), collapse="")),nn,T),
num1 = round(exp(rnorm(nn,mean=6.5,sd=1.5)),2),
str6 = sample(c("Y","N"),nn,T),
str7 = sample(c("M","F"),nn,T),
str8 = sample(c("B","W"),nn,T),
int1 = as.integer(ceiling(rexp(nn))),
num2 = round(exp(rnorm(nn,mean=6,sd=1.5)),2),
num3 = (-1)^sample(2,nn,T)*round(exp(rnorm(nn,mean=6,sd=1.5)),2))
count <- data.table(n = ns,
c = c(rep(1000, 12),
rep(100, 6),
rep(10, 7)))
mbs <- lapply(ns, function(nn){
print(nn)
set.seed(51423)
DT <- DTn(nn)
microbenchmark(times = count[n==nn,c],
write.csv=write.csv(DT, "writecsv.csv", quote=FALSE, row.names=FALSE),
save=save(DT, file = "save.RData", compress=FALSE),
fwrite=fwrite(DT, "fwrite_turbo.csv", quote=FALSE, sep=","),
feather=write_feather(DT, "feather.feather"))})
png("microbenchmark.png", height=600, width=600)
par(las=2, oma = c(1, 0, 0, 0))
matplot(ns, t(sapply(mbs, function(x) {
y <- summary(x)[,"median"]
y/y[3]})),
main = "Relative Speed of fwrite (turbo) vs. rest",
xlab = "", ylab = "Time Relative to fwrite (turbo)",
type = "l", lty = 1, lwd = 2,
col = c("red", "blue", "black", "magenta"), xaxt = "n",
ylim=c(0,25), xlim=c(0, max(ns)))
axis(1, at = ns, labels = prettyNum(ns, ","))
mtext("# Rows", side = 1, las = 1, line = 5)
legend("right", lty = 1, lwd = 3,
legend = c("write.csv", "save", "feather"),
col = c("red", "blue", "magenta"))
dev.off()
Ответ 3
Другой вариант - использовать формат feather.
df <- as.data.frame(matrix(runif(256*65536), nrow = 256))
system.time(feather::write_feather(df, "df.feather"))
#> user system elapsed
#> 0.237 0.355 0.617
Feather - это формат двоичного файла, который очень эффективен для чтения и записи. Он предназначен для работы с несколькими языками: в настоящее время существуют клиенты R и python, а клиент julia работает.
Для сравнения, здесь, как долго saveRDS
принимает:
system.time(saveRDS(df, "df.rds"))
#> user system elapsed
#> 17.363 0.307 17.856
Теперь это несколько несправедливое сравнение, потому что по умолчанию для saveRDS
заключается в сжатии данных, и здесь данные несжимаемы, потому что они полностью случайны. Сжатие сжимания делает saveRDS
значительно быстрее:
system.time(saveRDS(df, "df.rds", compress = FALSE))
#> user system elapsed
#> 0.181 0.247 0.473
И действительно, это теперь немного быстрее, чем перо. Так зачем использовать перо? Это обычно быстрее, чем readRDS()
, и вы обычно записываете данные относительно немного раз по сравнению с количеством раз, которое вы читали.
system.time(readRDS("df.rds"))
#> user system elapsed
#> 0.198 0.090 0.287
system.time(feather::read_feather("df.feather"))
#> user system elapsed
#> 0.125 0.060 0.185
Ответ 4
Вы также можете попробовать 'readr' package read_rds (по сравнению с data.table:: fread) и write_rds (по сравнению с data.table:: fwrite).
Вот простой пример в моем наборе данных (1133 строки и 429499 столбцов):
записать набор данных
fwrite(rankp2,file="rankp2_429499.txt",col.names=T,row.names=F,quote = F,sep="\t")
write_rds(rankp2,"rankp2_429499.rds")
прочитать набор данных (1133 строки и столбцы 429499)
system.time(fread("rankp2_429499.txt",sep="\t",header=T,fill = TRUE))
user system elapsed
42.391 0.526 42.949
system.time(read_rds("rankp2_429499.rds"))
user system elapsed
2.157 0.388 2.547
Надеюсь, что это поможет.
Ответ 5
fst
package
Более поздняя опция для быстрого чтения и записи файлов данных - это fst
пакет. fst
генерирует файлы в двоичном формате.
Используйте write.fst(dat, "file.fst", compress=0)
, где compress
может перейти от 0 (без сжатия) до 100 (максимальное сжатие). Данные можно считать в R с помощью dat = read.fst("file.fst")
. На основе таймингов, перечисленных на веб-сайте он быстрее, чем feather
, data.table
и базовый R readRDS
и writeRDS
,
Сайт разработки пакета предупреждает, что формат данных fst
все еще развивается и что fst
поэтому не должен использоваться для долговременного хранения данных.