Recorte de un archivo csv enorme (3,5 GB) para leerlo en R

87

Así que tengo un archivo de datos (separado por punto y coma) que tiene muchos detalles y filas incompletas (lo que hace que Access y SQL se bloqueen). Es un conjunto de datos a nivel de condado dividido en segmentos, subsegmentos y subsegmentos (para un total de ~ 200 factores) durante 40 años. En resumen, es enorme y no va a caber en la memoria si intento simplemente leerlo.

Entonces mi pregunta es esta, dado que quiero todos los condados, pero solo un año (y solo el nivel más alto de segmento ... lo que lleva a aproximadamente 100,000 filas al final), ¿cuál sería la mejor manera de obtener este resumen en R?

Actualmente estoy tratando de eliminar años irrelevantes con Python, superando el límite leyendo y operando en una línea a la vez, pero preferiría una solución solo R (paquetes CRAN OK). ¿Existe una forma similar de leer en archivos un fragmento a la vez en R?

Cualquier idea será muy apreciada.

Actualizar:

  • Restricciones
    • Necesita usar mi máquina, por lo que no hay instancias EC2
    • Tan solo R como sea posible. La velocidad y los recursos no son una preocupación en este caso ... siempre que mi máquina no explote ...
    • Como puede ver a continuación, los datos contienen tipos mixtos, en los que necesito operar más tarde
  • Datos
    • Los datos son de 3,5 GB, con aproximadamente 8,5 millones de filas y 17 columnas.
    • Un par de miles de filas (~ 2k) están mal formadas, con solo una columna en lugar de 17
      • Estos son completamente insignificantes y pueden descartarse
    • Solo necesito ~ 100,000 filas de este archivo (ver más abajo)

Ejemplo de datos:

County; State; Year; Quarter; Segment; Sub-Segment; Sub-Sub-Segment; GDP; ...
Ada County;NC;2009;4;FIRE;Financial;Banks;80.1; ...
Ada County;NC;2010;1;FIRE;Financial;Banks;82.5; ...
NC  [Malformed row]
[8.5 Mill rows]

Quiero cortar algunas columnas y elegir dos de los 40 años disponibles (2009-2010 de 1980-2020), para que los datos puedan caber en R:

County; State; Year; Quarter; Segment; GDP; ...
Ada County;NC;2009;4;FIRE;80.1; ...
Ada County;NC;2010;1;FIRE;82.5; ...
[~200,000 rows]

Resultados:

Después de jugar con todas las sugerencias hechas, decidí que readLines, sugerido por JD y Marek, funcionaría mejor. Le di a Marek el cheque porque dio una implementación de muestra.

Reproduje una versión ligeramente adaptada de la implementación de Marek para mi respuesta final aquí, usando strsplit y cat para mantener solo las columnas que quiero.

También debe tenerse en cuenta que esto es MUCHO menos eficiente que Python ... como en, Python muerde el archivo de 3.5GB en 5 minutos mientras que R toma alrededor de 60 ... pero si todo lo que tiene es R, entonces este es el boleto.

## Open a connection separately to hold the cursor position
file.in <- file('bad_data.txt', 'rt')
file.out <- file('chopped_data.txt', 'wt')
line <- readLines(file.in, n=1)
line.split <- strsplit(line, ';')
# Stitching together only the columns we want
cat(line.split[[1]][1:5], line.split[[1]][8], sep = ';', file = file.out, fill = TRUE)
## Use a loop to read in the rest of the lines
line <- readLines(file.in, n=1)
while (length(line)) {
  line.split <- strsplit(line, ';')
  if (length(line.split[[1]]) > 1) {
    if (line.split[[1]][3] == '2009') {
        cat(line.split[[1]][1:5], line.split[[1]][8], sep = ';', file = file.out, fill = TRUE)
    }
  }
  line<- readLines(file.in, n=1)
}
close(file.in)
close(file.out)

Fallos por enfoque:

  • sqldf
    • Esto es definitivamente lo que usaré para este tipo de problema en el futuro si los datos están bien formados. Sin embargo, si no es así, SQLite se ahoga.
  • Mapa reducido
    • Para ser honesto, los médicos me intimidaron un poco en este caso, así que no pude intentarlo. Parecía que también requería que el objeto estuviera en la memoria, lo que anularía el punto si ese fuera el caso.
  • gran memoria
    • Este enfoque está claramente vinculado a los datos, pero solo puede manejar un tipo a la vez. Como resultado, todos mis vectores de caracteres cayeron cuando se pusieron en una tabla grande. Sin embargo, si necesito diseñar grandes conjuntos de datos para el futuro, consideraría usar solo números para mantener viva esta opción.
  • escanear
    • El escaneo parecía tener problemas de tipo similares a los de la memoria grande, pero con todos los mecanismos de readLines. En resumen, esta vez no encajaba bien.
