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:
- 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). - 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. - 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?
- Si tenía un archivo delimitado, querría ver
read_csv
oread_delim
dentro del paquetereadr
.
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.