Leer rápidamente tablas muy grandes como marcos de datos

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 cosas hacia abajo. En mi caso, supongo que sé los tipos de las columnas con anticipación, la tabla no contiene ningún encabezado de columna o nombre de fila, y no tiene ningún carácter patológico del que deba preocuparme.

Sé que leer en una tabla como una lista usando 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 bashs de convertir esto en un dataframe parecen disminuir el rendimiento de los anteriores en un factor de 6:

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

¿Hay una mejor manera de hacer esto? ¿O posiblemente un enfoque completamente diferente del problema?

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

Esta respuesta es vieja, y R ha avanzado. read.table para ejecutar un poco más rápido tiene un pequeño beneficio precioso. Tus opciones son:

  1. Usando fread en data.table para importar datos desde archivos csv / tab-delimited directamente a R. Vea la respuesta de mnel .

  2. Usando read_table en readr (en CRAN desde abril de 2015). Esto funciona mucho como fread arriba. El archivo léame en el enlace explica la diferencia entre las dos funciones ( readr actualmente afirma ser “1.5-2x más lento” que data.table::fread ).

  3. read.csv.raw de iotools proporciona una tercera opción para leer rápidamente archivos CSV.

  4. Intentando almacenar tantos datos como sea posible en bases de datos en lugar de archivos planos. (Además de ser un mejor medio de almacenamiento permanente, los datos se transfieren read.csv.sql desde R en un formato binario, que es más rápido.) read.csv.sql en el paquete sqldf , como se describe en la respuesta de JD Long , importa datos en un sqldf temporal Base de datos SQLite y luego la lee en R. Consulte también: el paquete RODBC y la sección inversa depende de la página del paquete DBI . MonetDB.R le proporciona un tipo de datos que pretende ser un dataframe pero que en realidad es un MonetDB debajo, lo que aumenta el rendimiento. Importar datos con su función monetdb.read.csv . dplyr permite trabajar directamente con datos almacenados en varios tipos de base de datos.

  5. Almacenar datos en formatos binarios también puede ser útil para mejorar el rendimiento. Utilice saveRDS / readRDS (consulte a continuación), los paquetes h5 o rhdf5 para el formato HDF5 o write_fst / read_fst del primer paquete.


La respuesta original

Hay un par de cosas simples que probar, ya sea que use read.table o scan.

  1. Set nrows = la cantidad de registros en sus datos ( nmax en el scan ).

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

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

  4. Establecer multi.line=FALSE también puede mejorar el rendimiento en el escaneo.

Si nada de esto funciona, entonces use uno de los paquetes de perfiles para determinar qué líneas disminuyen la velocidad. Tal vez pueda escribir una versión reducida de read.table función de los resultados.

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

O bien, si el problema es que tiene que leerlo regularmente, utilice estos métodos para leer los datos una vez, luego guarde el dataframe como un blob binario con save saveRDS , entonces la próxima vez puede recuperarlo más rápido con load readRDS .

Aquí hay un ejemplo que utiliza fread from data.table 1.8.7

Los ejemplos provienen de la página de ayuda para ” 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] 

estándar read.table

 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 

fread

 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 

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

Se ha discutido poco sobre la mejor forma de importar 2GB o más de datos de texto en un dataframe R. Ayer escribí una publicación en un blog sobre el uso de sqldf() para importar los datos en SQLite como un área de preparación, y luego succionar de SQLite a R. Esto funciona muy bien para mí. Pude obtener datos de 2 GB (3 columnas, 40 mm) en <5 minutos. Por el contrario, el comando read.csv ejecutó toda la noche y nunca se completó.

Aquí está mi código de prueba:

Configure los datos de 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))) 

Dejo que la siguiente línea se ejecute toda la noche pero nunca se completó:

 system.time(big.df <- read.csv('bigdf.csv')) 

Extrañamente, nadie contestó la parte inferior de la pregunta durante años, aunque esta es una importante: los data.frame s son simplemente listas con los atributos correctos, por lo tanto, si tiene datos grandes, no quiere usarlos como as.data.frame o similar para una lista. Es mucho más rápido simplemente “convertir” una lista en un dataframe in situ:

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

