Cortando um arquivo csv enorme (3,5 GB) para ler em R

87

Então, eu tenho um arquivo de dados (separados por ponto-e-vírgula) que tem muitos detalhes e linhas incompletas (levando o Access e o SQL a engasgar). É o conjunto de dados em nível de condado dividido em segmentos, subsegmentos e subsegmentos (para um total de aproximadamente 200 fatores) por 40 anos. Resumindo, é enorme e não vai caber na memória se eu tentar simplesmente lê-lo.

Portanto, minha pergunta é esta, visto que quero todos os condados, mas apenas um único ano (e apenas o nível mais alto de segmento ... levando a cerca de 100.000 linhas no final), qual seria a melhor maneira de proceder para obter este rollup em R?

Atualmente estou tentando cortar anos irrelevantes com Python, contornando o limite de tamanho de arquivo lendo e operando em uma linha de cada vez, mas prefiro uma solução apenas R (pacotes CRAN OK). Existe uma maneira semelhante de ler arquivos um pedaço por vez no R?

Quaisquer ideias seriam muito apreciadas.

Atualizar:

  • Restrições
    • Precisa usar minha máquina, portanto, nenhuma instância EC2
    • O mais R-only possível. Velocidade e recursos não são preocupações neste caso ... desde que minha máquina não exploda ...
    • Como você pode ver abaixo, os dados contêm tipos mistos, que preciso operar mais tarde
  • Dados
    • Os dados são 3,5 GB, com cerca de 8,5 milhões de linhas e 17 colunas
    • Alguns milhares de linhas (~ 2k) estão malformadas, com apenas uma coluna em vez de 17
      • Estes são totalmente sem importância e podem ser descartados
    • Eu só preciso de cerca de 100.000 linhas deste arquivo (veja abaixo)

Exemplo de dados:

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]

Quero cortar algumas colunas e escolher dois dos 40 anos disponíveis (2009-2010 de 1980-2020), para que os dados possam caber em 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:

Depois de mexer em todas as sugestões feitas, decidi que readLines, sugerido por JD e Marek, funcionaria melhor. Dei o cheque a Marek porque ele deu uma implementação de amostra.

Reproduzi uma versão ligeiramente adaptada da implementação de Marek para minha resposta final aqui, usando strsplit e cat para manter apenas as colunas que desejo.

Também deve ser notado que isso é MUITO menos eficiente do que Python ... como em, Python chomps através do arquivo de 3,5 GB em 5 minutos enquanto R leva cerca de 60 ... mas se tudo que você tem é R, então este é o bilhete.

## 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)

Falhas por abordagem:

  • sqldf
    • Definitivamente, é isso que usarei para esse tipo de problema no futuro, se os dados estiverem bem formados. No entanto, se não for, o SQLite engasga.
  • MapReduce
    • Para ser honesto, os médicos me intimidaram um pouco, então não tive tempo de tentar. Parecia que era necessário que o objeto estivesse na memória também, o que invalidaria o ponto se fosse esse o caso.
  • grande memória
    • Essa abordagem vincula claramente os dados, mas só pode lidar com um tipo de cada vez. Como resultado, todos os meus vetores de caracteres caíram quando colocados em uma tabela grande. Se eu precisar projetar grandes conjuntos de dados para o futuro, porém, consideraria usar apenas números apenas para manter essa opção viva.
  • Varredura
    • A digitalização parecia ter problemas de tipo semelhantes aos de grande memória, mas com toda a mecânica de readLines. Em suma, simplesmente não se encaixava no projeto desta vez.
