Removendo linhas duplicadas com base no valor da coluna

Removendo linhas duplicadas com base no valor da coluna

Eu tenho um arquivo de texto de tamanho aprox. 25 GB. Quero excluir as linhas duplicadas com base no valor da segunda coluna. Se forem encontradas duplicatas 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, quero apenas um valor máximo de aumento para cada um, Cell_Idexcluindo 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

Responder1

Como a entrada parece já estar agrupada/classificada pela 2ª coluna, isso deve ser bastante simples enãoexigem manter e classificar todo o conjunto de dados na memória, apenas dois registros por vez. 1

Pensei primeiro em uma solução Awk, mas achei muito desajeitado lidar com matrizes e delimitadores de campos não vazios. Então 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 colunas, então você precisa retirá-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 Esta é uma grande vantagem em relaçãoWaltinatoremotorista de aço abordagens para conjuntos de dados que não cabem na memória principal.

Responder2

Se você os tivesse classificadodiminuindoordem do 4º campo, você poderia simplesmente ter obtido a primeira ocorrência de cada valor do 2º campo usando uma matriz associativa ou hash, por exemplo, awk -F, '!seen[$2]++' fileouperl -F, -ne 'print $_ unless $seen{$F[1]}++'

Com os valores em ordem crescente, é um pouco mais complicado fazer isso em uma única passagem 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

Responder3

Se você não tiver muitos Cell_ids exclusivos, poderá acompanhar os já vistos em uma matriz associativa Perl. Se você tiver muitos (e meu script Perl ficar sem memória), escreva um Cprograma 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

informação relacionada