Esto no hace ninguna copia de los datos por lo que es inmediato (a diferencia de todos los demás métodos). Supone que ya 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 uso readBin() - que es, con mucho, el método más rápido (que no sea 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.]

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

Una sugerencia fue utilizar readChar() y luego realizar la manipulación de 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 desee echarle un vistazo al paquete HadoopStreaming . Esto utiliza Hadoop , que es un marco MapReduce diseñado para tratar con grandes conjuntos de datos. Para esto, usarí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 fragmentos. Incluso podría utilizar una de las estructuras paralelas (por ejemplo, nieve) y ejecutar la importación de datos en paralelo segmentando el archivo, pero lo más probable es que tenga grandes conjuntos de datos que no serán de ayuda, ya que se encontrará con limitaciones de memoria, por eso map-reduce es un mejor enfoque.

Un pequeño punto adicional que vale la pena mencionar. Si tiene un archivo muy grande, puede calcular el número de filas (si no hay encabezado) usando (donde bedGraph es el nombre de su archivo en su directorio de trabajo):

 >numRow=as.integer(system(paste("wc -l", bedGraph, "| sed 's/[^0-9.]*\\([0-9.]*\\).*/\\1/'"), intern=T)) 

Puede usar eso en read.csv , read.table

 >system.time((BG=read.table(bedGraph, nrows=numRow, col.names=c('chr', 'start', 'end', 'score'),colClasses=c('character', rep('integer',3))))) user system elapsed 25.877 0.887 26.752 >object.size(BG) 203949432 bytes 

Muchas veces creo que es una buena práctica mantener bases de datos más grandes dentro de una base de datos (por ejemplo, Postgres). No uso nada mucho más grande que (nrow * ncol) ncell = 10M, que es bastante pequeño; pero a menudo me parece que quiero que R cree y mantenga gráficos intensivos en memoria solo mientras consulto desde múltiples bases de datos. En el futuro de las laptops de 32 GB, algunos de estos tipos de problemas de memoria desaparecerán. Pero el atractivo de usar una base de datos para contener los datos y luego usar la memoria de R para los resultados de las consultas resultantes y los gráficos aún puede ser útil. Algunas ventajas son:

(1) Los datos permanecen cargados en su base de datos. Simplemente vuelva a conectar en pgadmin a las bases de datos que desee cuando vuelva a encender su computadora portátil.

(2) Es cierto que R puede hacer muchas más operaciones estadísticas y gráficas ingeniosas que SQL. Pero creo que SQL está mejor diseñado para consultar grandes cantidades de datos que R.

 # Looking at Voter/Registrant Age by Decade library(RPostgreSQL);library(lattice) con <- dbConnect(PostgreSQL(), user= "postgres", password="password", port="2345", host="localhost", dbname="WC2014_08_01_2014") Decade_BD_1980_42 <- dbGetQuery(con,"Select PrecinctID,Count(PrecinctID),extract(DECADE from Birthdate) from voterdb where extract(DECADE from Birthdate)::numeric > 198 and PrecinctID in (Select * from LD42) Group By PrecinctID,date_part Order by Count DESC;") Decade_RD_1980_42 <- dbGetQuery(con,"Select PrecinctID,Count(PrecinctID),extract(DECADE from RegistrationDate) from voterdb where extract(DECADE from RegistrationDate)::numeric > 198 and PrecinctID in (Select * from LD42) Group By PrecinctID,date_part Order by Count DESC;") with(Decade_BD_1980_42,(barchart(~count | as.factor(precinctid)))); mtext("42LD Birthdays later than 1980 by Precinct",side=1,line=0) with(Decade_RD_1980_42,(barchart(~count | as.factor(precinctid)))); mtext("42LD Registration Dates later than 1980 by Precinct",side=1,line=0) 

En lugar de la tabla de lectura convencional, siento que el fread es una función más rápida. Especificar atributos adicionales como seleccionar solo las columnas requeridas, especificar colclasses y string como factores reducirá la toma de tiempo para importar el archivo.

 data_frame <- fread("filename.csv",sep=",",header=FALSE,stringsAsFactors=FALSE,select=c(1,4,5,6,7),colClasses=c("as.numeric","as.character","as.numeric","as.Date","as.Factor"))