r io large-files

forma más rápida de copiar las primeras líneas X de un archivo a otro dentro de R?(multiplataforma)



io large-files (8)

No puedo cargar el archivo en la RAM (suponiendo que un usuario pueda querer los primeros mil millones de un archivo con diez mil millones de registros)

esta es mi solución, pero creo que tiene que haber una manera más rápida?

Gracias

# specified by the user infile <- "/some/big/file.txt" outfile <- "/some/smaller/file.txt" num_lines <- 1000 # my attempt incon <- file( infile , "r") outcon <- file( outfile , "w") for ( i in seq( num_lines ) ){ line <- readLines( incon , 1 ) writeLines( line , outcon ) } close( incon ) close( outcon )


Me gustan las tuberías, ya que podemos usar otras herramientas. Y convenientemente, la interfaz de conexiones (realmente excelente) en R lo admite:

## scratch file filename <- "foo.txt" ## create a file, no header or rownames for simplicity write.table(1:50, file=filename, col.names=FALSE, row.names=FALSE) ## sed command: print from first address to second, here 4 to 7 ## the -n suppresses output unless selected cmd <- paste0("sed -n -e ''4,7p'' ", filename) ##print(cmd) # to debug if needed ## we use the cmd inside pipe() as if it was file access so ## all other options to read.csv (or read.table) are available too val <- read.csv(pipe(cmd), header=FALSE, col.names="selectedRows") print(val, row.names=FALSE) ## clean up unlink(filename)

Si ejecutamos esto, obtenemos las filas cuatro a siete como se esperaba:

edd@max:/tmp$ r piper.R selectedRows 4 5 6 7 edd@max:/tmp$

Tenga en cuenta que nuestro uso de sed no hizo suposiciones sobre las estructuras de archivos, además de asumir

  • archivo de texto "ascii" estándar para leer en modo texto
  • terminaciones de línea CR / LF estándar como ''separadores de registros''

Si asumió archivos binarios con diferentes separadores de registros, podríamos sugerir soluciones diferentes.

También tenga en cuenta que controla el comando pasado a las funciones pipe() . Por lo tanto, si desea las filas 1000004 a 1000007, el uso es exactamente el mismo: solo debe dar la primera y la última fila (de cada segmento, puede haber varias). Y en lugar de read.csv() su readLines() podría usarse igualmente bien.

Por último, sed está disponible en todas partes y, si la memoria sirve, también forma parte de Rtools. La funcionalidad básica de filtrado también se puede obtener con Perl o una serie de otras herramientas.


Normalmente acelero esos bucles leyendo y escribiendo en trozos de, digamos, 1000 líneas. Si num_lines es un múltiplo de 1000, el código se convierte en:

# specified by the user infile <- "/some/big/file.txt" outfile <- "/some/smaller/file.txt" num_lines <- 1000000 # my attempt incon <- file( infile, "r") outcon <- file( outfile, "w") step1 = 1000 nsteps = ceiling(num_lines/step1) for ( i in 1:nsteps ){ line <- readLines( incon, step1 ) writeLines( line, outcon ) } close( incon ) close( outcon )


intenta usar

line<-read.csv(infile,nrow=1000) write(line,file=outfile,append=T)


Puede usar ff::read.table.ffdf para esto. Almacena los datos en el disco duro y no usa ninguna RAM.

library(ff) infile <- read.table.ffdf(file = "/some/big/file.txt")

Básicamente, puede utilizar la función anterior de la misma manera que base::read.table con la diferencia de que el objeto resultante se almacenará en el disco duro.

También puede usar el argumento nrow y cargar una cantidad específica de filas. La documentación está aquí si quieres leer. Una vez que haya leído el archivo, puede subconjuntar las filas específicas que necesita e incluso convertirlas a data.frames de data.frames si pueden caber en la RAM.

También hay una función write.table.ffdf que le permitirá escribir un objeto ffdf (resultante de read.table.ffdf ) que facilitará aún más el proceso.

Como ejemplo de cómo usar read.table.ffdf (o read.delim.ffdf que es más o menos lo mismo) vea lo siguiente:

