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?
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:
Usando
fread
endata.table
para importar datos de archivos csv/delimitados por tabulaciones directamente a R. Ver respuesta de mnel.Usando
read_table
enreadr
(en CRAN desde abril de 2015). Esto funciona muy parecido afread
anterior. El readme en el enlace explica la diferencia entre las dos funciones (readr
actualmente afirma ser "1.5-2x más lento" quedata.table::fread
).read.csv.raw
desdeiotools
proporciona una tercera opción para leer rápidamente archivos CSV.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 elsqldf
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 laDBI
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 sumonetdb.read.csv
función.dplyr
permite trabajar directamente con los datos almacenados en varios tipos de bases de datos.Almacenar datos en formatos binarios también puede ser útil para mejorar el rendimiento. Uso
saveRDS
/readRDS
(véase a continuación), elh5
orhdf5
paquetes para formato HDF5, owrite_fst
/read_fst
desde elfst
paquete.
La respuesta original
Hay un par de cosas simples que probar, ya sea que uses read.cuadro o escanear.
Set
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
.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
.
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
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'))
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.]
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.
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
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)
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"))
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