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 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?
Una actualización, varios años después.
Esta respuesta es antigua y R ha seguido adelante. Ajustar read.table
para correr un poco más rápido tiene muy pocos beneficios. Tus opciones son:
Uso
vroom
del paquete tidyversevroom
para importar datos desde archivos csv/delimitados por tabulaciones directamente a un tibble R. Vea la respuesta de Héctor .Usar
fread
indata.table
para importar datos desde archivos csv/delimitados por tabulaciones directamente a R. Consulte la respuesta de mnel .Utilizando
read_table
enreadr
(en CRAN desde abril de 2015). Esto funciona de manera muy similar afread
lo anterior. El archivo Léame en el enlace explica la diferencia entre las dos funciones (readr
actualmente afirma ser "1,5-2 veces más lento" quedata.table::fread
).read.csv.raw
fromiotools
proporciona una tercera opción para leer rápidamente archivos CSV.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.sql
en elsqldf
paquete, 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: elRODBC
paquete, y lo contrario depende de la sección de la páginaDBI
del paquete .MonetDB.R
le 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 sumonetdb.read.csv
función.dplyr
le permite trabajar directamente con datos almacenados en varios tipos de bases de datos.Almacenar datos en formatos binarios también puede resultar útil para mejorar el rendimiento. Utilice
saveRDS
/readRDS
(ver más abajo), los paquetesh5
orhdf5
para el formato HDF5, owrite_fst
/read_fst
delfst
paquete.
La respuesta original
Hay un par de cosas sencillas que puedes probar, ya sea que utilices read.table o scan.
Establecer
nrows
= el número de registros en sus datos (nmax
enscan
).Asegúrese
comment.char=""
de desactivar la interpretación de comentarios.Defina explícitamente las clases de cada columna usando
colClasses
inread.table
.La configuración
multi.line=FALSE
tambié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
.
Aquí hay un ejemplo que utiliza fread
desde data.table
1.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
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.csv
comando 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'))
Curiosamente, nadie respondió la parte inferior de la pregunta durante años, a pesar de que es importante: las data.frame
listas son simplemente listas con los atributos correctos, por lo que si tiene muchos datos que no desea usar as.data.frame
o 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.]
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.