Eliminar filas duplicadas según el valor de la columna

Eliminar filas duplicadas según el valor de la columna

Tengo un archivo de texto de tamaño aprox. 25 GB. Quiero eliminar las filas duplicadas según el valor de la segunda columna. Si se encuentran duplicados en un archivo, quiero eliminar todas las filas con ese valor en la columna y mantener solo una fila con el valor más alto en la cuarta columna. El archivo está en formato CSV y ya está ordenado.

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

En el ejemplo anterior, solo quiero un valor de aumento máximo para cada uno Cell_Ideliminando otras filas duplicadas.

El resultado esperado es:

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

Respuesta1

Dado que la entrada parece estar agrupada/ordenada por la segunda columna, esto debería ser bastante simple ynorequieren mantener y ordenar todo el conjunto de datos en la memoria, solo dos registros a la vez. 1

Primero pensé en una solución Awk, pero me resultó demasiado complicado lidiar con matrices y delimitadores de campos que no estaban en blanco. Luego me decidí por un programa Python corto:

#!/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()

En mi sistema tiene un rendimiento de ~250.000 registros o 5 MB por segundo de CPU.

Uso

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

El programa no puede manejar encabezados de columna, por lo que debes eliminarlos:

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

Si desea volver a agregarlos al resultado:

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

1 Esta es una gran ventaja sobreWaltinatoryconductor de acero enfoques para conjuntos de datos que no caben en la memoria principal.

Respuesta2

Si los hubieras ordenadodecrecienteorden del cuarto campo, simplemente podría haber tomado la primera aparición de cada valor del segundo campo usando una matriz asociativa o hash, por ejemplo, awk -F, '!seen[$2]++' fileoperl -F, -ne 'print $_ unless $seen{$F[1]}++'

Con los valores en orden creciente, es un poco más complicado hacerlo en una sola pasada eficiente; puede hacerlo (con un poco de configuración) imprimiendo la línea anterior cada vez que cambia el valor clave:

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

Respuesta3

Si no tiene demasiados Cell_ids únicos, puede realizar un seguimiento de los ya vistos en una matriz asociativa de Perl. Si tiene demasiados (y mi script Perl se queda sin memoria), escriba un Cprograma para mantener los únicos en un campo de bits. Aquí está el 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;
}

Aquí está mi prueba:

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

información relacionada