Leer rápidamente tablas muy grandes como marcos de datos

Resuelto eytan asked hace 54 años • 12 respuestas

Tengo tablas muy grandes (30 millones de filas) que me gustaría cargar como marcos de datos en R. read.table() Tiene muchas características convenientes, pero parece que hay mucha lógica en la implementación que ralentizaría las cosas. En mi caso, supongo que conozco los tipos de columnas de antemano, que la tabla no contiene encabezados de columna ni nombres de filas, y no tiene ningún carácter patológico del que deba preocuparme.

Sé que leer una tabla como una lista scan()puede ser bastante rápido, por ejemplo:

datalist <- scan('myfile',sep='\t',list(url='',popularity=0,mintime=0,maxtime=0)))

Pero algunos de mis intentos de convertir esto en un marco de datos parecen disminuir el rendimiento de lo anterior en un factor de 6:

df <- as.data.frame(scan('myfile',sep='\t',list(url='',popularity=0,mintime=0,maxtime=0))))

¿Existe una mejor manera de hacer esto? ¿O tal vez un enfoque completamente diferente del problema?

eytan avatar Jan 01 '70 08:01 eytan
Aceptado

Una actualización, varios años después.

Esta respuesta es antigua y R ha seguido adelante. Ajustar read.tablepara correr un poco más rápido tiene muy pocos beneficios. Tus opciones son:

  1. Uso vroomdel paquete tidyverse vroompara importar datos desde archivos csv/delimitados por tabulaciones directamente a un tibble R. Vea la respuesta de Héctor .

  2. Usar freadin data.tablepara importar datos desde archivos csv/delimitados por tabulaciones directamente a R. Consulte la respuesta de mnel .

  3. Utilizando read_tableen readr(en CRAN desde abril de 2015). Esto funciona de manera muy similar a freadlo anterior. El archivo Léame en el enlace explica la diferencia entre las dos funciones ( readractualmente afirma ser "1,5-2 veces más lento" que data.table::fread).

  4. read.csv.rawfrom iotoolsproporciona una tercera opción para leer rápidamente archivos CSV.

  5. Intentar almacenar la mayor cantidad de datos posible en bases de datos en lugar de archivos planos. (Además de ser un mejor medio de almacenamiento permanente, los datos se pasan hacia y desde R en formato binario, lo cual es más rápido). read.csv.sqlen el sqldfpaquete, como se describe en la respuesta de JD Long , importa datos a una base de datos SQLite temporal y luego los lee. en R. Consulte también: el RODBCpaquete, y lo contrario depende de la sección de la página DBIdel paquete . MonetDB.Rle brinda un tipo de datos que pretende ser un marco de datos pero que en realidad es un MonetDB subyacente, lo que aumenta el rendimiento. Importar datos con su monetdb.read.csvfunción. dplyrle permite trabajar directamente con datos almacenados en varios tipos de bases de datos.

  6. Almacenar datos en formatos binarios también puede resultar útil para mejorar el rendimiento. Utilice saveRDS/ readRDS(ver más abajo), los paquetes h5o rhdf5para el formato HDF5, o write_fst/ read_fstdel fstpaquete.


La respuesta original

Hay un par de cosas sencillas que puedes probar, ya sea que utilices read.table o scan.

  1. Establecer nrows= el número de registros en sus datos ( nmaxen scan).

  2. Asegúrese comment.char=""de desactivar la interpretación de comentarios.

  3. Defina explícitamente las clases de cada columna usando colClassesin read.table.

  4. La configuración multi.line=FALSEtambién puede mejorar el rendimiento del escaneo.

Si nada de esto funciona, utilice uno de los paquetes de creación de perfiles para determinar qué líneas están ralentizando el proceso. Quizás puedas escribir una versión reducida deread.table basada en los resultados.

La otra alternativa es filtrar sus datos antes de leerlos en R.

O, si el problema es que tiene que leerlo regularmente, utilice estos métodos para leer los datos una vez y luego guarde el marco de datos como un blob binario consave saveRDS, la próxima vez podrás recuperarlo más rápido conload readRDS.

Richie Cotton avatar Nov 13 '2009 10:11 Richie Cotton

