Lo que podría hacer es iterar sobre las líneas en el archivo, y solo agregar las líneas que tienen la longitud correcta.
que define el siguiente archivo csv prueba:
1;2;3;4
1;2;3;4
1;2;3
1;2;3;4
Usando read.table
falla:
> read.table("test.csv", sep = ";")
Error in scan(file, what, nmax, sep, dec, quote, skip, nlines, na.strings, :
line 3 did not have 4 elements
ahora un enfoque iterativo:
require(plyr)
no_lines = 4
correct_length = 4
file_con = file("test.csv", "r")
result = ldply(1:no_lines, function(line) {
dum = strsplit(readLines(file_con, n = 1), split = ";")[[1]]
if(length(dum) == correct_length) {
return(dum)
} else {
cat(sprintf("Skipped line %s\n", line))
return(NULL)
}
})
close(file_con)
> result
V1 V2 V3 V4
1 1 2 3 4
2 1 2 3 4
3 1 2 3 4
Por supuesto esto es un ejemplo trivial como el el archivo es realmente pequeño Déjenos crear un ejemplo más desafiante para actuar como un punto de referencia.
# First file with invalid rows
norow = 10e5 # number of rows
no_lines = round(runif(norow, min = 3, max = 4))
no_lines[1] = correct_length
file_content = ldply(no_lines, function(line) paste(1:line, collapse = ";"))
writeLines(paste(file_content[[1]], sep = "\n"), "big_test.csv")
# Same length with valid rows
file_content = ldply(rep(4, norow), function(line) paste(1:line, collapse = ";"))
writeLines(paste(file_content[[1]], sep = "\n"), "big_normal.csv")
ya por el punto de referencia
# Iterative approach
system.time({file_con <- file("big_test.csv", "r")
result_test <- ldply(1:norow, function(line) {
dum = strsplit(readLines(file_con, n = 1), split = ";")[[1]]
if(length(dum) == correct_length) {
return(dum)
} else {
# Commenting this speeds up by 30%
#cat(sprintf("Skipped line %s\n", line))
return(NULL)
}
})
close(file_con)})
user system elapsed
20.559 0.047 20.775
# Normal read.table
system.time(result_normal <- read.table("big_normal.csv", sep = ";"))
user system elapsed
1.060 0.015 1.079
# read.table with fill = TRUE
system.time({result_fill <- read.table("big_test.csv", sep = ";", fill=TRUE)
na_rows <- complete.cases(result_fill)
result_fill <- result_fill[-na_rows,]})
user system elapsed
1.161 0.033 1.203
# Specifying which type the columns are (e.g. character or numeric)
# using the colClasses argument.
system.time({result_fill <- read.table("big_test.csv", sep = ";", fill=TRUE,
colClasses = rep("numeric", 4))
na_rows <- complete.cases(result_fill)
result_fill <- result_fill[-na_rows,]})
user system elapsed
0.933 0.064 1.001
lo tanto, el enfoque iterativo es un poco más lento, pero 20 segundos para 1 millón de filas podría ser aceptable (aunque esto depende de su definición aceptable). Especialmente cuando solo tiene que hacer esto una vez, y luego guardarlo usando save
para una recuperación posterior. La solución sugerida por @Paolo es casi tan rápida como la llamada normal al read.table
. Las filas que contienen una cantidad incorrecta de columnas (por lo tanto, NA
) se eliminan utilizando complete.cases
. Especificar qué clases son las columnas mejora aún más el rendimiento, y creo que este efecto será mayor cuando la cantidad de columnas y filas aumente.
Por lo tanto, en conclusión, la mejor opción es usar read.table
con fill = TRUE
, al tiempo que se especifican las clases de las columnas. El enfoque iterativo que usa ldply
es solo una buena opción si desea más flexibilidad en la elección de cómo leer las líneas, p. solo lea la línea si un cierto valor está por encima de un umbral. Pero probablemente esto podría hacerse más rápido leyendo todos los datos en R, y luego creando un subconjunto. Solo cuando los datos son más grandes que tu RAM, podría imaginarme que el enfoque iterativo tiene sus méritos.
¿Qué es malo en tu definición? –
¿Alguna razón por la que no está usando 'read.table' directamente? Tiene muchos argumentos para seleccionar e ignorar varios personajes "malos". También hay un argumento para "completar" filas incompletas, si ese es el problema que está teniendo. –