Lectura rápida de tablas muy grandes como dataframes


Tengo tablas muy grandes (30 millones de filas) que me gustaría cargar como dataframes 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, asumo que conozco los tipos de columnas con anticipación, la tabla no contiene encabezados de columna ni nombres de fila y no tiene caracteres patológicos de los que deba preocuparme.

Sé que leer en una tabla como una lista usar 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 a un dataframe 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))))

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

Author: Uwe Keim, 2009-11-13

8 answers

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

Esta respuesta es antigua, y R ha seguido adelante. Ajustes read.table correr un poco más rápido tiene muy poco beneficio. Sus opciones son:

  1. Usando fread en data.table para importar datos de archivos csv/delimitados por tabulaciones directamente a R. Ver respuesta de mnel.

  2. Usando read_table en readr (en CRAN desde abril de 2015). Esto funciona muy parecido a fread anterior. El readme 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 desde iotools proporciona una tercera opción para leer rápidamente archivos CSV.

  4. Tratando de 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 pasan hacia y desde R en un formato binario, que es más rápido.) read.csv.sql en el sqldf paquete, como se describe en JD Long's answer , importa datos en una base de datos SQLite temporal y luego los lee en R. RODBC paquete, y el reverso depende de la sección de la DBI paquete página. MonetDB.R le da un tipo de datos que pretende ser un marco de datos, pero es realmente un MonetDB debajo, aumentando el rendimiento. Importar datos con su monetdb.read.csv función. dplyr permite trabajar directamente con los datos almacenados en varios tipos de bases de datos.

  5. Almacenar datos en formatos binarios también puede ser útil para mejorar el rendimiento. Uso saveRDS/readRDS (véase a continuación), el h5 o rhdf5 paquetes para formato HDF5, o write_fst/read_fst desde el fst paquete.


La respuesta original

Hay un par de cosas simples que probar, ya sea que uses read.cuadro o escanear.

  1. Set nrows=el número de registros en sus datos (nmax en 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. El ajuste multi.line=FALSE también puede mejorar el rendimiento en el análisis.

Si ninguna de estas cosas funciona, entonces use uno de los paquetes de perfiles para determinar qué líneas están retrasando las cosas. Tal vez pueda escribir una versión reducida de read.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, use estos métodos para leer los datos una vez, luego guarde el marco de datos como un blob binario con save saveRDS, entonces la próxima vez puede recuperarlo más rápido con load readRDS.

 365
Author: Richie Cotton,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-02-12 09:35:30

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

Los ejemplos vienen de la página de ayuda a 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]

Lectura estándar.tabla

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

Lectura optimizada.tabla

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
 256
Author: mnel,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-02-25 01:56:36

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

Ha habido un poco de discusión en cuanto a la mejor manera de importar 2 GB o más de datos de texto en un marco de datos R. Ayer escribí una entrada de blog sobre el uso de sqldf() para importar los datos a SQLite como un área de preparación, y luego succionarlos de SQLite en R. Esto funciona muy bien para mí. Pude extraer 2 GB (3 columnas, filas de 40 mm) de datos en read.csv funcionó 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)))

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

system.time(big.df <- read.csv('bigdf.csv'))
 243
Author: JD Long,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-11-21 21:36:20

Extrañamente, nadie respondió la parte inferior de la pregunta durante años a pesar de que esta es una pregunta importante {data.frame s son simplemente listas con los atributos correctos, por lo que si tiene datos grandes no desea usar as.data.frame o similar 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 hace ninguna copia de los datos por lo que es inmediata (a diferencia de todos los otros métodos). Se asume que ya ha establecido names() en la lista en consecuencia.

[En cuanto a cargando grandes datos en R personally 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.]

 70
Author: Simon Urbanek,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-02-24 21:42:54

Esto fue previamente preguntado en R-Help, vale la pena revisarlo.

Una sugerencia fue usar readChar() y luego hacer manipulación de cadenas en el resultado con strsplit() y substr(). Puedes ver que la lógica involucrada en readChar es mucho menor que read.tabla.

No se si la memoria es un problema aquí, pero también puede que quiera echar un vistazo al paquete HadoopStreaming . Este utiliza Hadoop , que es un framework 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 trozos. Incluso podría ir tan lejos como para usar 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 grandes conjuntos de datos que no ayudarán, ya que se encontrará con restricciones de memoria, por lo que map-reduce es un mejor enfoque.

 30
Author: Shane,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-10-11 09:34:14

Un menor puntos adicionales que vale la pena mencionar. Si tiene un archivo muy grande, puede calcular sobre la marcha 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))

A continuación, puede utilizar que ya sea 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
 5
Author: Stephen Henderson,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-11-28 17:20:55

Muchas veces pienso que es solo 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 encuentro que quiero que R cree y mantenga gráficos intensivos de memoria solo mientras hago consultas desde múltiples bases de datos. En el futuro de los portátiles 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 el los resultados de las consultas y los gráficos resultantes pueden ser útiles. Algunas ventajas son:

(1) Los datos permanecen cargados en su base de datos. Simplemente vuelva a conectarse 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)
 4
Author: rferrisx,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-08-24 15:54:25

En lugar de la lectura convencional.tabla Siento 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á el tiempo necesario 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"))
 0
Author: Aayush Agrawal,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-04-18 07:22:01