FTWynn
fonte
3
Se seus critérios forem simples o suficiente, você provavelmente poderá usar sede / ou awkcriar uma versão reduzida do CSV que possa ser lida diretamente. Visto que isso é mais uma solução alternativa do que uma resposta, vou deixar como um comentário.
Hank Gay
Eu concordo com Hank - você deve usar a ferramenta certa para o trabalho, e se for simples limpeza / remoção de dados irrelevantes linhas / colunas, ferramentas de fluxo de linha de comando como sort / sed / awk são ótimas e vão consumir menos recursos do que R ou python - se você der uma amostra do formato de seus arquivos, provavelmente poderíamos dar um exemplo
Aaron Statham
Ótimo. Deixe-nos saber o que você descobriu.
Shane
@Hank & Aaron: Em geral, adoro usar a ferramenta certa para o trabalho, mas como isso é em uma máquina Windows no trabalho e estou aprendendo R no decorrer do trabalho, achei que seria um bom exercício para renunciar às práticas recomendadas e tente isso como R-only, se possível.
FTWynn
2
Para referência futura, verifique o pacote R data.table. A freadfunção é muito mais rápida do que read.table. Use algo como x = fread(file_path_here, data.table=FALSE)carregá-lo como um data.frameobjeto.
paleo13

Respostas:

39

Minha tentativa com readLines. Esta parte de um código cria csvcom anos selecionados.

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
fonte
Isso é quase exatamente o que eu estava escrevendo. Acho que essa também será a melhor resposta, dadas as restrições de memória, tipos mistos e linhas malformadas.
FTWynn
10

Não sou um especialista nisso, mas você pode considerar experimentar o MapReduce , o que basicamente significaria adotar uma abordagem de "dividir para conquistar". R tem várias opções para isso, incluindo:

  1. mapReduce (R puro)
  2. RHIPE (que usa Hadoop ); veja o exemplo 6.2.2 na documentação para um exemplo de arquivos de subconjunto

Como alternativa, R fornece vários pacotes para lidar com grandes dados que vão para fora da memória (para o disco). Você provavelmente poderia carregar todo o conjunto de dados em um bigmemoryobjeto e fazer a redução completamente em R. Veja http://www.bigmemory.org/ para um conjunto de ferramentas para lidar com isso.

Shane
fonte
Boa sugestão, mas não tenho muita experiência com MapReduce e similares. Vou ter que ler sobre isso.
FTWynn
bigmemorypode ser mais fácil para você tentar primeiro, nesse caso.
Shane
10

Existe uma maneira semelhante de ler arquivos um pedaço por vez no R?

Sim. A função readChar () irá ler um bloco de caracteres sem assumir que eles são terminados em nulo. Se você quiser ler dados em uma linha por vez, você pode usar readLines () . Se você ler um bloco ou uma linha, fizer uma operação e depois gravar os dados, pode evitar o problema de memória. Porém, se você quiser iniciar uma instância de grande memória no EC2 da Amazon, poderá obter até 64 GB de RAM. Isso deve conter seu arquivo e mais espaço para manipular os dados.

Se você precisa de mais velocidade, a recomendação de Shane de usar Map Reduce é muito boa. No entanto, se você seguir o caminho de usar uma instância de grande memória no EC2, deverá examinar o pacote multicore para usar todos os núcleos em uma máquina.

Se quiser ler muitos GB de dados delimitados em R, você deve pelo menos pesquisar o pacote sqldf que permite importar diretamente para sqldf de R e operar os dados de dentro de R. Descobri que sqldf é um das maneiras mais rápidas de importar gigs de dados para o R, conforme mencionado na pergunta anterior .

JD Long
fonte
Vou manter uma instância EC2 em mente, mas no momento tenho que ficar com meu desktop e 2 GB de RAM. O sqldf definitivamente parece ser o que eu tinha em mente. No entanto, ele bloqueia nas linhas malformadas também (deveria haver 17 colunas, mas alguns milhares de linhas têm apenas uma). Isso exige algum outro método de pré-processamento ou há uma opção que estou perdendo?
FTWynn
6

Existe um pacote totalmente novo chamado colbycol que permite ler apenas as variáveis ​​que você deseja de arquivos de texto enormes:

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

Ele passa todos os argumentos para read.table, portanto, a combinação deve permitir que você crie um subconjunto bem definido.

Ari B. Friedman
fonte
6

O ffpacote é uma forma transparente de lidar com arquivos enormes.

Você pode ver o site do pacote e / ou uma apresentação sobre ele.

Eu espero que isso ajude

Todos
fonte
5

Você pode importar dados para o banco de dados SQLite e usar o RSQLite para selecionar subconjuntos.

