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:
Usando
fread
endata.table
para importar datos de archivos delimitados por csv / tab directamente a R. Ver la respuesta de mnel .Uso de
read_table
enreadr
(en CRAN desde abril de 2015). Esto funciona de manera muy parecidafread
anterior. El archivo Léame en el enlace explica la diferencia entre las dos funciones (actualmente,readr
es "1.5-2x más lento" quedata.table::fread
).read.csv.raw
deiotools
proporciona una tercera opción para leer rápidamente archivos CSV.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 paquetesqldf
, 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 paqueteRODBC
y la sección inversa depende de la página del paqueteDBI
.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ónmonetdb.read.csv
.dplyr
permite trabajar directamente con datos almacenados en varios tipos de bases de datos.El almacenamiento de datos en formatos binarios también puede ser útil para mejorar el rendimiento. Utilice
saveRDS
/readRDS
(ver más abajo), los paquetesh5
orhdf5
para el formato HDF5, owrite_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.
Establezca
nrows
= el número de registros en sus datos (nmax
enscan
).Asegúrese de que
comment.char=""
para desactivar la interpretación de los comentarios.Defina explícitamente las clases de cada columna usando
colClasses
enread.table
.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
.