#writting a file on my current directory #note that there is no standard number of columns sink(file=''test.txt'') cat(''foo , foo, foo/n'') cat(''foo, foo/n'') cat(''bar bar , bar/n'') sink() #read it with read.delim.ffdf or read.table.ffdf read.delim.ffdf(file=''test.txt'', sep=''/n'', header=F)

Salida:

ffdf (all open) dim=c(3,1), dimorder=c(1,2) row.names=NULL ffdf virtual mapping PhysicalName VirtualVmode PhysicalVmode AsIs VirtualIsMatrix PhysicalIsMatrix PhysicalElementNo PhysicalFirstCol PhysicalLastCol PhysicalIsOpen V1 V1 integer integer FALSE FALSE FALSE 1 1 1 TRUE ffdf data V1 1 foo , foo, foo 2 foo, foo 3 bar bar , bar

Si está utilizando un archivo txt, esta es una solución general ya que cada línea terminará con un carácter /n .


El sistema operativo es el mejor nivel para realizar grandes manipulaciones de archivos. Esto es rápido y viene con un punto de referencia (que parece importante, dado que el afiche preguntó por un método más rápido):

# create test file in shell echo "hello world" > file.txt for i in {1..29}; do cat file.txt file.txt > file2.txt && mv file2.txt file.txt; done wc -l file.txt # about a billion rows

Esto toma unos segundos para miles de millones de filas. Cambia de 29 a 32 para obtener unos diez mil millones.

Luego, en R, usando diez millones de filas de los mil millones (cientos de millones de forma demasiado lenta para compararla con la solución del póster)

# in R, copy first ten million rows of the billion system.time( system("head -n 10000000 file.txt > out.txt") ) # posters solution system.time({ infile <- "file.txt" outfile <- "out.txt" num_lines <- 1e7 incon <- file( infile , "r") outcon <- file( outfile , "w") for ( i in seq( num_lines )) { line <- readLines( incon , 1 ) writeLines( line , outcon ) } close( incon ) close( outcon ) })

Y los resultados en un MacBook Pro de rango medio, hace un par de años.

Rscript head.R user system elapsed 1.349 0.164 1.581 user system elapsed 620.665 3.614 628.260

Estaría interesado en ver qué tan rápido son las otras soluciones.


La respuesta "correcta" o la mejor para esto sería usar un lenguaje que funcione mucho más fácilmente con Filehandles. Por ejemplo, aunque perl es un lenguaje feo de muchas maneras, aquí es donde brilla. Python también puede hacer esto muy bien, de una manera más detallada.

Sin embargo, usted ha declarado explícitamente que quiere cosas en R. En primer lugar, supondré que esto podría no ser un CSV u otro archivo plano delimitado.

Use el readr la biblioteca. Dentro de esa biblioteca, use read_lines() . Algo así (primero, obtenga el n. ° de líneas en todo el archivo, usando algo como lo que se muestra aquí ):

library(readr) # specified by the user infile <- "/some/big/file.txt" outfile <- "/some/smaller/file.txt" num_lines <- 1000 # readr attempt # num_lines_tot is found via the method shown in the link above num_loops <- ceiling(num_lines_tot / num_lines) incon <- file( infile , "r") outcon <- file( outfile , "w") for ( i in seq(num_loops) ){ lines <- read_lines(incon, skip= (i - 1) * num_lines, n_max = num_lines) writeLines( lines , outcon ) } close( incon ) close( outcon )

Algunas cosas a tener en cuenta:

  1. No hay una manera agradable y conveniente de escribir en el readr la biblioteca que sea tan genérico como parezca desear. (Hay, por ejemplo, write_delim , pero no especificó delimitado).
  2. Toda la información que está en las encarnaciones previas del "outfile" se perderá. No estoy seguro de si pretendía abrir "outfile" en el modo de agregar ( "a" ), pero sospecho que sería útil.
  3. He encontrado que cuando trabajo con archivos de gran tamaño como este, a menudo querré filtrar los datos, al abrirlos así. Hacer la copia simple parece extraño. Tal vez quieres hacer más?
  4. Si tenía un archivo delimitado, querría ver read_csv o read_delim dentro del paquete readr .