Aquí hay un ejemplo que utiliza freaddesde data.table1.8.7

Los ejemplos provienen de la página de ayuda de fread, con los tiempos en mi 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]

tabla de lectura estándar

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

tabla de lectura optimizada

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

miedo

require(data.table)
system.time(DT <- fread("test.csv"))                                  
 ##    user  system elapsed 
##    3.12    0.01    3.22

sqldf

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

ff/ffdf

 require(ff)

 system.time(FFDF <- read.csv.ffdf(file="test.csv",nrows=n))   
 ##    user  system elapsed 
 ##   10.85    0.10   10.99

En resumen:

##    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
mnel avatar Feb 25 '2013 01:02 mnel

No vi esta pregunta inicialmente y hice una pregunta similar unos días después. Voy a eliminar mi pregunta anterior, pero pensé en agregar una respuesta aquí para explicar cómo solía sqldf()hacer esto.

Ha habido un poco de discusión sobre la mejor manera de importar 2 GB o más de datos de texto en un marco de datos R. Ayer escribí una publicación de blog sobre cómo sqldf()importar los datos a SQLite como área de preparación y luego absorberlos de SQLite a R. Esto funciona muy bien para mí. Pude extraer 2 GB (3 columnas, filas de 40 mm) de datos en <5 minutos. Por el contrario, el read.csvcomando se ejecutó toda la noche y nunca se completó.

Aquí está mi código de prueba:

Configure los datos de la prueba:

bigdf <- data.frame(dim=sample(letters, replace=T, 4e7), fact1=rnorm(4e7), fact2=rnorm(4e7, 20, 50))
write.csv(bigdf, 'bigdf.csv', quote = F)

Reinicié R antes de ejecutar la siguiente rutina de importación:

library(sqldf)
f <- file("bigdf.csv")
system.time(bigdf <- sqldf("select * from f", dbname = tempfile(), file.format = list(header = T, row.names = F)))

Dejé que la siguiente línea se ejecutara toda la noche pero nunca se completó:

system.time(big.df <- read.csv('bigdf.csv'))
JD Long avatar Nov 30 '2009 15:11 JD Long

Curiosamente, nadie respondió la parte inferior de la pregunta durante años, a pesar de que es importante: las data.framelistas son simplemente listas con los atributos correctos, por lo que si tiene muchos datos que no desea usar as.data.frameo similares para una lista. Es mucho más rápido simplemente "convertir" una lista en un marco de datos en el lugar:

attr(df, "row.names") <- .set_row_names(length(df[[1]]))
class(df) <- "data.frame"

Esto no realiza ninguna copia de los datos, por lo que es inmediato (a diferencia de todos los demás métodos). Se supone que ya se ha configurado names()en la lista en consecuencia.

[En cuanto a cargar datos grandes en R, personalmente los vuelco por columna en archivos binarios y los uso readBin(); ese es, con diferencia, el método más rápido (aparte de mmapping) y solo está limitado por la velocidad del disco. El análisis de archivos ASCII es inherentemente lento (incluso en C) en comparación con los datos binarios.]

Simon Urbanek avatar Dec 20 '2012 04:12 Simon Urbanek

Esto se preguntó anteriormente en R-Help , por lo que vale la pena revisarlo.

Una sugerencia fue usar readChar()y luego manipular cadenas en el resultado con strsplit()y substr(). Puede ver que la lógica involucrada en readChar es mucho menor que read.table.

No sé si la memoria es un problema aquí, pero es posible que también quieras echar un vistazo al paquete HadoopStreaming . Esto utiliza Hadoop , que es un marco MapReduce diseñado para manejar grandes conjuntos de datos. Para ello, utilizaría la función hsTableReader. Este es un ejemplo (pero tiene una curva de aprendizaje para aprender 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)

La idea básica aquí es dividir la importación de datos en partes. Incluso podría ir tan lejos como para utilizar uno de los marcos paralelos (por ejemplo, Snow) y ejecutar la importación de datos en paralelo segmentando el archivo, pero lo más probable es que para conjuntos de datos grandes eso no ayude, ya que se encontrará con limitaciones de memoria. Es por eso que map-reduce es un mejor enfoque.

Shane avatar Nov 13 '2009 15:11 Shane