diff dois arquivos CSV grandes (cada 90GB) e saída para outro csv

4

Esta é uma pergunta de acompanhamento de Classificar arquivos CSV grandes (90 GB), cota de disco excedida . Então agora eu tenho dois arquivos CSV classificados, como file1.csv e file2.csv Cada arquivo CSV tem 4 colunas, por exemplo,

arquivo 1:

ID Date Feature Value
01 0501 PRCP    150
01 0502 PRCP    120
02 0501 ARMS    5.6
02 0502 ARMS    5.6

arquivo 2:

ID Date Feature Value
01 0501 PRCP    170
01 0502 PRCP    120
02 0501 ARMS    5.6
02 0502 ARMS    5.6

Idealmente, quero diferenciar os dois arquivos de forma que, se duas linhas nos dois arquivos tiverem o mesmo ID, Data e Recurso, mas valores diferentes, então produza algo como:

ID Date Feature Value1 Value2

Claro, isso pode estar pedindo muito. Algo como

ID1 Date1 Feature1 Value1       ID2 Date2 Feature2 Value2

também funciona. No exemplo acima, gostaria de produzir

01 0501 PRCP    150 170

ou

01 0501 PRCP    150 01 0501 PRCP    150

Eu acho que a questão principal é como comparar de tal maneira e como saída para um arquivo csv. Obrigado.

Exemplo de saída de Gilles: A saída do comm é

$ head -20 comm_output.txt ACW00011604,19490101,PRCP,0 AE000041196,20070402,TAVG,239 AE000041196,20070402,TAVG,244 AE000041196,20080817,TMIN,282 AE000041196,20130909,TAVG,350 AE000041196,20130909,TMAX,438 AE000041196,20130909,TMIN,294 AE000041196,20130910,TAVG,339 AE000041196,20130910,TAVG,341 AE000041196,20150910,TAVG,344 A saída do awk é

$ head awk_output.csv , ACW00011604,19490101,PRCP,0,,, AE000041196,20070402,TAVG,239,,, AE000041196,20070402,TAVG,244,,, AE000041196,20080817,TMIN,282,,, AE000041196,20130909,TAVG,350,,, AE000041196,20130909,TMAX,438,,, AE000041196,20130909,TMIN,294,,, AE000041196,20130910,TAVG,339,,, AE000041196,20130910,TAVG,341,,, AE000041196,20150910,TAVG,344,,, Aqui está a entrada da amostra, se você insistir

head file1.csv

ACW00011604,19490101,PRCP,0 ACW00011604,19490101,SNOW,0 ACW00011604,19490101,SNWD,0 ACW00011604,19490101,TMAX,289 ACW00011604,19490101,TMIN,217 ACW00011604,19490102,PRCP,30 ACW00011604,19490102,SNOW,0 ACW00011604,19490102,SNWD,0 ACW00011604,19490102,TMAX,289 ACW00011604,19490102,TMIN,228

 head file2.csv

ACW00011604,19490101,SNOW,0 ACW00011604,19490101,SNWD,0 ACW00011604,19490101,TMAX,289 ACW00011604,19490101,TMIN,217 ACW00011604,19490102,PRCP,30 ACW00011604,19490102,SNOW,0 ACW00011604,19490102,SNWD,0 ACW00011604,19490102,TMAX,289 ACW00011604,19490102,TMIN,228 ACW00011604,19490102,WT16,1

    
por Xuezhou Zhang 10.03.2017 / 21:47

2 respostas

3

Vamos analisar ferramentas que combinam dois arquivos linha a linha de alguma forma:

  • colar combina dois arquivos linha por linha, sem prestar atenção a o conteúdo.
  • comm combina arquivos classificados, prestando atenção a linhas idênticas. Isso pode eliminar linhas idênticas, mas a combinação posterior da linha diferente exigiria uma ferramenta diferente.
  • join combina arquivos classificados, combinando campos idênticos juntos.
  • classificar pode mesclar dois arquivos.
  • O
  • awk pode combinar vários arquivos de acordo com as regras que você der. Mas com arquivos tão grandes, você provavelmente obterá o melhor desempenho usando as ferramentas de finalidade especial mais adequadas do que com ferramentas generalistas.

Assumirei que não há duplicados, ou seja, dentro de um arquivo não há duas linhas com o mesmo ID, data e recurso. Se houver duplicatas, então, como lidar com elas depende de como você deseja tratá-las. Eu também suponho que os arquivos estão classificados. Eu também presumo que seu shell tem substituição de processo , por exemplo bash ou ksh ao invés de simples sh, e que você tem GNU coreutils (que é o caso de Linux e Cygwin não-embarcados).

Não sei se seus separadores são espaços em branco ou guias. Eu assumirei o espaço em branco; se o separador for sempre exatamente uma guia, declarar a guia como o caractere separador ( cut -d $'\t' , join -t $'\t' , sort -t $'\t' ) e usar \ t em vez de [ \t]\+ deverá reduzir um pouco o desempenho.

Defina a localidade como puro ASCII ( LC_ALL=C ) para evitar qualquer perda de desempenho relacionada a caracteres multibyte.

Como join só pode combinar linhas com base em um campo, precisamos organizar os campos 1 a 3 para aparecer como um único campo. Para fazer isso, altere o separador, entre 1 e 2 e 2 e 3 ou entre 3 e 4. Vou mudar de 1 a 3 para usar ; em vez de espaço em branco. Dessa forma, você obtém todas as combinações de linhas, sejam elas idênticas ou não. Você pode então usar sed para remover linhas com valores idênticos.

join -a 1 -a 2 <(sed 's/[ \t]\+/;/; s/[ \t]\+/;/' file1.csv) <(sed 's/[ \t]\+/;/; s/[ \t]\+/;/' file2.csv) |
sed '/[ \t]\(.*\)[ \t]\+$/d' |
tr ';' '\t'

Observe que as linhas não pareadas acabam sendo uma linha de quatro colunas sem indicação se vieram do arquivo 1 ou arquivo 2. Remova -a 1 -a 2 para suprimir todas as linhas não parciais.

Se você tem uma maioria de linhas idênticas, isso desperdiça tempo juntando-as e eliminando-as. Outra abordagem seria usar comm -3 para eliminar as linhas idênticas. Isso produz um único fluxo de saída onde as linhas estão em ordem, mas as linhas do arquivo 2 têm uma guia principal. Você pode então usar o awk para combinar linhas consecutivas onde os dois arquivos têm os mesmos campos 1–3. Como isso envolve o awk, pode acabar sendo mais lento se houver muitas linhas não idênticas.

comm -3 file1.csv file2.csv |
awk '
    $1 "\t" $2 "\t" $3 == k { if ($4 != v) print k "\t" v "\t" $4; next; }
    { print k "\t" v }
    { k=$1 "\t" $2 "\t" $3; v=$4; }
'
    
por 11.03.2017 / 01:56
0

EDIT: Esta resposta pode funcionar bem para qualquer pessoa com 200GB de RAM livre em seu sistema . Opa.

diff --side-by-side --suppress-common-lines file1.csv file2.csv

é padronizado para separar com | e linhas de 130 caracteres (quebra, se necessário). Eu acho que não é o mesmo que o seu formato de entrada.

Você poderia tentar algo como

diff --old-line-format="%l$(printf '\t')" --new-line-format="%L" --unchanged-line-format="" file1.csv file2.csv
    
por 10.03.2017 / 22:14