Acelerando el rendimiento de write.table

Tengo un data.frame y quiero escribirlo. Las dimensiones de mi data.frame son 256 filas por 65536 columnas. ¿Cuáles son las alternativas más rápidas para write.csv ?

Si todas sus columnas son de la misma clase, conviértanse a una matriz antes de escribir, proporciona una velocidad de casi 6x. Además, puede buscar el uso de write.matrix() desde el paquete MASS , aunque no resultó más rápido en este ejemplo. Tal vez no configuré algo correctamente:

 #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 

EDITAR

Para abordar la preocupación planteada a continuación de que los resultados anteriores no son justos para data.frame, aquí hay algunos resultados más y el momento para mostrar que el mensaje general sigue siendo "convertir su objeto de datos a una matriz si es posible. Si no es posible, trate con Alternativamente, reconsidere por qué necesita escribir un archivo de 200MB + en formato CSV si el momento es de sum importancia ":

 #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 

Entonces, nada realmente cambia. Para confirmar que esto es razonable, considere los costos relativos de tiempo de as.data.frame() :

 m3 < - as.matrix(m2) system.time(as.data.frame(m3)) # user system elapsed # 0.77 0.00 0.77 

Por lo tanto, no es realmente un gran problema o sesgar información tanto como el comentario a continuación podría creer. Si aún no está convencido de que el uso de write.csv() en grandes data.frames sea una mala idea en cuanto al rendimiento, consulte el manual en la 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. 

Finalmente, considere mudarse a un objeto RData nativo si todavía está perdiendo el sueño por salvar cosas más rápido

 system.time(save(m2, file = "thisisfast.RData")) # user system elapsed # 21.67 0.12 21.81 

data.table::fwrite() fue aportado por Otto Seiskari y está disponible en las versiones 1.9.8+. Matt ha realizado mejoras adicionales en la parte superior (incluida la paralelización) y escribió un artículo al respecto. Informe cualquier problema en el rastreador .

Primero, aquí hay una comparación de las mismas dimensiones utilizadas por @chase anterior (es decir, un número muy grande de columnas: 65,000 columnas (!) X 256 filas), junto con fwrite y write_feather , para que tengamos cierta consistencia entre las máquinas. Tenga en cuenta la gran diferencia compress=FALSE hace en la base 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 | 

Tenga en cuenta que fwrite() ejecuta en paralelo. El tiempo que se muestra aquí es en un Macbook Pro de 13 ‘con 2 núcleos y 1 subproceso / núcleo (+2 subprocesos virtuales mediante hyperthreading), SSD de 512 GB, caché L2 de 256 KB / core y 4 MB de caché L4. Dependiendo de las especificaciones de tu sistema, YMMV.

También modifiqué los puntos de referencia en datos relativamente más probables (y más grandes) :

 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 | # ------------------------------------------------------------------------------- 

Así que fwrite es ~ 2 veces más rápido que la feather en esta prueba. Esto se ejecutó en la misma máquina que se indicó anteriormente con fwrite ejecutándose en paralelo en 2 núcleos.

feather parece un formato binario bastante rápido, pero aún no tiene compresión.


Aquí hay un bash de mostrar cómo se compara fwrite con respecto a la escala:

NB: el benchmark se ha actualizado ejecutando save() base R con compress = FALSE (ya que el plumero tampoco está comprimido).

megabyte

Así que fwrite es el más rápido de todos en estos datos (se ejecuta en 2 núcleos) y además crea un .csv que se puede ver, inspeccionar y pasar fácilmente a grep , sed , etc.

Código de reproducción:

 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() 

Otra opción es usar el formato de archivo de plumas .

 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 es un formato de archivo binario diseñado para ser muy eficiente para leer y escribir. Está diseñado para trabajar con múltiples idiomas: actualmente hay clientes R y python, y un cliente de Julia está en proceso.

A modo de comparación, aquí está cuánto tiempo lleva saveRDS :

 system.time(saveRDS(df, "df.rds")) #> user system elapsed #> 17.363 0.307 17.856 

Ahora bien, esta es una comparación un tanto injusta porque el valor predeterminado para saveRDS es comprimir los datos, y aquí los datos son incompresibles porque es completamente aleatorio. Desactivar la compresión hace que saveRDS sea ​​mucho más rápido:

 system.time(saveRDS(df, "df.rds", compress = FALSE)) #> user system elapsed #> 0.181 0.247 0.473 

Y, de hecho, ahora es un poco más rápido que la pluma. Entonces, ¿por qué usar plumas? Bueno, es típicamente más rápido que readRDS() , y generalmente se escriben los datos relativamente pocas veces en comparación con la cantidad de veces que se lee.

 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 

El primer paquete

Una opción más reciente para la lectura y escritura de archivos de datos es el primer paquete . fst genera archivos en formato binario.

Use write.fst(dat, "file.fst", compress=0) , donde compress puede ir de 0 (sin compresión) a 100 (compresión máxima). Los datos se pueden leer nuevamente en R con dat = read.fst("file.fst") . En función de los tiempos indicados en el sitio web del paquete , es más rápido que feather , data.table y base R readRDS y writeRDS .

El sitio de desarrollo de paquetes advierte que el primer formato de datos aún está evolucionando y que, por lo tanto, aún no debe usarse para el almacenamiento de datos a largo plazo.

También puede probar read_rds del paquete ‘readr’ (compare con data.table :: fread) y write_rds (compare con data.table :: fwrite).

Aquí hay un ejemplo simple en mi conjunto de datos (1133 filas y 429499 columnas):

escribir el conjunto de datos

fwrite(rankp2,file="rankp2_429499.txt",col.names=T,row.names=F,quote = F,sep="\t") write_rds(rankp2,"rankp2_429499.rds")

leer el conjunto de datos (1133 filas y 429499 columnas)

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

Espero eso ayude.

Creo que deberías usar fwrite ()

es mucho más rápido y me ayudó mucho:

 fwrite(x, file = "", append = FALSE, quote = "auto", sep = ",", sep2 = c("","|",""), eol = if (.Platform$OS.type=="windows") "\r\n" else "\n", na = "", dec = ".", row.names = FALSE, col.names = TRUE, qmethod = c("double","escape"), logical01 = getOption("datatable.logical01", FALSE), # due to change to TRUE; see NEWS logicalAsInt = logical01, # deprecated dateTimeAs = c("ISO","squash","epoch","write.csv"), buffMB = 8L, nThread = getDTthreads(), showProgress = interactive(), verbose = getOption("datatable.verbose", FALSE)) 

https://jangorecki.gitlab.io/data.table/library/data.table/html/fwrite.html