열 값을 기준으로 중복 행 제거

열 값을 기준으로 중복 행 제거

대략 크기의 텍스트 파일이 있습니다. 25GB. 두 번째 열의 값을 기준으로 중복 행을 삭제하고 싶습니다. 파일에서 중복 항목이 발견되면 열에서 해당 값을 가진 모든 행을 삭제하고 네 번째 열에서 가장 높은 값을 가진 행 하나만 유지하고 싶습니다. 파일은 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개의 레코드 또는 CPU 초당 5MB의 처리량을 갖습니다.

용법

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 이는 다른 것보다 큰 장점 중 하나입니다.월티네이터의그리고스틸드라이버 메인 메모리에 맞지 않는 데이터 세트에 대한 접근 방식.

답변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비트 필드에 고유한 항목을 유지하는 프로그램을 작성하십시오. 여기 펄이 있습니다.

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

관련 정보