Pruebe la utilidad principal. Debe estar disponible en todos los sistemas operativos compatibles con R (en Windows se supone que tiene Rtools instalados y que el directorio Rtools bin está en su ruta). Por ejemplo, para copiar las primeras 100 líneas de in.dat a out.dat:

shell("head -n 100 in.dat > out.dat")


Solución C ++

No es demasiado difícil escribir algún código de C ++ para esto:

#include <fstream> #include <R.h> #include <Rdefines.h> extern "C" { // [[Rcpp::export]] SEXP dump_n_lines(SEXP rin, SEXP rout, SEXP rn) { // no checks on types and size std::ifstream strin(CHAR(STRING_ELT(rin, 0))); std::ofstream strout(CHAR(STRING_ELT(rout, 0))); int N = INTEGER(rn)[0]; int n = 0; while (strin && n < N) { char c = strin.get(); if (c == ''/n'') ++n; strout.put(c); } strin.close(); strout.close(); return R_NilValue; } }

Cuando se guarda como su yourfile.cpp , puede hacer

Rcpp::sourceCpp(''yourfile.cpp'')

Desde RStudio no tienes que cargar nada. En la consola, deberás cargar Rcpp. Probablemente tengas que instalar Rtools en Windows.

R-code más eficiente

Al leer bloques más grandes en lugar de líneas simples, su código también se acelerará:

dump_n_lines2 <- function(infile, outfile, num_lines, block_size = 1E6) { incon <- file( infile , "r") outcon <- file( outfile , "w") remain <- num_lines while (remain > 0) { size <- min(remain, block_size) lines <- readLines(incon , n = size) writeLines(lines , outcon) # check for eof: if (length(lines) < size) break remain <- remain - size } close( incon ) close( outcon ) }

Punto de referencia

lines <- "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean commodo imperdiet nunc, vel ultricies felis tincidunt sit amet. Aliquam id nulla eu mi luctus vestibulum ac at leo. Integer ultrices, mi sit amet laoreet dignissim, orci ligula laoreet diam, id elementum lorem enim in metus. Quisque orci neque, vulputate ultrices ornare ac, interdum nec nunc. Suspendisse iaculis varius dapibus. Donec eget placerat est, ac iaculis ipsum. Pellentesque rhoncus maximus ipsum in hendrerit. Donec finibus posuere libero, vitae semper neque faucibus at. Proin sagittis lacus ut augue sagittis pulvinar. Nulla fermentum interdum orci, sed imperdiet nibh. Aliquam tincidunt turpis sit amet elementum porttitor. Aliquam lectus dui, dapibus ut consectetur id, mollis quis magna. Donec dapibus ac magna id bibendum." lines <- rep(lines, 1E6) writeLines(lines, con = "big.txt") infile <- "big.txt" outfile <- "small.txt" num_lines <- 1E6L library(microbenchmark) microbenchmark( solution0(infile, outfile, num_lines), dump_n_lines2(infile, outfile, num_lines), dump_n_lines(infile, outfile, num_lines) )

Resultados en (solución0 es la solución original de OP):

Unit: seconds expr min lq mean median uq max neval cld solution0(infile, outfile, num_lines) 11.523184 12.394079 12.635808 12.600581 12.904857 13.792251 100 c dump_n_lines2(infile, outfile, num_lines) 6.745558 7.666935 7.926873 7.849393 8.297805 9.178277 100 b dump_n_lines(infile, outfile, num_lines) 1.852281 2.411066 2.776543 2.844098 2.965970 4.081520 100 a

La solución de c ++ probablemente puede acelerarse leyendo en grandes bloques de datos a la vez. Sin embargo, esto hará que el código sea mucho más complejo. A menos que esto sea algo que tendría que hacer de forma regular, probablemente me quedaría con la solución R pura.

Observación : cuando sus datos son tabulares, puede usar mi paquete LaF para leer líneas y columnas arbitrarias de su conjunto de datos sin tener que leer todos los datos en la memoria.