Marek
fonte
Um bom plano, mas como é essencialmente isso que o sqldf faz nos bastidores, prefiro assim. A menos que haja uma maneira melhor de lidar com as linhas malformadas se você usar RSQLite direto.
FTWynn
5

Que tal usar readre a read_*_chunkedfamília?

Então, no seu 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)

Isso se aplica fa cada bloco, lembrando os nomes de coluna e combinando os resultados filtrados no final. Veja ?callbackqual é a fonte deste exemplo.

Isto resulta em:

# 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

Você pode até aumentar, chunk_sizemas neste exemplo existem apenas 4 linhas.

Rentrop
fonte
4

Já consisered bigmemory ? Verifique isso e isso .

George Dontas
fonte
Boa ideia. Vou dar uma olhada nisso.
FTWynn
3

Talvez você possa migrar para MySQL ou PostgreSQL para evitar as limitações do MS Access.

É muito fácil conectar R a esses sistemas com um conector de banco de dados baseado em DBI (disponível no CRAN).

FloE
fonte
Toque para usar melhores ferramentas de banco de dados, mas como isso envolveria um aborrecimento administrativo (tenho que amar essas regulamentações de administração em grandes empresas), estou tentando manter o que tenho. Além disso, estou buscando o mínimo possível de conversões entre o arquivo de texto que recebo.
FTWynn
3

scan () tem um argumento nlines e um argumento skip. Existe algum motivo pelo qual você pode simplesmente usar isso para ler um pedaço de linhas por vez, verificando a data para ver se é apropriada? Se o arquivo de entrada estiver ordenado por data, você pode armazenar um índice que informa qual deve ser o seu salto e nlines para acelerar o processo no futuro.

Frankc
fonte
Vou verificar, mas o arquivo não está classificado por nada útil como data. Os provedores parecem achar que é mais importante classificar por qual região um determinado condado está. / Suspiro ...
FTWynn
Acho que você entendeu mal a proposta dele: leia seu arquivo, pedaço por pedaço, e extraia apenas as linhas que você precisa de cada pedaço. Os arquivos não precisam ser pedidos.
Karl Forner
1

Hoje em dia, 3,5 GB simplesmente não é tão grande, posso obter acesso a uma máquina com 244 GB de RAM (r3.8xlarge) na nuvem Amazon por US $ 2,80 / hora. Quantas horas você levará para descobrir como resolver o problema usando soluções do tipo big data? Quanto vale o seu tempo? Sim, você levará uma ou duas horas para descobrir como usar a AWS - mas você pode aprender o básico em um nível gratuito, fazer upload dos dados e ler as primeiras 10k linhas em R para verificar se funcionou e, em seguida, pode iniciar um instância de grande memória como r3.8xlarge e leia tudo em! Apenas meu 2c.

Sean
fonte
0

Agora, 2017, eu sugeriria ir para spark e sparkR.

  • a sintaxe pode ser escrita de uma forma simples, bastante semelhante ao dplyr

  • se encaixa muito bem em memória pequena (pequena no sentido de 2017)

No entanto, pode ser uma experiência intimidante para começar ...

Ott Toomet
fonte
-3

Eu iria para um banco de dados e, em seguida, faria algumas consultas para extrair as amostras que você precisa via DBI

Evite importar um arquivo csv de 3,5 GB para o SQLite. Ou, pelo menos, verifique se seu banco de dados ENORME se encaixa nos limites do SQLite, http://www.sqlite.org/limits.html

É um banco de dados muito grande que você tem. Eu escolheria o MySQL se você precisar de velocidade. Mas esteja preparado para esperar muitas horas pelo término da importação. A menos que você tenha algum hardware não convencional ou esteja escrevendo do futuro ...

O EC2 da Amazon pode ser uma boa solução também para instanciar um servidor executando R e MySQL.

meu valor de dois humildes centavos.

Liborio Francesco Cannici
fonte
18
Qual é o tamanho de 3,5 Gb para sqlite? Contanto que você esteja usando um sistema de arquivos apropriado, não deve haver nenhum problema (eu estou usando regularmente dbs sqlite> 30Gb para aplicativos de usuário único)
Aaron Statham