img - ¿Cómo leo solo las líneas que cumplen una condición de un csv a R?
tags$img shiny (5)
Estoy intentando leer un archivo csv grande en R. Aunque el archivo es grande, solo quiero trabajar con algunas de las filas que cumplen una condición particular (por ejemplo, Variable2> = 3). Este es un conjunto de datos mucho más pequeño. Me gustaría leer estas líneas directamente en un marco de datos en lugar de cargar todo el conjunto de datos en un marco de datos y luego seleccionar de acuerdo con la condición. La razón principal es que el conjunto de datos no cabe fácilmente en la memoria de una computadora de escritorio o portátil. Estoy buscando una solución que use solo R, y no requiere Python u otros idiomas. Gracias.
Estaba viendo readr::read_csv_chunked
cuando vi esta pregunta y pensé que haría una evaluación comparativa. Para este ejemplo, read_csv_chunked
funciona bien y aumentar el tamaño del trozo fue beneficioso. sqldf
fue solo ligeramente más rápido que awk
.
library(tidyverse)
library(sqldf)
library(microbenchmark)
# Generate an example dataset with two numeric columns and 5 million rows
data_frame(
norm = rnorm(5e6, mean = 5000, sd = 1000),
unif = runif(5e6, min = 0, max = 10000)
) %>%
write_csv(''medium.csv'')
microbenchmark(
readr = read_csv_chunked(''medium.csv'', callback = DataFrameCallback$new(function(x, pos) subset(x, unif > 9000)), col_types = ''dd'', progress = F),
readr2 = read_csv_chunked(''medium.csv'', callback = DataFrameCallback$new(function(x, pos) subset(x, unif > 9000)), col_types = ''dd'', progress = F, chunk_size = 1000000),
sqldf = read.csv.sql(''medium.csv'', sql = ''select * from file where unif > 9000'', eol = ''/n''),
awk = read.csv(pipe("awk ''BEGIN {FS=/",/"} {if ($2 > 9000) print $0}'' medium.csv")),
awk2 = read_csv(pipe("awk ''BEGIN {FS=/",/"} {if ($2 > 9000) print $0}'' medium.csv"), col_types = ''dd'', progress = F),
check = function(values) all(sapply(values[-1], function(x) all.equal(values[[1]], x))),
times = 10L
)
# Unit: seconds
# expr min lq mean median uq max neval
# readr 5.58 5.79 6.16 5.98 6.68 7.12 10
# readr2 2.94 2.98 3.07 3.03 3.06 3.43 10
# sqldf 13.59 13.74 14.20 13.91 14.64 15.49 10
# awk 16.83 16.86 17.07 16.92 17.29 17.77 10
# awk2 16.86 16.91 16.99 16.92 16.97 17.57 10
Por mucho, lo más fácil (en mi libro) es usar el preprocesamiento.
R> DF <- data.frame(n=1:26, l=LETTERS)
R> write.csv(DF, file="/tmp/data.csv", row.names=FALSE)
R> read.csv(pipe("awk ''BEGIN {FS=/",/"} {if ($1 > 20) print $0}'' /tmp/data.csv"),
+ header=FALSE)
V1 V2
1 21 U
2 22 V
3 23 W
4 24 X
5 25 Y
6 26 Z
R>
Aquí utilizamos awk
. Le decimos a awk
que use una coma como separador de campo, y luego usamos la condición "si el primer campo es mayor que 20" para decidir si imprimimos (toda la línea a través de $0
).
La salida de ese comando se puede leer mediante R a través de pipe()
.
Esto va a ser más rápido y más eficiente en memoria que leerlo todo en R.
Puede abrir el archivo en modo de lectura utilizando el file
función (por ejemplo, file("mydata.csv", open = "r")
).
Puede leer el archivo una línea a la vez utilizando la función readLines
con la opción n = 1
, l = readLines(fc, n = 1)
.
Luego tiene que analizar su cadena usando una función como strsplit
, expresiones regulares, o puede probar el paquete stringr
(disponible en CRAN).
Si la línea cumple con las condiciones para importar los datos, puede importarlos.
Para resumir yo haría algo como esto:
df = data.frame(var1=character(), var2=int(), stringsAsFactors = FALSE)
fc = file("myfile.csv", open = "r")
i = 0
while(length( (l <- readLines(fc, n = 1) ) > 0 )){ # note the parenthesis surrounding l <- readLines..
##parse l here: and check whether you need to import the data.
if (need_to_add_data){
i=i+1
df[i,] = #list of data to import
}
}
Puede leer el archivo en partes, procesar cada parte y luego unir solo los subconjuntos.
Este es un ejemplo mínimo que asume que el archivo tiene 1001 líneas (incluido el encabezado) y que solo 100 cabrán en la memoria. Los datos tienen 3 columnas, y esperamos que un máximo de 150 filas cumplan con la condición (esto es necesario para asignar previamente el espacio para los datos finales:
# initialize empty data.frame (150 x 3)
max.rows <- 150
final.df <- data.frame(Variable1=rep(NA, max.rows=150),
Variable2=NA,
Variable3=NA)
# read the first chunk outside the loop
temp <- read.csv(''big_file.csv'', nrows=100, stringsAsFactors=FALSE)
temp <- temp[temp$Variable2 >= 3, ] ## subset to useful columns
final.df[1:nrow(temp), ] <- temp ## add to the data
last.row = nrow(temp) ## keep track of row index, incl. header
for (i in 1:9){ ## nine chunks remaining to be read
temp <- read.csv(''big_file.csv'', skip=i*100+1, nrow=100, header=FALSE,
stringsAsFactors=FALSE)
temp <- temp[temp$Variable2 >= 3, ]
final.df[(last.row+1):(last.row+nrow(temp)), ] <- temp
last.row <- last.row + nrow(temp) ## increment the current count
}
final.df <- final.df[1:last.row, ] ## only keep filled rows
rm(temp) ## remove last chunk to free memory
Edición: Se stringsAsFactors=FALSE
opción stringsAsFactors=FALSE
en la sugerencia de @ lucacerone en los comentarios.
Podría usar la función read.csv.sql
en el paquete sqldf
y filtrar utilizando la selección de SQL. Desde la página de ayuda de read.csv.sql
:
library(sqldf)
write.csv(iris, "iris.csv", quote = FALSE, row.names = FALSE)
iris2 <- read.csv.sql("iris.csv",
sql = "select * from file where `Sepal.Length` > 5", eol = "/n")