Удаление дубликатов строк на основе значения столбца

Удаление дубликатов строк на основе значения столбца

У меня есть текстовый файл размером около 25 ГБ. Я хочу удалить дубликаты строк на основе значения во втором столбце. Если в файле будут найдены дубликаты, я хочу удалить все строки с этим значением в столбце и оставить только одну строку с самым высоким значением в четвертом столбце. Файл в формате CSV и уже отсортирован.

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

В приведенном выше примере мне нужно всего лишь одно максимальное значение всплеска для каждого, Cell_Idудалив другие дубликаты строк.

Ожидаемый результат:

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

решение1

Поскольку входные данные, по-видимому, уже сгруппированы/отсортированы по второму столбцу, это должно быть довольно просто ине делаеттребуется хранить и сортировать весь набор данных в памяти, только две записи за раз. 1

Сначала я подумал о решении на Awk, но обнаружил, что оно слишком неуклюже для работы с массивами и непустыми разделителями полей. Тогда я решил использовать короткую программу на Python:

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

На моей системе пропускная способность составляет около 250 000 записей или 5 МБ в секунду ЦП.

Применение

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

Программа не может работать с заголовками столбцов, поэтому их нужно удалить:

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

Если вы хотите добавить их обратно в результат:

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

1 Это одно из главных преимуществ по сравнению сwaltinator'sистальной водитель подходы для наборов данных, которые не помещаются в основную память.

решение2

Если бы вы их отсортировалиуменьшениепорядок 4-го поля, вы могли бы просто взять первое вхождение каждого значения 2-го поля, используя ассоциативный массив или хэш, например, awk -F, '!seen[$2]++' fileилиperl -F, -ne 'print $_ unless $seen{$F[1]}++'

При наличии значений в порядке возрастания сделать это за один эффективный проход немного сложнее — это можно сделать (с небольшой настройкой), печатая предыдущую строку каждый раз, когда изменяется значение ключа:

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

решение3

Если у вас не так много уникальных Cell_id, вы можете отслеживать уже просмотренные в ассоциативном массиве Perl. Если у вас их слишком много (и мой скрипт Perl исчерпывает память), напишите программу Cдля сохранения уникальных в битовом поле. Вот 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;
}

Вот мой тест:

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

Связанный контент