team sort software org data citation r import dataframe r-faq

sort - repository r



Lectura rĂ¡pida de tablas muy grandes como marcos de datos (8)

Al principio no vi esta pregunta e hice una pregunta similar unos días después. Voy a descartar mi pregunta anterior, pero pensé que agregaría una respuesta aquí para explicar cómo usé sqldf() para hacer esto.

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

Configurar 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é correr la siguiente línea toda la noche pero nunca se completó:

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

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 abajo En mi caso, asumo que conozco los tipos de columnas antes de tiempo, 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 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))))

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


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

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

temible

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


En lugar de la tabla de lectura convencional, siento que el miedo es una función más rápida. Especificar atributos adicionales, como seleccionar solo las columnas requeridas, especificar clases de caracteres y cadenas como factores, reducirá el tiempo que lleva 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"))


Esto fue preguntado previamente en R-Help , por lo que vale la pena revisar.

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

No sé si la memoria es un problema aquí, pero es posible que también desee ver el 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 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 mediante la segmentación del archivo, pero lo más probable es que los conjuntos de datos grandes no le ayuden, ya que se encontrará con limitaciones de memoria. por lo que map-reduce es un mejor enfoque.


Extrañamente, nadie contestó la parte inferior de la pregunta durante años, a pesar de que esta es una pregunta importante: los data.frame s son simplemente listas con los atributos correctos, por lo que si tiene datos grandes no quiere 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 una copia de los datos, por lo que es inmediato (a diferencia de todos los otros métodos). Se supone que ya ha establecido los names() en la lista en consecuencia.

[En cuanto a la carga de datos de gran tamaño en R - personalmente, los volteo por columna en archivos binarios y uso readBin() - que es, con mucho, el método más rápido (excepto el mmapping) y está limitado solo por la velocidad del disco. El análisis de los archivos ASCII es intrínsecamente lento (incluso en C) en comparación con los datos binarios.]


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 encuentro que quiero que R cree y mantenga gráficos con gran cantidad de memoria solo mientras hago consultas desde múltiples bases de datos. En el futuro de las computadoras 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 almacenar los datos y luego usar la memoria de R para los resultados de la consulta y los gráficos resultantes puede ser útil. 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 desea cuando vuelva a encender su computadora portátil.

(2) Es cierto que R puede realizar 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)


Unos pequeños 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 tiene 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))

Luego puedes 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


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

Esta respuesta es antigua, y R ha avanzado. read.table de read.table para que se ejecute un poco más rápido tiene muy poco beneficio. Sus opciones son:

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

  2. Uso de read_table en readr (en CRAN desde abril de 2015). Esto funciona de manera muy parecida fread anterior. El archivo Léame en el enlace explica la diferencia entre las dos funciones (actualmente, readr es "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. Intenta almacenar tantos datos como puedas en bases de datos en lugar de archivos planos. (Además de ser un mejor medio de almacenamiento permanente, los datos se transfieren a 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 los datos en una cuenta temporal. Base de datos SQLite y luego la lee en R. Vea 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 marco de datos pero que en realidad es un MonetDB por 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 bases de datos.

  5. El almacenamiento de datos en formatos binarios también puede ser útil para mejorar el rendimiento. Utilice saveRDS / readRDS (ver más abajo), 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 para probar, ya sea que use read.table o scan.

  1. Establezca 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. La configuración de multi.line=FALSE también puede mejorar el rendimiento en la exploración.

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