当特定列值多次出现时删除行

当特定列值多次出现时删除行

我正在通过 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在文件中是否是唯一的。

您需要打开该文件两次。第一次用于计数,第二次打印 if count[$2] == 1

awk '
    NR==FNR && FNR>1 {count[$2]++};
    NR>FNR && (FNR==1 || count[$2] == 1)
' file.txt file.txt
  • NR==FNR我补充说,仅在第一个文件上为 true FNR>1,因为我们不想计算标头。
  • NR>FNR对于第二个文件来说是正确的。
    • 打印 header( FNR==1) 或 if count[$2]is 1

答案2

非常类似于@pLumo 的回答但不比较NR两次FNR,不测试FNR两次1并处理包含空白字段的可能性:

$ 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

上述两遍方法的缺点是它无法处理来自流的输入。如果你需要处理这个,那么这个直属单位方法可以处理它,而无需 awk 将所有输入读取到内存中(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) 将所有记录读入内存,计算一个新列 ,count其中包含具有相同值的记录数b。然后根据该计数过滤记录,仅保留计数为 1 的记录(唯一的记录),然后输出除该count字段之外的所有字段:

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

该选项是(输入漂亮打印)后跟(输出制表符分隔)的--p2t简写。如果您的数据是制表符分隔的,请使用而不是.--ipprint--otsv--tsv--p2t

答案4

该解决方案使用datamashawk

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] }'

相关内容