FTWynn
fuente
3
Si sus criterios son lo suficientemente simples, probablemente pueda salirse con la suya usando sedy / o awkcreando una versión reducida del CSV que pueda leer directamente. Dado que esto es más una solución que una respuesta, lo dejaré como comentario.
Hank Gay
Estoy de acuerdo con Hank - usted debe utilizar la herramienta adecuada para el trabajo, y si es simple limpieza de datos / eliminar filas irrelevantes / columnas mando herramientas de línea de corriente como género / sed / awk son grandes y van a ser manera menos recursos de R o python - si da una muestra del formato de sus archivos, probablemente podríamos dar un ejemplo
Aaron Statham
Excelente. Háganos saber lo que descubre.
Shane
@Hank & Aaron: generalmente estoy a favor de usar la herramienta adecuada para el trabajo, pero dado que esto está en una máquina con Windows en el trabajo y estoy aprendiendo R a medida que avanzo, pensé que sería un buen ejercicio renunciar a las mejores prácticas e intente esto como R solo si es posible.
FTWynn
2
Para referencia futura, consulte el paquete data.table R. La freadfunción es mucho más rápida que read.table. Usa algo como x = fread(file_path_here, data.table=FALSE)para cargarlo como un data.frameobjeto.
paleo13

Respuestas:

39

Mi intento con readLines. Esta parte de un código se crea csvcon años seleccionados.

file_in <- file("in.csv","r")
file_out <- file("out.csv","a")
x <- readLines(file_in, n=1)
writeLines(x, file_out) # copy headers

B <- 300000 # depends how large is one pack
while(length(x)) {
    ind <- grep("^[^;]*;[^;]*; 20(09|10)", x)
    if (length(ind)) writeLines(x[ind], file_out)
    x <- readLines(file_in, n=B)
}
close(file_in)
close(file_out)
Marek
fuente
Esto es casi exactamente lo que estaba escribiendo. Siento que esta también será la mejor respuesta, dadas las limitaciones de memoria, los tipos mixtos y las filas mal formadas.
FTWynn
10

No soy un experto en esto, pero podría considerar probar MapReduce , lo que básicamente significaría adoptar un enfoque de "divide y vencerás". R tiene varias opciones para esto, que incluyen:

  1. mapReduce (R pura)
  2. RHIPE (que usa Hadoop ); consulte el ejemplo 6.2.2 en la documentación para ver un ejemplo de subconjuntos de archivos

Alternativamente, R proporciona varios paquetes para manejar datos grandes que salen de la memoria (al disco). Probablemente podría cargar todo el conjunto de datos en un bigmemoryobjeto y hacer la reducción completamente dentro de R. Consulte http://www.bigmemory.org/ para obtener un conjunto de herramientas para manejar esto.

Shane
fuente
Buena sugerencia, pero no tengo mucha experiencia con MapReduce y sus similares. Tendré que leerlo.
FTWynn
bigmemorypuede ser más fácil para ti probar primero, en ese caso.
Shane
10

¿Existe una forma similar de leer en archivos un fragmento a la vez en R?

Si. La función readChar () leerá en un bloque de caracteres sin asumir que terminan en nulo. Si desea leer datos en una línea a la vez, puede usar readLines () . Si lee un bloque o una línea, realiza una operación y luego escribe los datos, puede evitar el problema de la memoria. Aunque si tiene ganas de activar una gran instancia de memoria en el EC2 de Amazon, puede obtener hasta 64 GB de RAM. Eso debería contener su archivo y mucho espacio para manipular los datos.

Si necesita más velocidad, la recomendación de Shane de usar Map Reduce es muy buena. Sin embargo, si sigue la ruta de usar una instancia de memoria grande en EC2, debe mirar el paquete multinúcleo para usar todos los núcleos en una máquina.

Si desea leer muchos gigas de datos delimitados en R, al menos debería investigar el paquete sqldf que le permite importar directamente a sqldf desde R y luego operar con los datos desde dentro de R. He encontrado que sqldf es uno de las formas más rápidas de importar gigas de datos en R, como se mencionó en esta pregunta anterior .

JD Long
fuente
Tendré una instancia EC2 en mente, pero por el momento tengo que ceñirme a mi escritorio y tiene 2GB de RAM. sqldf definitivamente parece lo que tenía en mente. Sin embargo, también se ahoga en las filas mal formadas (debería haber 17 columnas, pero un par de miles de filas solo tienen una). ¿Eso requiere algún otro método de preprocesamiento, o hay una opción que me falta?
FTWynn
6

Hay un paquete completamente nuevo llamado colbycol que le permite leer solo las variables que desea de archivos de texto enormes:

http://colbycol.r-forge.r-project.org/

Pasa cualquier argumento a read.table, por lo que la combinación debería permitirle subconjuntos bastante ajustados.

Ari B. Friedman
fuente
6

El ffpaquete es una forma transparente de tratar archivos de gran tamaño.

Puede ver el sitio web del paquete y / o una presentación al respecto.

espero que esto ayude

Ali
fuente
5

Puede importar datos a la base de datos SQLite y luego usar RSQLite para seleccionar subconjuntos.

