Removendo linhas duplicadas com base no valor da coluna

2

Eu tenho um arquivo de texto de tamanho aprox. 25 GB. Desejo excluir as linhas duplicadas com base no valor da segunda coluna. Se duplicatas forem encontradas em um arquivo, desejo excluir todas as linhas com esse valor na coluna e manter apenas uma linha com o valor mais alto na quarta coluna. O arquivo está no formato CSV e já está classificado.

storm_id,Cell_id,Windspeed,Storm_Surge,-1
2,10482422,45,0.06,-1
2,10482422,45,0.18,-1
2,10482422,45,0.4,-1
2,10482423,45,0.15,-1
2,10482423,45,0.43,-1
2,10482424,45,0.18,-1
2,10482424,45,0.49,-1
2,10482425,45,0.21,-1
2,10482425,45,0.52,-1
2,10482426,45,0.27,-1
2,10482426,45,0.64,-1
2,10482427,45,0.09,-1
2,10482427,45,0.34,-1
2,10482427,45,0.73,-1

No exemplo acima, eu só quero um valor máximo de pico para cada Cell_Id , excluindo outras linhas duplicadas

A saída esperada é:

2,10482422,45,0.4,-1
2,10482423,45,0.43,-1
2,10482424,45,0.49,-1
2,10482425,45,0.52,-1
2,10482426,45,0.64,-1
2,10482427,45,0.73,-1
    
por Sami 19.03.2018 / 20:47

3 respostas

1

Já que a entrada parece estar agrupada / ordenada pela segunda coluna, isso deve ser bem simples e não requer manter e ordenar todo o conjunto de dados na memória, apenas dois registros em um tempo. 1

Primeiramente, pensei em uma solução Awk, mas achei desastroso lidar com arrays e delimitadores de campo não-brancos. Então eu decidi por um programa Python curto:

#!/usr/bin/python3
import sys
DELIMITER = ','

def remove_duplicates(records):
    prev = None
    for r in records:
        r = (int(r[0]), int(r[1]), int(r[2]), float(r[3]), int(r[4]))
        if prev is None:
            prev = r
        elif r[1] != prev[1]:
            yield prev
            prev = r
        elif r[3] > prev[3]:
            prev = r
    if prev is not None:
        yield prev

def main():
    for r in remove_duplicates(
        l.rstrip('\n').rsplit(DELIMITER) for l in sys.stdin
    ):
        print(*r, sep=',')

if __name__ == '__main__':
    main()

No meu sistema, ele tem uma taxa de transferência de aproximadamente 250.000 registros ou 5 MB por segundo de CPU.

Uso

python3 remove-duplicates.py < input.txt > output.txt

O programa não consegue lidar com cabeçalhos de coluna, então você precisa desmembrá-los:

tail -n +2 < input.txt | python3 remove-duplicates.py > output.txt

Se você quiser adicioná-los de volta ao resultado:

{ read -r header && printf '%s\n' "$header" && python3 remove-duplicates.py; } < input.txt > output.txt

1 Essa é uma grande vantagem em relação às chaves de aço e do Waltinator abordagens para conjuntos de dados que não se encaixam na memória principal.

    
por David Foerster 19.03.2018 / 23:09
1

Se você os classificasse na ordem decrescente do 4º campo, você poderia simplesmente ter tomado a primeira ocorrência de cada valor do 2º campo usando um array associativo ou hash, por exemplo awk -F, '!seen[$2]++' file ou perl -F, -ne 'print $_ unless $seen{$F[1]}++'

Com os valores em ordem crescente, é um pouco mais complicado fazer isso em um passagem única eficiente - você pode fazer isso (com um pouco de configuração) imprimindo a linha anterior sempre que o valor da chave for alterado:

awk -F, '
  NR==1 {print; next}        # print the header line
  NR==2 {key=$2; next}       # initialize the comparison
  $2 != key {
    print lastval; key = $2  # print the last (largest) value of the previous key group
  } 
  {lastval = $0}             # save the current line
  END {print lastval}        # clean up
' file
storm_id,Cell_id,Windspeed,Storm_Surge,-1
2,10482422,45,0.4,-1
2,10482423,45,0.43,-1
2,10482424,45,0.49,-1
2,10482425,45,0.52,-1
2,10482426,45,0.64,-1
2,10482427,45,0.73,-1
    
por steeldriver 20.03.2018 / 00:29
0

Se você não tem muitos Cell_ids exclusivos, pode acompanhar os que já são vistos em um array associativo Perl. Se você tiver muitos (e meu script Perl ficar sem memória), escreva um programa C para manter os únicos em um campo de bits. Aqui está o Perl.

#!/usr/bin/perl -w
use strict;
my %seen = ();          # key=Cell_ID, value=1
my @cols=();            # for splitting input

while( <> ) {           # read STDIN
  @cols = split ',',$_;
  next if ( defined $seen{$cols[1]}); # skip if we already saw this Cell_Id
  $seen{$cols[1]} = 1;
  print;
}

Aqui está meu teste:

walt@bat:~(0)$ cat u.dat
storm_id,Cell_id,Windspeed,Storm_Surge,-1
2,10482422,45,0.06,-1
2,10482422,45,0.18,-1
2,10482422,45,0.4,-1
2,10482423,45,0.15,-1
2,10482423,45,0.43,-1
2,10482424,45,0.18,-1
2,10482424,45,0.49,-1
2,10482425,45,0.21,-1
2,10482425,45,0.52,-1
2,10482426,45,0.27,-1
2,10482426,45,0.64,-1
2,10482427,45,0.09,-1
2,10482427,45,0.34,-1
2,10482427,45,0.73,-1
walt@bat:~(0)$ perl ./unique.pl u.dat
storm_id,Cell_id,Windspeed,Storm_Surge,-1
2,10482422,45,0.06,-1
2,10482423,45,0.15,-1
2,10482424,45,0.18,-1
2,10482425,45,0.21,-1
2,10482426,45,0.27,-1
2,10482427,45,0.09,-1
    
por waltinator 19.03.2018 / 22:41