특정 열 값이 여러 번 나타나는 경우 행 삭제

특정 열 값이 여러 번 나타나는 경우 행 삭제

awk를 통해 많은 수 .tsv의 파일을 처리 중인데 2열에서 중복되거나 더 동일한 값이 관찰되는 모든 행을 삭제해야 합니다.

예를 들어

a b c
value1 2 3
value2 2 5
value3 1 9
value4 1 0
value5 4 0
value6 1 0

되어야 한다

a b c
value5 4 0

사용을 고려했지만 sort -k2 filename | awk 'count[$2]++ > 1'이상한 결과가 나왔습니다.

$ sort -k2 file.txt | awk 'count[$2]++ > 1' | wc
    162    4212   33348
$ sort -k2 file.txt | awk 'count[$2]++ == 1' | wc
   3954  102804  704176
$ wc file.txt
  1029257  26760682 176254913

...합계가 1029257이어야 하므로 의미가 없습니다. 내가 뭘 잘못했나요?

답변1

awk$2파일을 한 줄씩 반복하므로 현재 줄의 필드가 파일에서 고유한지 여부를 알 수 없습니다 .

파일을 두 번 열어야 합니다. 첫 번째는 계산을 위한 것이고 두 번째는 인쇄를 위한 것입니다 count[$2] == 1.

awk '
    NR==FNR && FNR>1 {count[$2]++};
    NR>FNR && (FNR==1 || count[$2] == 1)
' file.txt file.txt
  • NR==FNRFNR>1헤더를 계산하고 싶지 않기 때문에 첫 번째 파일에서만 true가 되도록 추가했습니다 .
  • NR>FNR이는 두 번째 파일에도 적용됩니다.
    • 헤더( FNR==1) 또는 인 경우 count[$2]인쇄 합니다 1.

답변2

매우 유사하다@pLumo의 답변그러나 NR두 번 비교하지 않고 두 번 FNR테스트하지 않고 빈 필드를 포함할 가능성을 처리하지 않습니다.FNR1

$ awk -F'\t' '
    NR == FNR { if (NR > 1) cnt[$2]++; next } 
    cnt[$2] < 2
' file file
a       b       c
value5  4       0

또는 원하는 경우:

$ awk -F'\t' '
    NR == FNR { cnt[$2] += (NR > 1); next }
    cnt[$2] < 2
' file file
a       b       c
value5  4       0

위의 2단계 접근 방식의 단점은 스트림의 입력을 처리할 수 없다는 것입니다. 이 문제를 처리해야 한다면 다음과 같이 하세요.하위 단위메소드는 모든 입력을 메모리로 읽지 않고도 이를 처리할 수 있습니다( sort요구 페이지 매김 등을 사용하여 대규모 입력을 처리하기 위한 것이지만 우리는 모든 입력을 sort모든 고유한 두 번째 필드 값에 전달하지 않고 단 2줄 이하). 원래 순서를 유지합니다. 입력 라인 중:

$ cat tst.sh
#!/usr/bin/env bash

awk '
    BEGIN { FS=OFS="\t" }
    (cnt[$2] += (NR>1)) < 3 {
        print NR, cnt[$2], $0
    }
' "${@:--}" |
sort -t $'\t' -k4,4 -k1,1rn |
awk '
    BEGIN { FS=OFS="\t" }
    $4 != prev {
        if ( $2 < 2 ) {
            print
        }
        prev = $4
    }
' |
sort -t $'\t' -k1,1n |
cut -d $'\t' -f3-

파일의 입력을 사용합니다.

$ ./tst.sh file
a       b       c
value5  4       0

그리고 스트림/파이프에서 입력:

$ cat file | ./tst.sh
a       b       c
value5  4       0

위 스크립트는 모든 버전의 awk, sort 및 cut에서 작동합니다.

답변3

pLumo의 설명에 따르면그들의 대답에, 모든 레코드를 읽을 때까지 어떤 레코드가 고유한지 알 수 없습니다. 그들의 대답은 파일을 두 번 읽음으로써 이 문제를 해결합니다. 이 문제를 해결하는 또 다른 방법은 데이터를 메모리로 읽는 것입니다. 데이터 양이 "작은" 경우(상황에 따라 "작은"에 대한 일부 정의) 이 작업을 수행할 수 있습니다.

이는 다음과 같이 수행할 수 있습니다 awk.

awk 'NR == 1 { head = $0; next }
    { count[$2]++; text[$2] = $0 }
    END { print head; for (i in count) if (count[i] == 1) print text[i] }' file

...주어진 데이터에 대해 출력됩니다.

a b c
value5 4 0

사용밀러( )는 모든 레코드를 메모리로 읽어 들인 후 동일한 값을 가진 레코드 수를 포함하는 mlr새 열을 계산합니다 . 그런 다음 해당 개수를 기준으로 레코드를 필터링하고 개수가 1인 레코드(고유 레코드)만 유지한 다음 해당 필드를 제외한 모든 항목을 출력합니다.countbcount

$ mlr --p2t count-similar -g b then filter '$count == 1' then cut -x -f count file
a       b       c
value5  4       0

이 옵션은 (input beautifulprint) 다음에 (output tab-delimited) 가 오는 약어입니다 --p2t. 데이터가 탭으로 구분된 경우 대신 사용하세요.--ipprint--otsv--tsv--p2t

답변4

솔루션은 datamash및 를 사용합니다 awk.

datamash -H -sW count 1 -f -g 2 <input |
awk '$NF == 1 || NR == 1 {$NF = ""; print}'

사용 gawk:

awk 'NR==1; 
NR>1 { if (ar[$2]) ar[$2] = "repeated";
else ar[$2] = $0 }
END { for (i=1; i<=length (ar); i++) if (ar[i] != "repeated") print ar[i] }'

관련 정보