Marek
fuente
Un buen plan, pero como esto es esencialmente lo que hace sqldf detrás de escena, preferiría eso. ¿A menos que haya una mejor manera de manejar las filas mal formadas si usa RSQLite directo?
FTWynn
5

¿Qué pasa con el uso readry la read_*_chunkedfamilia?

Entonces, en tu caso:

testfile.csv

County; State; Year; Quarter; Segment; Sub-Segment; Sub-Sub-Segment; GDP
Ada County;NC;2009;4;FIRE;Financial;Banks;80.1
Ada County;NC;2010;1;FIRE;Financial;Banks;82.5
lol
Ada County;NC;2013;1;FIRE;Financial;Banks;82.5

Código real

require(readr)
f <- function(x, pos) subset(x, Year %in% c(2009, 2010))
read_csv2_chunked("testfile.csv", DataFrameCallback$new(f), chunk_size = 1)

Esto se aplica fa cada fragmento, recordando los nombres de las columnas y combinando los resultados filtrados al final. Vea ?callbackcuál es la fuente de este ejemplo.

Esto resulta en:

# A tibble: 2 × 8
      County State  Year Quarter Segment `Sub-Segment` `Sub-Sub-Segment`   GDP
*      <chr> <chr> <int>   <int>   <chr>         <chr>             <chr> <dbl>
1 Ada County    NC  2009       4    FIRE     Financial             Banks   801
2 Ada County    NC  2010       1    FIRE     Financial             Banks   825

Incluso puede aumentar, chunk_sizepero en este ejemplo solo hay 4 líneas.

Rentrop
fuente
4

¿Has tenido una gran memoria ? Mira esto y esto .

George Dontas
fuente
Buena idea. Lo miraré.
FTWynn
3

Quizás pueda migrar a MySQL o PostgreSQL para evitar las limitaciones de MS Access.

Es bastante fácil conectar R a estos sistemas con un conector de base de datos basado en DBI (disponible en CRAN).

Témpano de hielo
fuente
Touche por usar mejores herramientas de base de datos, pero dado que eso implicaría una molestia administrativa (tengo que amar esas regulaciones de administración en las grandes empresas), estoy tratando de seguir con lo que tengo. Además, estoy apuntando a la menor cantidad posible de conversiones entre el archivo de texto que recibo.
FTWynn
3

scan () tiene un argumento de nlines y un argumento de omisión. ¿Hay alguna razón por la que pueda usar eso para leer un fragmento de líneas a la vez, verificando la fecha para ver si es apropiada? Si el archivo de entrada está ordenado por fecha, puede almacenar un índice que le indique cuáles deberían ser sus omisiones y líneas que acelerarían el proceso en el futuro.

Frankc
fuente
Lo comprobaré, pero el archivo no está ordenado por nada útil como la fecha. Los proveedores parecen pensar que es más importante ordenar por región en la que se encuentra un condado determinado. / Suspiro ...
FTWynn
Creo que entendiste mal su propuesta: lee tu archivo fragmento por fragmento y extrae solo las filas que necesitas de cada fragmento. No es necesario pedir los archivos.
Karl Forner
1

En estos días, 3.5GB simplemente no es tan grande, puedo acceder a una máquina con 244GB de RAM (r3.8xlarge) en la nube de Amazon por $ 2.80 / hora. ¿Cuántas horas le tomará descubrir cómo resolver el problema usando soluciones de tipo big data? ¿Cuánto vale su tiempo? Sí, le llevará una o dos horas descubrir cómo usar AWS, pero puede aprender los conceptos básicos en un nivel gratuito, cargar los datos y leer las primeras 10k líneas en R para verificar que funcionó y luego puede iniciar un instancia de memoria grande como r3.8xlarge y leerlo todo! Solo mi 2c.

Sean
fuente
0

Ahora, 2017, sugeriría ir por Spark y SparkR.

  • la sintaxis se puede escribir de una manera simple bastante similar a dplyr

  • encaja bastante bien en la memoria pequeña (pequeña en el sentido de 2017)

Sin embargo, comenzar puede ser una experiencia intimidante ...

Ott Toomet
fuente
-3

Buscaría una base de datos y luego haría algunas consultas para extraer las muestras que necesita a través de DBI

Evite importar un archivo csv de 3,5 GB en SQLite. O al menos verifique que su enorme base de datos se ajuste a los límites de SQLite, http://www.sqlite.org/limits.html

Es una base de datos muy grande la que tienes. Optaría por MySQL si necesita velocidad. Pero prepárate para esperar muchas horas hasta que finalice la importación. A menos que tenga algún hardware poco convencional o esté escribiendo desde el futuro ...

El EC2 de Amazon podría ser una buena solución también para crear instancias de un servidor que ejecute R y MySQL.

mis dos humildes centavos valen.

Liborio Francesco Cannici
fuente
18
¿Cuál es el tamaño de 3.5Gb para sqlite? Siempre que esté usando un sistema de archivos apropiado, no debería haber ningún problema (regularmente uso> 30Gb sqlite dbs para aplicaciones de un solo usuario)
Aaron Statham